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.

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.
@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.

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