Skip to content

Fix #11481: 1.1.410 detects type as module#11501

Open
rchiodo wants to merge 2 commits into
microsoft:mainfrom
rchiodo:fix11481
Open

Fix #11481: 1.1.410 detects type as module#11501
rchiodo wants to merge 2 commits into
microsoft:mainfrom
rchiodo:fix11481

Conversation

@rchiodo

@rchiodo rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Description

When a package re-exports (via a wildcard from .sub import *) a name that is both an implicitly-imported submodule and a class of the same name, Pylance 1.1.410 incorrectly resolved the name to the submodule instead of the class. This broke using the name as a type annotation or calling it.

How you figured out what to do

Reproduced the issue with a package whose submodule (ImageView.py) defines a class (ImageView) re-exported through wildcard imports. The symbol ended up with multiple declarations: an implicit submodule alias and the class. The binder's multipart module-alias handling treated the wildcard re-export as a pure submodule re-export, shadowing the class even though the non-module declaration is the symbol's effective (last) declaration.

Implementation

In binder.ts, the wildcard re-export is now only treated as a pure submodule re-export when the module alias is the symbol's effective (last) declaration. When a later non-module declaration (the class) wins, the binder falls through to create a normal alias declaration that resolves to the winning symbol.

const importedDecls = importedSymbol.getDeclarations();
if (importedDecls[importedDecls.length - 1] !== importedModuleAliasDecl) {
    return false;
}

Testing

Added a fourslash regression test (import.multipartModuleClassShadow.fourslash.ts) that sets up a package re-exporting a class whose name matches a submodule. It verifies the name resolves to type[ImageView], can be used as a type annotation and called, with no diagnostics.

Run with:

cd packages/pyright/packages/pyright-internal
node node_modules\jest\bin\jest fourSlashRunner.test.ts --runInBand -t import.multipartModuleClassShadow.fourslash

Addresses

Fixes https://github.com/microsoft/pylance-release/issues/11481


Generated by fix_all_my_issues pipeline

When a package re-exports (via 'from .sub import *') a name that is both an implicitly-imported submodule and a class/function/variable of the same name, the wildcard merge introduced in microsoft#11396 propagated only the submodule alias, dropping the real symbol. The re-exported name then resolved to a Module instead of the class.

_addWildcardImportedModuleAlias now only treats the wildcard re-export as a pure submodule re-export when the module alias is the imported symbol's effective (last) declaration; otherwise it falls through so a normal alias declaration is created that resolves to the winning symbol.

Fixes microsoft#11481

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Comment thread packages/pyright-internal/src/analyzer/binder.ts
Comment thread packages/pyright-internal/src/analyzer/binder.ts
Comment thread packages/pyright-internal/src/analyzer/binder.ts Outdated
@rchiodo

rchiodo commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Sound, minimal fix with good regression coverage — approving. A couple of non-blocking follow-ups worth considering: (1) the new guard keys off the raw last getDeclarations() entry rather than the evaluator's canonical typed "effective declaration" notion, so a short comment (or reuse of the canonical helper) would lock in that the two agree; (2) consider a companion assertion that a pure submodule re-export (no shadowing class) still resolves as a module, to guard the original multipart-alias behavior. Also note the inline issue-URL comments — this repo prefers issue links in PR/commit metadata rather than in source/tests.

…ubmodule re-export

- Explain why the guard intentionally uses the raw last declaration rather than getLastTypedDeclarationForSymbol (alias decls are untyped) and note the deliberate loss of submoduleFallback when the class wins.

- Drop inline issue links per repo convention (kept in PR/commit metadata).

- Add a companion fourslash assertion verifying a pure submodule re-export still resolves as a module with accessible members.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

