Fix #11453: no error on accessing __qualname__ of instance#11503
Fix #11453: no error on accessing __qualname__ of instance#11503rchiodo wants to merge 3 commits into
Conversation
The binder injects an implicit __qualname__ symbol into every class scope so it can be referenced by name within the class body (e.g. print(__qualname__)). Because the scope is a class scope, the symbol was flagged as a class member, so member access on an instance resolved it and skipped the expected attribute-access error. Unlike __doc__/__module__, __qualname__ is exposed via the metaclass (type), not as a class/instance attribute. Thread an optional isClassMember flag through the implicit-symbol helpers and inject __qualname__ without the ClassMember flag. It stays name-resolvable in the class body, instance access now reports an attribute-access error, and class-object access still resolves via type.__qualname__: str. Fixes microsoft#11453 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
|
It looks to me like this has introduced a whole number of failures along the lines of: dd-trace-py (https://github.com/DataDog/dd-trace-py)
pydantic (https://github.com/pydantic/pydantic)
|
Inject the implicit __qualname__ symbol after walking the class suite so a class that explicitly declares __qualname__ (e.g. type, function, and other typeshed classes) keeps the class-member symbol created for that declaration. Previously the non-class-member symbol was created up front and the explicit declaration merged into it without restoring the class-member flag, which hid the legitimate member and caused reportAttributeAccessIssue errors on valid instance.__qualname__ accesses (MethodType, function, etc.). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Good catch — fixed in 73f5461. Root cause: the implicit qualname symbol was injected into the class scope before the class suite was walked. For typeshed classes that legitimately declare qualname ( Fix: inject the implicit Added regression coverage to |
|
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/tests/test_discrete_rv.py:41:12 - error: Operator "-" not supported for types "Unknown | Expr | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | Literal[0]" and "Unknown | Expr | MatrixExpr | One | NegativeOne | Zero | Integer | int"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:41:12 - error: Operator "-" not supported for types "Unknown | Expr | tuple[Unknown, ...] | Sum | ZeroMatrix | Zero | NaN | Piecewise | Basic | Literal[0]" and "Unknown | Expr | MatrixExpr | One | NegativeOne | Zero | Integer | int"
- .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:41:37 - error: Operator "**" not supported for types "Unknown | Expr | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | Literal[0]" and "Literal[2]"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:41:37 - error: Operator "**" not supported for types "Unknown | Expr | tuple[Unknown, ...] | Sum | ZeroMatrix | Zero | NaN | Piecewise | Basic | Literal[0]" and "Literal[2]"
- .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:85:12 - error: Operator "-" not supported for types "Expr | Unknown | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | Literal[0]" and "Expr | Unknown | MatrixExpr | One | NegativeOne | Zero | Integer | int"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:85:12 - error: Operator "-" not supported for types "Expr | Unknown | tuple[Unknown, ...] | Sum | ZeroMatrix | Zero | NaN | Piecewise | Basic | Literal[0]" and "Expr | Unknown | MatrixExpr | One | NegativeOne | Zero | Integer | int"
- .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:85:37 - error: Operator "**" not supported for types "Expr | Unknown | tuple[Unknown, ...] | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Basic | Literal[0]" and "Literal[2]"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:85:37 - error: Operator "**" not supported for types "Expr | Unknown | tuple[Unknown, ...] | Sum | ZeroMatrix | Zero | NaN | Piecewise | Basic | Literal[0]" and "Literal[2]"
- .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:138:33 - error: Operator "+" not supported for types "Expr | Basic | Expectation | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" and "Expr | Unknown | tuple[Unknown, ...] | MatMul | One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Any | int"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:138:33 - error: Operator "+" not supported for types "Expr | Basic | Expectation | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" and "Expr | Unknown | tuple[Unknown, ...] | MatMul | One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Any | int"
- .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:138:33 - error: Operator "+" not supported for types "Expr | Unknown | Any | One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | tuple[Unknown, ...] | MatAdd | int" and "Literal[3]"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:138:33 - error: Operator "+" not supported for types "Expr | Unknown | Any | One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | tuple[Unknown, ...] | MatAdd | Infinity | NegativeInfinity | Float | Number | int" and "Literal[3]"
- .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:138:43 - error: Operator "*" not supported for types "Literal[2]" and "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:138:43 - error: Operator "*" not supported for types "Literal[2]" and "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]"
- .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:164:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
+ .../projects/sympy/sympy/stats/tests/test_discrete_rv.py:164:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
- Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
+ Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
... (truncated 329 lines) ...
streamlit (https://github.com/streamlit/streamlit)
+ .../projects/streamlit/lib/streamlit/type_util.py:131:56 - error: Cannot access attribute "__qualname__" for class "object"
+ Attribute "__qualname__" is unknown (reportAttributeAccessIssue)
+ .../projects/streamlit/lib/streamlit/type_util.py:132:20 - error: Cannot access attribute "__qualname__" for class "object"
+ Attribute "__qualname__" is unknown (reportAttributeAccessIssue)
- 5595 errors, 111 warnings, 0 informations
+ 5597 errors, 111 warnings, 0 informations
dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ .../projects/dd-trace-py/ddtrace/contrib/internal/asyncio/patch.py:73:32 - error: "__qualname__" is not a known attribute of "None" (reportOptionalMemberAccess)
- 14915 errors, 754 warnings, 0 informations
+ 14916 errors, 754 warnings, 0 informations
trio (https://github.com/python-trio/trio)
- .../projects/trio/src/trio/_core/_run.py:1974:68 - error: Unnecessary "# type: ignore" comment (reportUnnecessaryTypeIgnoreComment)
+ .../projects/trio/src/trio/_deprecate.py:51:38 - error: Type of "__qualname__" is unknown (reportUnknownMemberType)
+ .../projects/trio/src/trio/_deprecate.py:51:44 - error: Cannot access attribute "__qualname__" for class "object"
+ Attribute "__qualname__" is unknown (reportAttributeAccessIssue)
+ .../projects/trio/src/trio/_util.py:221:25 - error: Cannot assign to attribute "__qualname__" for class "object"
+ Attribute "__qualname__" is unknown (reportAttributeAccessIssue)
- 1686 errors, 13 warnings, 0 informations
+ 1688 errors, 13 warnings, 0 informations
spack (https://github.com/spack/spack)
+ .../projects/spack/lib/spack/spack/build_environment.py:1517:37 - error: Cannot access attribute "__qualname__" for class "str"
+ Attribute "__qualname__" is unknown (reportAttributeAccessIssue)
- 2146 errors, 26 warnings, 0 informations
+ 2147 errors, 26 warnings, 0 informations
jax (https://github.com/google/jax)
+ .../projects/jax/jax/_src/api.py:1972:51 - error: Cannot access attribute "__qualname__" for class "partial[Unknown]"
+ Attribute "__qualname__" is unknown (reportAttributeAccessIssue)
+ .../projects/jax/jax/_src/util.py:537:11 - error: Cannot assign to attribute "__qualname__" for class "object*"
+ Attribute "__qualname__" is unknown (reportAttributeAccessIssue)
- 3403 errors, 90 warnings, 0 informations
+ 3405 errors, 90 warnings, 0 informations
|
|
Follow-up on the mypy_primer
Mechanism of the fix (73f5461): the implicit |
| # This should not generate an error because the attribute is explicitly declared | ||
| # as a class member. | ||
| WithQualname().__qualname__ | ||
| reveal_type(WithQualname().__qualname__, expected_text="str") |
There was a problem hiding this comment.
The WithQualname block asserts WithQualname().__qualname__ is valid str, but at runtime type.__new__ strips a class-body __qualname__ assignment from __dict__, so WithQualname().__qualname__ raises AttributeError. This is consistent with pyright's general modeling of class-body assignments as instance-accessible class variables, so changing the diagnostic may be out of scope — but the inline comment "exposes it on instances" is factually inaccurate and slightly contradicts this PR's stated principle. Please reword the comment to note the runtime would AttributeError (and that this reflects pyright's class-variable modeling), so the test doesn't read as enshrining runtime behavior it doesn't match.
| // and the explicit declaration would merge into it without restoring the | ||
| // class-member flag, incorrectly hiding the legitimate member. | ||
| this._addImplicitSymbolToCurrentScope('__qualname__', node, 'str', /* isClassMember */ false); | ||
|
|
There was a problem hiding this comment.
Moving the __qualname__ injection to after this.walk(node.d.suite) means that for symbols with a real declaration (typeshed function/type, or a user __qualname__ = "..."), the synthetic empty-range Intrinsic declaration is now appended last via addDeclaration. Consumers that select the "last" declaration for hover / go-to-definition could now resolve to the empty range instead of the real declaration. reveal_type still yields str, so existing tests won't catch this. Worth verifying "Go to Definition" on func1.__qualname__ lands on the real declaration in a post-patch build.
| Worktree: `C:\Users\rchiodo\.fix_loop\repos\microsoft__pyright\.worktrees_8765\fix11453` | ||
|
|
||
| ## Context | ||
|
|
There was a problem hiding this comment.
Repo/verb mismatch: the plan and pyright-internal test placement treat this as microsoft/pyright issue #11453 (matching the "Fixes" verb), but the PR footer points at microsoft/pylance-release. Per repo convention, pylance-release issues use "Addresses" and only pyrx issues use "Fixes". Please reconcile the repo and verb before merge.
|
Solid, surgical fix for instance |
heejaechang
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Description
Pyright/Pylance failed to report the expected "cannot access attribute" diagnostic when
__qualname__was accessed on an instance (e.g.MyClass().__qualname__). At runtime,__qualname__is exposed via the metaclasstype, not as an instance attribute, so instance access raisesAttributeError. Class-object access (MyClass.__qualname__) is and should remain valid.How you figured out what to do
The existing sample
classes3.pyalready documented this exact bug at theinstance.__qualname__line. Reproduced it (no diagnostic emitted), then traced the root cause: the binder unconditionally injects an implicit__qualname__symbol into each class scope's symbol table (binder.ts~L510). In a Class scope,_addSymbolToCurrentScopestamps it withSymbolFlags.ClassMember. Instance member-access resolution (getClassMemberIterator) treats anyisClassMember()symbol as accessible, so the error was suppressed. Confirmed via debugger that the symbol hadisClassMember()=true/isInstanceMember()=false, and via CPython thatA().__qualname__raisesAttributeError.Implementation
Threaded an optional
isClassMemberparameter (defaulttrue) through_addImplicitSymbolToCurrentScope→_addSymbolToCurrentScope. TheClassMemberflag is now only set when the scope is a Class andisClassMemberis true. The__qualname__injection passes/* isClassMember */ false, so it stays name-resolvable inside the class body (print(__qualname__)) but is no longer exposed as a class/instance member.Class.__qualname__still resolves via the metaclasstype.__qualname__: str.__doc__/__module__are unchanged (they are legitimately instance-accessible).Testing
classes3.py:instance.__qualname__now expected error; addedreveal_type(..., expected_text="str")guards for class-object access; added subclass, nested/inner class, andtype[_T]generic cases. BumpedClasses3expected error count 3 → 6.completions.qualname.fourslash.ts: instance completions exclude__qualname__(but include__doc__/__module__); class completions include__qualname__.typeEvaluator1-8+checker(9 suites / 1234 tests) pass; fourslash completions/hover (333 tests) pass.Addresses
Fixes https://github.com/microsoft/pylance-release/issues/11453
Generated by fix_all_my_issues pipeline