diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java index 8265559da00c..4705984b7b18 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java @@ -52,6 +52,9 @@ public interface Scope { boolean containsDeclaration(String name); + /** Cheap O(1) check whether this scope itself declares a (non-import) named element {@code name}. */ + boolean declaresName(@NotNull String name); + @NotNull List getImportedNameDefiners(); diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java index 09faeea488fa..8d4421d15303 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java @@ -169,6 +169,14 @@ public class ScopeImpl implements Scope { return false; } + @Override + public boolean declaresName(@NotNull String name) { + if (myNamedElements == null) { + collectDeclarations(); + } + return myNamedElements.containsKey(name); + } + @Override public @NotNull List getImportedNameDefiners() { if (myImportedNameDefiners == null) { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java index 2c33c7ac5656..22d1ad948549 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java @@ -274,6 +274,24 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference return ((PyFile)realContext).multiResolveName(referencedName); } + // PY-89956 fast path: for a plain local variable, resolve straight to its control-flow + // reaching definitions instead of first collecting *all* same-name definitions of the scope + final TypeEvalContext typeEvalContext = myContext.getTypeEvalContext(); + final ScopeOwner owner = ScopeUtil.getScopeOwner(realContext); + if (typeEvalContext.maySwitchToAST(realContext) && owner != null && !(owner instanceof PyClass)) { + final Scope scope = ControlFlowCache.getScope(owner); + if (scope.declaresName(referencedName) && !scope.isGlobal(referencedName) && !scope.isNonlocal(referencedName)) { + final List defs = + PyDefUseUtil.getLatestDefs(owner, referencedName, realContext, false, true, typeEvalContext).defs(); + if (!defs.isEmpty() && ContainerUtil.and(defs, i -> i.getElement() instanceof PyTargetExpression)) { + final ResolveResultList latest = resolveToLatestDefs(defs, realContext, referencedName, typeEvalContext); + if (!latest.isEmpty()) { + return latest; + } + } + } + } + // here we have an unqualified expr. it may be defined: // ...in current file final PyResolveProcessor processor = new PyResolveProcessor(referencedName);