From d6175bb3354215da833f7f644cbcfd228e04f36c Mon Sep 17 00:00:00 2001 From: Angus Lees Date: Wed, 14 Oct 2026 14:20:16 +1100 Subject: [PATCH] fix: Resolve the package of class/module symbols from the declaring file typeToSymbol resolved the package of a class or module type with getPackageInfo(node, ...), which classifies by the file containing the reference. Every reference emitted while visiting a project file is in the project, so any class reached through an alias declaration (`pkg.Thing`, or `Thing` re-exported from a submodule) was emitted under the project package instead of the package that declares it. Functions already used guessPackage(decl.moduleName, decl.path) and were correct. Use guessPackage with the type's own moduleName and filePath for classes, modules and TypeVar bounds, falling back to a local symbol when the package is unknown, as getFunctionSymbol does. Adds a snapshot test with a fake `widgets` package exposed through extraPaths and listed in testEnv.json, so references to classes re-exported from a submodule are checked against the dependency's package. --- .../external_class_import/external_class.py | 10 +++++ .../external_class_import/pyrightconfig.json | 4 ++ .../external_class_import/external_class.py | 37 +++++++++++++++++++ .../site-packages/widgets/__init__.py | 3 ++ .../snapshots/site-packages/widgets/models.py | 11 ++++++ packages/pyright-scip/snapshots/testEnv.json | 5 +++ packages/pyright-scip/src/treeVisitor.ts | 20 ++++++++-- 7 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 packages/pyright-scip/snapshots/input/external_class_import/external_class.py create mode 100644 packages/pyright-scip/snapshots/input/external_class_import/pyrightconfig.json create mode 100644 packages/pyright-scip/snapshots/output/external_class_import/external_class.py create mode 100644 packages/pyright-scip/snapshots/site-packages/widgets/__init__.py create mode 100644 packages/pyright-scip/snapshots/site-packages/widgets/models.py diff --git a/packages/pyright-scip/snapshots/input/external_class_import/external_class.py b/packages/pyright-scip/snapshots/input/external_class_import/external_class.py new file mode 100644 index 000000000..484228b3d --- /dev/null +++ b/packages/pyright-scip/snapshots/input/external_class_import/external_class.py @@ -0,0 +1,10 @@ +import widgets +from widgets import Widget + + +def render(name: str, width: int) -> str: + widget: widgets.Widget = widgets.Widget(name, width=width) + try: + return widgets.Widget.render(widget, prefix="> ") + except widgets.WidgetError: + return "" diff --git a/packages/pyright-scip/snapshots/input/external_class_import/pyrightconfig.json b/packages/pyright-scip/snapshots/input/external_class_import/pyrightconfig.json new file mode 100644 index 000000000..7d6d6a962 --- /dev/null +++ b/packages/pyright-scip/snapshots/input/external_class_import/pyrightconfig.json @@ -0,0 +1,4 @@ +{ + "pythonVersion": "3.11", + "extraPaths": ["../../site-packages"] +} diff --git a/packages/pyright-scip/snapshots/output/external_class_import/external_class.py b/packages/pyright-scip/snapshots/output/external_class_import/external_class.py new file mode 100644 index 000000000..9a3d10489 --- /dev/null +++ b/packages/pyright-scip/snapshots/output/external_class_import/external_class.py @@ -0,0 +1,37 @@ +# < definition scip-python python snapshot-util 0.1 external_class/__init__: + +import widgets +# ^^^^^^^ reference widgets 1.2.3 widgets/__init__: +from widgets import Widget +# ^^^^^^^ reference widgets 1.2.3 widgets/__init__: +# ^^^^^^ reference widgets 1.2.3 `widgets.models`/Widget# + + +def render(name: str, width: int) -> str: +# ^^^^^^ definition snapshot-util 0.1 external_class/render(). +# ^^^^ definition snapshot-util 0.1 external_class/render().(name) +# ^^^ reference python-stdlib 3.11 builtins/str# +# ^^^^^ definition snapshot-util 0.1 external_class/render().(width) +# ^^^ reference python-stdlib 3.11 builtins/int# +# ^^^ reference python-stdlib 3.11 builtins/str# + widget: widgets.Widget = widgets.Widget(name, width=width) +# ^^^^^^ definition local 0 +# ^^^^^^^ reference widgets 1.2.3 widgets/__init__: +# ^^^^^^ reference widgets 1.2.3 `widgets.models`/Widget# +# ^^^^^^^ reference widgets 1.2.3 widgets/__init__: +# ^^^^^^ reference widgets 1.2.3 `widgets.models`/Widget# +# ^^^^ reference snapshot-util 0.1 external_class/render().(name) +# ^^^^^ reference widgets 1.2.3 `widgets.models`/Widget#__init__().(width) +# ^^^^^ reference snapshot-util 0.1 external_class/render().(width) + try: + return widgets.Widget.render(widget, prefix="> ") +# ^^^^^^^ reference widgets 1.2.3 widgets/__init__: +# ^^^^^^ reference widgets 1.2.3 `widgets.models`/Widget# +# ^^^^^^ reference widgets 1.2.3 `widgets.models`/Widget#render(). +# ^^^^^^ reference local 0 +# ^^^^^^ reference widgets 1.2.3 `widgets.models`/Widget#render().(prefix) + except widgets.WidgetError: +# ^^^^^^^ reference widgets 1.2.3 widgets/__init__: +# ^^^^^^^^^^^ reference widgets 1.2.3 `widgets.models`/WidgetError# + return "" + diff --git a/packages/pyright-scip/snapshots/site-packages/widgets/__init__.py b/packages/pyright-scip/snapshots/site-packages/widgets/__init__.py new file mode 100644 index 000000000..d2ae7bf59 --- /dev/null +++ b/packages/pyright-scip/snapshots/site-packages/widgets/__init__.py @@ -0,0 +1,3 @@ +from .models import Widget, WidgetError + +__all__ = ["Widget", "WidgetError"] diff --git a/packages/pyright-scip/snapshots/site-packages/widgets/models.py b/packages/pyright-scip/snapshots/site-packages/widgets/models.py new file mode 100644 index 000000000..798ad90d5 --- /dev/null +++ b/packages/pyright-scip/snapshots/site-packages/widgets/models.py @@ -0,0 +1,11 @@ +class WidgetError(Exception): + pass + + +class Widget: + def __init__(self, name: str, width: int = 0) -> None: + self.name = name + self.width = width + + def render(self, prefix: str = "") -> str: + return prefix + self.name diff --git a/packages/pyright-scip/snapshots/testEnv.json b/packages/pyright-scip/snapshots/testEnv.json index de02ed5bb..b7778de65 100644 --- a/packages/pyright-scip/snapshots/testEnv.json +++ b/packages/pyright-scip/snapshots/testEnv.json @@ -13,5 +13,10 @@ "name": "sqlparse", "version": "0.4.2", "files": [] + }, + { + "name": "widgets", + "version": "1.2.3", + "files": ["widgets/__init__.py", "widgets/models.py"] } ] diff --git a/packages/pyright-scip/src/treeVisitor.ts b/packages/pyright-scip/src/treeVisitor.ts index 4ccbe804d..1379027ab 100644 --- a/packages/pyright-scip/src/treeVisitor.ts +++ b/packages/pyright-scip/src/treeVisitor.ts @@ -1123,7 +1123,13 @@ export class TreeVisitor extends ParseTreeWalker { const bound = typeVar.details.boundType! as ClassType; return this.getSymbolOnce(node, () => { - const pythonPackage = this.getPackageInfo(node, bound.details.moduleName)!; + const pythonPackage = this.guessPackage( + bound.details.moduleName, + bound.details.filePath + ); + if (!pythonPackage) { + return ScipSymbol.local(this.counter.next()); + } let symbol = Symbols.makeTerm( Symbols.makeType( Symbols.makeModule(pythonPackage, bound.details.moduleName), @@ -1343,7 +1349,12 @@ export class TreeVisitor extends ParseTreeWalker { } return this.getFunctionSymbol(decl); } else if (Types.isClass(typeObj)) { - const pythonPackage = this.getPackageInfo(node, typeObj.details.moduleName)!; + // The package comes from the declaring file. `node` is the referencing + // site, which is in the project whenever a project file is visited. + const pythonPackage = this.guessPackage(typeObj.details.moduleName, typeObj.details.filePath); + if (!pythonPackage) { + return ScipSymbol.local(this.counter.next()); + } return Symbols.makeClass(pythonPackage, typeObj.details.moduleName, node.value); } else if (Types.isClassInstance(typeObj)) { typeObj = typeObj as ClassType; @@ -1352,7 +1363,10 @@ export class TreeVisitor extends ParseTreeWalker { } else if (Types.isTypeVar(typeObj)) { throw 'typevar'; } else if (Types.isModule(typeObj)) { - const pythonPackage = this.getPackageInfo(node, typeObj.moduleName)!; + const pythonPackage = this.guessPackage(typeObj.moduleName, typeObj.filePath); + if (!pythonPackage) { + return ScipSymbol.local(this.counter.next()); + } return Symbols.makeModuleInit(pythonPackage, typeObj.moduleName); } else if (Types.isOverloadedFunction(typeObj)) { if (!typeObj.overloads) {