fix(python): the module of a from-import no longer links a same-named variable (#2603)

The module of a Python `from X import ...` names a module or package,
never a symbol, so `from app import email` no longer links the
importing file to a project variable named app, like a FastAPI or
Flask app = FastAPI(). A from-import module that resolves to anything
but a file or module links nothing. On five Python repos this removes
12 wrong import links and nothing else.

Fixes issue 2584.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Eric Minish
2026-10-20 17:02:50 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent e48565067e
commit 4f222a3d65
4 changed files with 102 additions and 1 deletions
+5 -1
View File
@@ -21,7 +21,7 @@ import {
isImportableKind,
CPP_DEFINE_SIGNATURE,
} from './types';
import { isPythonSelfCall, matchJsStoreBindingCall, isUnresolvedJsMemberCall, matchObjectPathCall, thisScopeCaller, isVisibleAcrossFiles, matchReference, matchByFilePath, matchFunctionRef, matchDottedCallChain, matchScopedCallChain, matchMethodCall, sameLanguageFamily, crossesCodeBoundary, gateLanguageMatch, dumpNameMatcherProfile, clearNameMatcherMemos, isRustNameInScope, isRustForeignImplCallable, CASE_INSENSITIVE_LANGUAGES, isDartMemberRead, matchDartMemberRead, isDartChainLink, matchDartChainLink, isDartAnnotation, matchDartAnnotation, isStdMethodName, isGoUnknownQualified, isGoProjectLocalCall, GO_STDLIB_PACKAGES, isGoBareName, goTypePositionTarget, GO_TYPE_KINDS, matchGoAssertedCall, isGoAssertedLocal, isGoLocallyBound } from './name-matcher';
import { isPythonSelfCall, matchJsStoreBindingCall, isUnresolvedJsMemberCall, matchObjectPathCall, thisScopeCaller, isVisibleAcrossFiles, matchReference, matchByFilePath, matchFunctionRef, matchDottedCallChain, matchScopedCallChain, matchMethodCall, sameLanguageFamily, crossesCodeBoundary, gateLanguageMatch, dumpNameMatcherProfile, clearNameMatcherMemos, isRustNameInScope, isRustForeignImplCallable, CASE_INSENSITIVE_LANGUAGES, isDartMemberRead, matchDartMemberRead, isDartChainLink, matchDartChainLink, isDartAnnotation, matchDartAnnotation, isStdMethodName, isGoUnknownQualified, isGoProjectLocalCall, GO_STDLIB_PACKAGES, isGoBareName, goTypePositionTarget, GO_TYPE_KINDS, matchGoAssertedCall, isGoAssertedLocal, isGoLocallyBound, isPythonFromImportModule } from './name-matcher';
import { isVisibleCppMacro, clearCppMacroVisibility } from './cpp-macro-visibility';
import { isCppConstructorRef, matchCppConstructor } from './cpp-constructor';
import { isCppSupertypeRef, matchCppSupertype, clearCppSupertypeMemos } from './cpp-supertypes';
@@ -1255,6 +1255,10 @@ export class ReferenceResolver {
// names itself: a self-edge that says nothing (#2327).
if (resolved && ref.language === 'dart' && ref.referenceKind === 'references' &&
resolved.targetNodeId === ref.fromNodeId) return null;
if (resolved && isPythonFromImportModule(ref, this.context)) {
const module = this.nodeById(resolved.targetNodeId);
if (module && module.kind !== 'file' && module.kind !== 'module') return null;
}
if (!resolved || ref.referenceKind !== 'calls') return resolved;
const target = this.nodeById(resolved.targetNodeId);
+20
View File
@@ -16637,6 +16637,26 @@ function goEmbeddedTypes(typeNode: Node, context: ResolutionContext): Array<{ na
return embedded;
}
/**
* The module of a Python `from X import …`, which the extractor refs at the
* statement's start. It names a module or package, never a symbol: `from app
* import email` is not a use of a variable named `app` (#2584).
*/
export function isPythonFromImportModule(ref: UnresolvedRef, context: ResolutionContext): boolean {
if (ref.language !== 'python' || ref.referenceKind !== 'imports') return false;
const lines = context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/);
const line = lines?.[ref.line - 1];
if (line === undefined || !line.startsWith('from', ref.column)) return false;
// `from app \` continues the statement on the next line.
let text = line.slice(ref.column + 4);
for (let next = ref.line; /\\\s*$/.test(text) && next < ref.line + 3; next++) {
text = text.replace(/\\\s*$/, ' ') + (lines![next] ?? '');
}
const name = text.trimStart();
return name.length < text.length && name.startsWith(ref.referenceName) &&
/^\s+import\b/.test(name.slice(ref.referenceName.length));
}
/**
* A Go call made through a type assertion — `srv.(KVServer).Range(ctx, in)`,
* `c.Reader.(*pipe).Close()`, `v.(storage.Store).Fetch(k)` — calls a method of