Skip to content

Conversation

@mfish33
Copy link

@mfish33 mfish33 commented Dec 19, 2025

This PR fixes the issue where an asymmetric attribute can not be narrowed after it has been assigned in a particular scope.

Take the following for example:

from typing import assert_type

class MyAttr:
   def __set__(self, instance: object, value: float) -> None:
      ...

   def __get__(self, instance: object, owner: type) -> int | None:
      ...

class MyClass:
   attr: MyAttr

MyClass.attr = 1.5

# Should be able to narrow types after initial assignment
assert MyClass.attr is not None
assert_type(MyClass.attr, int)

The current version of Pyright fails since it refuses to narrow MyClass.attr. The solution to this is to save the original type of MyClass.attr when an asymmetric assignment is found. This type can then be retrieved later during flow analysis.

@erictraut
Copy link
Collaborator

Pyright is working as intended here. This PR modifies its behavior in a way that is incorrect. For an explanation of the current behavior, refer to this documentation.

@erictraut erictraut closed this Dec 19, 2025
@mfish33
Copy link
Author

mfish33 commented Dec 19, 2025

This PR does not implement narrowing based on assignment. The current implementation prevents narrowing the type after assignment. This is a bug.

The following currently works fine with Pyright:

from typing import assert_type

class MyAttr:
   def __set__(self, instance: object, value: float) -> None:
      ...

   def __get__(self, instance: object, owner: type) -> int | None:
      ...

class MyClass:
   attr: MyAttr

# When the assignment is commented out the following type narrowing works as expected
# MyClass.attr = 1.5

# Should be able to narrow types after initial assignment
assert MyClass.attr is not None
assert_type(MyClass.attr, int)

@erictraut erictraut reopened this Dec 22, 2025
@erictraut
Copy link
Collaborator

I still don't consider the current behavior a bug, but I think there is an argument to be made for supporting type narrowing based on type guards for asymmetric attributes.

I'd appreciate it you'd file a feature request so we can discuss it there before posting a PR.

I've reopened the PR for now.

@mfish33
Copy link
Author

mfish33 commented Dec 22, 2025

Hi Eric, happy to open a bug. However, I am unsure if it should be considered a feature request. As I showed above, type guard narrowing is currently supported by Pyright except for when you try to do it after an assignment.

@mfish33 mfish33 force-pushed the SetAsymmetricAttributes branch from 1bd3f63 to db5f98b Compare January 5, 2026 20:11
This PR fixes the issue where an asymmetric attribute can not be narrowed after
it has been assigned in a particular scope.

Take the following for example:
```python
from typing import assert_type

class MyAttr:
   def __set__(self, instance: object, value: float) -> None:
      ...

   def __get__(self, instance: object, owner: type) -> int | None:
      ...

class MyClass:
   attr: MyAttr

MyClass.attr = 1.5

# Should be able to narrow types after initial assignment
assert MyClass.attr is not None
assert_type(MyClass.attr, int)
```

In the current version of Pyright this fails since it refuses to narrow
`MyClass.attr`. The solution to this is to save the original type of
`MyClass.attr` when an asymmetric assignment is found. This type can then be
retrieved later during flow analysis.
@mfish33 mfish33 force-pushed the SetAsymmetricAttributes branch from db5f98b to de4d07d Compare January 5, 2026 20:29
@mfish33
Copy link
Author

mfish33 commented Jan 6, 2026

Hi Eric, I am gently bumping this. Surprisingly, this is quite a typical code pattern here at Arista and is blocking wider Pyright adoption. I have even run mypy-primer on the change, and it found one instance of this issue in the wild.

mypy_primer run: mfish33#2
Issue in Colour: https://github.com/colour-science/colour/blob/develop/colour/io/tm2714.py#L1014. self.path is an asymmetric attribute that gets set in the same scope and thus can not be later narrowed.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

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

sympy (https://github.com/sympy/sympy)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:173:44 - error: Cannot access attribute "expand" for class "Basic"
+     Attribute "expand" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:528:23 - error: Operator "+" not supported for types "Generator[Unknown | int, Unknown, None] | list[Unknown | int]" and "list[Unknown | int]"
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:528:23 - error: Operator "+" not supported for types "Generator[Unknown | Literal[1], Unknown, None] | list[Unknown | Literal[1]]" and "list[Unknown | int]"
-     Operator "+" not supported for types "Generator[Unknown | int, Unknown, None]" and "list[Unknown | int]" (reportOperatorIssue)
+     Operator "+" not supported for types "Generator[Unknown | Literal[1], Unknown, None]" and "list[Unknown | int]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:313:30 - error: Cannot access attribute "as_independent" for class "Basic"
+     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:382:30 - error: Cannot access attribute "as_independent" for class "Basic"
+     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/hypergeometric.py:246:67 - error: Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:224:45 - error: Cannot access attribute "has" for class "tuple[Expr, int]"
+     Attribute "has" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:222:22 - error: No overloads for "__new__" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:222:25 - error: Argument of type "CRootOf | tuple[Expr, int]" cannot be assigned to parameter "arg" of type "Expr" in function "__new__"
-     Type "CRootOf | tuple[Expr, int]" is not assignable to type "Expr"
-       "tuple[Expr, int]" is not assignable to "Expr" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:237:40 - error: No overloads for "__new__" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:237:50 - error: Argument of type "CRootOf | tuple[Expr, int]" cannot be assigned to parameter "arg" of type "Expr" in function "__new__"
-     Type "CRootOf | tuple[Expr, int]" is not assignable to type "Expr"
-       "tuple[Expr, int]" is not assignable to "Expr" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:467:28 - error: Argument of type "Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:467:28 - error: Argument of type "Expr | Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
-     Type "Unknown | None" is not assignable to type "Expr"
+     Type "Expr | Unknown | None" is not assignable to type "Expr"
-     Attribute "pop" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:610:22 - error: Cannot access attribute "pop" for class "tuple[()]"
-     Attribute "pop" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:610:22 - error: Cannot access attribute "pop" for class "tuple[str, ...]"
-   .../projects/sympy/sympy/solvers/solveset.py:775:18 - error: Argument of type "Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "together"
+   .../projects/sympy/sympy/solvers/solveset.py:775:18 - error: Argument of type "Expr | Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "together"
-     Type "Unknown | None" is not assignable to type "Expr"
+     Type "Expr | Unknown | None" is not assignable to type "Expr"
- 37081 errors, 83 warnings, 0 informations
+ 37080 errors, 83 warnings, 0 informations

colour (https://github.com/colour-science/colour)
+ .../projects/colour/colour/io/tm2714.py
+   .../projects/colour/colour/io/tm2714.py:1014:26 - error: Unnecessary "# type: ignore" comment (reportUnnecessaryTypeIgnoreComment)
- 18 errors, 0 warnings, 0 informations
+ 19 errors, 0 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.

2 participants