-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Fix Type Narrowing for Asymmetric Attributes After Assignment #11180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
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. |
|
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) |
|
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. |
|
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. |
1bd3f63 to
db5f98b
Compare
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.
db5f98b to
de4d07d
Compare
|
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 |
|
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
|
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:
The current version of Pyright fails since it refuses to narrow
MyClass.attr. The solution to this is to save the original type ofMyClass.attrwhen an asymmetric assignment is found. This type can then be retrieved later during flow analysis.