sympy (https://github.com/sympy/sympy)
-   .../projects/sympy/sympy/solvers/tests/test_solvers.py:1216:18 - error: Operator "+" not supported for types "Basic" and "Expr" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:315:25 - error: "Set" is not iterable
+     "__iter__" method not defined (reportGeneralTypeIssues)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:315:25 - error: "ConditionSet" is not iterable
+     "__iter__" method not defined (reportGeneralTypeIssues)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2030:20 - error: "__getitem__" method not defined on type "Basic" (reportIndexIssue)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2042:20 - error: "__getitem__" method not defined on type "Basic" (reportIndexIssue)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2055:20 - error: "__getitem__" method not defined on type "Basic" (reportIndexIssue)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2076:16 - error: Argument of type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" cannot be assigned to parameter "obj" of type "Sized" in function "len"
+     Type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" is not assignable to type "Sized"
+       "Set" is incompatible with protocol "Sized"
+         "__len__" is not present (reportArgumentType)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2077:20 - error: "__getitem__" method not defined on type "Basic" (reportIndexIssue)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2270:16 - error: Argument of type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" cannot be assigned to parameter "obj" of type "Sized" in function "len"
+     Type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" is not assignable to type "Sized"
+       "Set" is incompatible with protocol "Sized"
+         "__len__" is not present (reportArgumentType)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2351:16 - error: Argument of type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" cannot be assigned to parameter "obj" of type "Sized" in function "len"
+     Type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" is not assignable to type "Sized"
+       "Set" is incompatible with protocol "Sized"
+         "__len__" is not present (reportArgumentType)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2364:16 - error: Argument of type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" cannot be assigned to parameter "obj" of type "Sized" in function "len"
+     Type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" is not assignable to type "Sized"
+       "Set" is incompatible with protocol "Sized"
+         "__len__" is not present (reportArgumentType)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2370:16 - error: Argument of type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" cannot be assigned to parameter "obj" of type "Sized" in function "len"
+     Type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" is not assignable to type "Sized"
+       "Set" is incompatible with protocol "Sized"
+         "__len__" is not present (reportArgumentType)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2538:16 - error: Argument of type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" cannot be assigned to parameter "obj" of type "Sized" in function "len"
+     Type "Unknown | FiniteSet | Set | Intersection | Union | Complement | Any | ConditionSet" is not assignable to type "Sized"
+       "Set" is incompatible with protocol "Sized"
+         "__len__" is not present (reportArgumentType)
+   .../projects/sympy/sympy/solvers/tests/test_solveset.py:2541:20 - error: Argument of type "Unknown | Basic | Any" cannot be assigned to parameter "obj" of type "Sized" in function "len"
+     Type "Unknown | Basic | Any" is not assignable to type "Sized"
+       "Basic" is incompatible with protocol "Sized"
+         "__len__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/stats/crv_types.py:2544:9 - error: Method "_cdf" overrides class "SingleContinuousDistribution" in an incompatible manner
-     Return type mismatch: base method returns type "None", override returns type "ComplexInfinity | NaN | Rational | Unknown | Zero | Expr"
-       Type "ComplexInfinity | NaN | Rational | Unknown | Zero | Expr" is not assignable to type "None"
-         "Expr" is not assignable to "None" (reportIncompatibleMethodOverride)
-   .../projects/sympy/sympy/stats/crv_types.py:2723:9 - error: Method "_cdf" overrides class "SingleContinuousDistribution" in an incompatible manner
-     Return type mismatch: base method returns type "None", override returns type "Expr | NaN | ComplexInfinity | Rational | Unknown | One | NegativeOne | Zero | Integer"
-       Type "Expr | NaN | ComplexInfinity | Rational | Unknown | One | NegativeOne | Zero | Integer" is not assignable to type "None"
-         "Expr" is not assignable to "None" (reportIncompatibleMethodOverride)
-   .../projects/sympy/sympy/stats/drv.py:269:22 - error: Argument of type "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None, None, None]" cannot be assigned to parameter "iterable" of type "Iterable[_SupportsSumNoDefaultT@sum]" in function "sum"
+   .../projects/sympy/sympy/stats/drv.py:269:22 - error: Argument of type "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None, None, None]" cannot be assigned to parameter "iterable" of type "Iterable[_SupportsSumNoDefaultT@sum]" in function "sum"
-     "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None, None, None]" is not assignable to "Iterable[_SupportsSumNoDefaultT@sum]"
+     "Generator[tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None, None, None]" is not assignable to "Iterable[_SupportsSumNoDefaultT@sum]"
-       Type parameter "_T_co@Iterable" is covariant, but "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None" is not a subtype of "_SupportsSumNoDefaultT@sum"
+       Type parameter "_T_co@Iterable" is covariant, but "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None" is not a subtype of "_SupportsSumNoDefaultT@sum"
-         Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
+         Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
-           Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
+           Type "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic | int | None" is not assignable to type "_SupportsSumWithNoDefaultGiven"
-   .../projects/sympy/sympy/stats/drv_types.py:293:16 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic"
+   .../projects/sympy/sympy/stats/drv_types.py:293:16 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Sum | Expr | ZeroMatrix | Zero | NaN | Piecewise | Basic"
-   .../projects/sympy/sympy/stats/frv_types.py:139:17 - error: Argument of type "NaN | ComplexInfinity | Rational | Unknown | Expr" cannot be assigned to parameter "value" of type "int" in function "__setitem__"
-     Type "NaN | ComplexInfinity | Rational | Unknown | Expr" is not assignable to type "int"
-       "Expr" is not assignable to "int" (reportArgumentType)
-   .../projects/sympy/sympy/stats/rv.py:473:25 - error: Argument of type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" cannot be assigned to parameter "args" of type "Expr | complex" in function "__new__"
+   .../projects/sympy/sympy/stats/rv.py:473:25 - error: Argument of type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" cannot be assigned to parameter "args" of type "Expr | complex" in function "__new__"
-     Type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" is not assignable to type "Expr | complex"
+     Type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" is not assignable to type "Expr | complex"

... (truncated 657 lines) ...

jax (https://github.com/google/jax)
-   .../projects/jax/jax/experimental/mosaic/gpu/examples/flash_attention.py:222:20 - error: Module is not callable (reportCallIssue)
-   .../projects/jax/jax/experimental/mosaic/gpu/examples/flash_attention.py:255:24 - error: Module is not callable (reportCallIssue)
-   .../projects/jax/jax/experimental/mosaic/gpu/examples/flash_attention.py:444:18 - error: Module is not callable (reportCallIssue)
-   .../projects/jax/jax/experimental/mosaic/gpu/examples/flash_attention.py:480:22 - error: Module is not callable (reportCallIssue)
-   .../projects/jax/jax/experimental/mosaic/gpu/examples/matmul.py:91:11 - error: Module is not callable (reportCallIssue)
-   .../projects/jax/jax/experimental/mosaic/gpu/examples/matmul.py:94:12 - error: Type "Unknown | WGMMAAccumulator" is not assignable to return type "dict[str, WGMMAAccumulator]"
+   .../projects/jax/jax/experimental/mosaic/gpu/examples/matmul.py:94:12 - error: Type "WGMMAAccumulator" is not assignable to return type "dict[str, WGMMAAccumulator]"
-     Type "Unknown | WGMMAAccumulator" is not assignable to type "dict[str, WGMMAAccumulator]"
-       "WGMMAAccumulator" is not assignable to "dict[str, WGMMAAccumulator]" (reportReturnType)
+     "WGMMAAccumulator" is not assignable to "dict[str, WGMMAAccumulator]" (reportReturnType)
- 3402 errors, 90 warnings, 0 informations
+ 3397 errors, 90 warnings, 0 informations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant