PY-89956 resolve local variable via PyDefUseUtil.getLatestDefs instead of PyResolveUtil.scopeCrawlUp

When a name is defined in the reference's own scope, scopeCrawlUp collects all
  same-name definitions just to find the owner and then narrows to the reaching defs
  anyway; for a variable reassigned N times that makes resolving its N references
  O(N^2) and hangs inspection. Go straight to the control-flow reaching defs for the
  plain local case; everything else falls back to the general path unchanged.

GitOrigin-RevId: 3694fdb0afd6077340e1d75eaf39090fe1f848a9
This commit is contained in:
Andrey Vokin
2026-06-15 20:55:35 +00:00
committed by intellij-monorepo-bot
parent 56f68c7cf0
commit b8a042e663
3 changed files with 29 additions and 0 deletions
@@ -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<PyImportedNameDefiner> getImportedNameDefiners();
@@ -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<PyImportedNameDefiner> getImportedNameDefiners() {
if (myImportedNameDefiners == null) {
@@ -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<Instruction> 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);