-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Narrowing for hasattr()
#18053
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
[ty] Narrowing for hasattr()
#18053
Conversation
408f532 to
b0c84c9
Compare
|
|
The primer report looks great to me! For comparison with other type checkers:
|
081c5b3 to
5ec3b42
Compare
5ec3b42 to
e4501a8
Compare
fixing this false positive in a followup PR: #18055 |
We should update the rather than The merged version is more readable in its display, and probably more performant too. However, there's some other work I'd like to do first before doing that. If this PR is merged, I'll open an issue to remind me to followup on this later. |
carljm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome!
…eepish * origin/main: [ty] Induct into instances and subclasses when finding and applying generics (#18052) [ty] Allow classes to inherit from `type[Any]` or `type[Unknown]` (#18060) [ty] Allow a class to inherit from an intersection if the intersection contains a dynamic type and the intersection is not disjoint from `type` (#18055) [ty] Narrowing for `hasattr()` (#18053) Update reference documentation for `--python-version` (#18056) [`flake8-bugbear`] Ignore `B028` if `skip_file_prefixes` is present (#18047) [`airflow`] Apply try-catch guard to all AIR3 rules (`AIR3`) (#17887) [`pylint`] add fix safety section (`PLW3301`) (#17878) Update `--python` to accept paths to executables in virtual environments (#17954) [`pylint`] add fix safety section (`PLE4703`) (#17824) [`ruff`] Implement a recursive check for `RUF060` (#17976) [`flake8-use-pathlib`] `PTH*` suppress diagnostic for all `os.*` functions that have the `dir_fd` parameter (#17968) [`refurb`] Mark autofix as safe only for number literals in `FURB116` (#17692) [`flake8-simplify`] Fix `SIM905` autofix for `rsplit` creating a reversed list literal (#18045) Avoid initializing progress bars early (#18049)
…eep-dish * origin/main: [ty] Infer parameter specializations of generic aliases (#18021) [ty] Understand homogeneous tuple annotations (#17998) [ty] Induct into instances and subclasses when finding and applying generics (#18052) [ty] Allow classes to inherit from `type[Any]` or `type[Unknown]` (#18060) [ty] Allow a class to inherit from an intersection if the intersection contains a dynamic type and the intersection is not disjoint from `type` (#18055) [ty] Narrowing for `hasattr()` (#18053) Update reference documentation for `--python-version` (#18056) [`flake8-bugbear`] Ignore `B028` if `skip_file_prefixes` is present (#18047) [`airflow`] Apply try-catch guard to all AIR3 rules (`AIR3`) (#17887) [`pylint`] add fix safety section (`PLW3301`) (#17878) Update `--python` to accept paths to executables in virtual environments (#17954) [`pylint`] add fix safety section (`PLE4703`) (#17824) [`ruff`] Implement a recursive check for `RUF060` (#17976) [`flake8-use-pathlib`] `PTH*` suppress diagnostic for all `os.*` functions that have the `dir_fd` parameter (#17968) [`refurb`] Mark autofix as safe only for number literals in `FURB116` (#17692) [`flake8-simplify`] Fix `SIM905` autofix for `rsplit` creating a reversed list literal (#18045) Avoid initializing progress bars early (#18049)
| if hasattr(x, "spam"): | ||
| reveal_type(x) # revealed: Foo & <Protocol with members 'spam'> | ||
| reveal_type(x.spam) # revealed: object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also add a test for the negated constraint (with a TODO, if needed)? This doesn't seem to work yet?
else:
reveal_type(x) # revealed: Foo & ~<Protocol with members 'spam'>
reveal_type(x.spam) # no error?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The false negative here appears to be due to some pre-existing TODOs for intersection types. I can reproduce it using isinstance() narrowing with else branches as well as with hasattr() narrowing:
from typing import reveal_type
class Foo:
y: int
def f(x: object):
if isinstance(x, Foo):
reveal_type(x.y) # revealed: int
else:
reveal_type(x.y) # revealed: @Todo(map with boundness: intersections with negative contributions)
if hasattr(x, "foo"):
reveal_type(x.foo) # revealed: int
else:
reveal_type(x.foo) # revealed: @Todo(map with boundness: intersections with negative contributions)But you're right, I should have added a test for the else branch! I'll make a followup PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
## Summary This addresses @sharkdp's post-merge review in #18053 (comment) ## Test Plan `cargo test -p ty_python_semantic`
…h#18067) ## Summary This addresses @sharkdp's post-merge review in astral-sh#18053 (comment) ## Test Plan `cargo test -p ty_python_semantic`
Summary
Narrow the type of an object in a
hasattr()branch by intersecting with a synthesized protocol type that has a single member with typeobjectTest Plan
mdtests added