From f8544fb8ee7307ec09d8623ef00380a5818de13b Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Wed, 4 Dec 2019 03:10:04 +0300 Subject: [PATCH] PY-26062, PY-38540, PY-25935: Various Django performance improvements. * Do not provide references for "open" in Django (already done by another code) * Do not use full resolve for class and functions names. In most cases "resolve", "ManyToMany" and "ModelForm" are imported as-is, not with aliases. GitOrigin-RevId: 8a0803e6b19482654c91186aa9754ea3d91da282 --- .../nameResolver/NameResolverTools.java | 29 +++++++++++++++---- .../python/psi/PyCallExpression.java | 9 ++++-- .../python/psi/impl/PyEvaluator.java | 5 ++-- .../extensions/python/PyCallExpressionExt.kt | 21 ++++++++++++++ 4 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 python/src/com/jetbrains/extensions/python/PyCallExpressionExt.kt diff --git a/python/python-psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java b/python/python-psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java index 8219e5d27d24..d2749be8c696 100644 --- a/python/python-psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java +++ b/python/python-psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java @@ -21,12 +21,14 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.RecursionManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiNamedElement; import com.intellij.psi.PsiReference; import com.intellij.psi.util.PsiCacheKey; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.QualifiedName; import com.intellij.util.Function; +import com.intellij.util.ObjectUtils; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.types.TypeEvalContext; @@ -72,9 +74,22 @@ public final class NameResolverTools { return false; } + /** + * Same as {@link #isName(PyElement, FQNamesProvider...)} for named elements, but only checks name. + * Aliases not supported, but much lighter that way + */ + public static boolean isNameShortCut(@NotNull PyElement element, @NotNull FQNamesProvider... namesProviders) { + String name = element.getName(); + if (name == null) { + return false; + } + return Arrays.stream(namesProviders).anyMatch(o -> getLastComponents(o).contains(name)); + } + /** * Checks if FQ element name is one of provided names. May be heavy. * It is always better to use less accurate but lighter {@link #isCalleeShortCut(PyCallExpression, FQNamesProvider)} + * and {@link #isNameShortCut(PyElement, FQNamesProvider...)} * * @param element element to check * @param namesProviders some enum that has one or more names @@ -83,7 +98,8 @@ public final class NameResolverTools { public static boolean isName(@NotNull final PyElement element, @NotNull final FQNamesProvider... namesProviders) { assert element.isValid(); final Pair qualifiedAndClassName = RecursionManager.doPreventingRecursion(element, false, - () -> QUALIFIED_AND_CLASS_NAME.getValue(element)); + () -> QUALIFIED_AND_CLASS_NAME + .getValue(element)); LOG.assertTrue(qualifiedAndClassName != null); //noinspection ConstantConditions if (qualifiedAndClassName == null) return false; @@ -119,7 +135,7 @@ public final class NameResolverTools { } /** - * Same as {@link #isName(PyElement, FQNamesProvider...)} for call expr, but first checks name. + * Same as {@link #isName(PyElement, FQNamesProvider...)} for call expr, but only checks name. * Aliases not supported, but much lighter that way * * @param call expr @@ -127,7 +143,7 @@ public final class NameResolverTools { * @return true if callee is correct */ public static boolean isCalleeShortCut(@NotNull final PyCallExpression call, - @NotNull final FQNamesProvider function) { + @NotNull final FQNamesProvider... function) { final PyExpression callee = call.getCallee(); if (callee == null) { return false; @@ -135,8 +151,7 @@ public final class NameResolverTools { final String callableName = callee.getName(); - final Collection possibleNames = getLastComponents(function); - return possibleNames.contains(callableName) && call.isCallee(function); + return Arrays.stream(function).anyMatch(o -> getLastComponents(o).contains(callableName)); } @NotNull @@ -178,6 +193,7 @@ public final class NameResolverTools { /** * Check if class has parent with some name + * * @param child class to check */ public static boolean isSubclass(@NotNull final PyClass child, @@ -205,7 +221,8 @@ public final class NameResolverTools { @Override public boolean value(final PsiElement element) { if (element instanceof PyCallExpression) { - return ((PyCallExpression)element).isCallee(myNameToSearch); + PyCallExpression callExpression = (PyCallExpression)element; + return isCalleeShortCut(callExpression, myNameToSearch); } return false; } diff --git a/python/python-psi-api/src/com/jetbrains/python/psi/PyCallExpression.java b/python/python-psi-api/src/com/jetbrains/python/psi/PyCallExpression.java index a559e66ce1c4..8fa2a5bd4442 100644 --- a/python/python-psi-api/src/com/jetbrains/python/psi/PyCallExpression.java +++ b/python/python-psi-api/src/com/jetbrains/python/psi/PyCallExpression.java @@ -233,14 +233,17 @@ public interface PyCallExpression extends PyCallSiteExpression { /** * Checks if the qualified name of the callee matches any of the specified names provided by provider. - * May be heavy. - * Use {@link NameResolverTools#isCalleeShortCut(PyCallExpression, FQNamesProvider)} - * if you can. + * May be heavy, and it is not recommended to use. + * Use {@link NameResolverTools#isCalleeShortCut(PyCallExpression, FQNamesProvider...)} or + * {@link com.jetbrains.extensions.python.PyCallExpressionExtKt#isCalleeName(PyCallExpression, FQNamesProvider...)}. * * @param name providers that provides one or more names to check * @return true if matches, false otherwise * @see com.jetbrains.python.nameResolver + * @see com.jetbrains.extensions.python.PyCallExpressionExtKt#isCalleeName(PyCallExpression, FQNamesProvider...) + * @deprecated use {@link com.jetbrains.extensions.python.PyCallExpressionExtKt#isCalleeName(PyCallExpression, FQNamesProvider...)}. */ + @Deprecated default boolean isCallee(@NotNull FQNamesProvider... name) { final PyExpression callee = getCallee(); return callee instanceof PyReferenceExpression && NameResolverTools.isName(callee, name); diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyEvaluator.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyEvaluator.java index c7cd4f8cdfea..237393ac4850 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyEvaluator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyEvaluator.java @@ -23,6 +23,7 @@ import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.PythonFQDNNames; +import com.jetbrains.python.nameResolver.NameResolverTools; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; import org.jetbrains.annotations.Contract; @@ -302,7 +303,7 @@ public class PyEvaluator { } // Support dict([("k", "v")]) syntax - if (myEnableResolve && expression.isCallee(PythonFQDNNames.DICT_CLASS)) { + if (myEnableResolve && NameResolverTools.isCalleeShortCut(expression, PythonFQDNNames.DICT_CLASS)) { final Collection tuples = PsiTreeUtil.findChildrenOfType(expression, PyTupleExpression.class); if (!tuples.isEmpty()) { final Map result = new HashMap<>(); @@ -470,7 +471,7 @@ public class PyEvaluator { @Nullable private Object evaluateOrGet(@Nullable final PyExpression expression) { final Object result = evaluate(expression); - if (result !=null) { + if (result != null) { return result; } return myAllowExpressionsAsValues ? expression : null; diff --git a/python/src/com/jetbrains/extensions/python/PyCallExpressionExt.kt b/python/src/com/jetbrains/extensions/python/PyCallExpressionExt.kt new file mode 100644 index 000000000000..98ad7a6beec7 --- /dev/null +++ b/python/src/com/jetbrains/extensions/python/PyCallExpressionExt.kt @@ -0,0 +1,21 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.extensions.python + +import com.jetbrains.python.nameResolver.FQNamesProvider +import com.jetbrains.python.nameResolver.NameResolverTools +import com.jetbrains.python.psi.PyAssignmentStatement +import com.jetbrains.python.psi.PyCallExpression +import com.jetbrains.python.psi.PyPossibleClassMember + +/** + * Checks if ``foo = SomeExpr()`` where foo is class attribute + */ +val PyCallExpression.isClassAttribute: Boolean + get() = + (parent as? PyAssignmentStatement)?.targets?.filterIsInstance()?.any { it.containingClass != null } == true + +/** + * Checks if callee has certain name. Only name is checked, so import aliases aren't supported, but it works pretty fast + */ +fun PyCallExpression.isCalleeName(vararg names: FQNamesProvider): Boolean = NameResolverTools.isCalleeShortCut(this, *names) +