-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Support __getitem__
type inference for subscripts
#13579
Conversation
__getitem__
type inference for subscripts
|
We're getting new errors in the benchmark due to expressions like
|
Those seem to have a type that's a union of |
Okay, I think (?) that's because in typeshed they're defined as:
|
Hmm, that's kinda odd that it's creating issues, though. Both |
Okay @charliermarsh I think this is because you're doing this: let CallOutcome::Callable { return_ty } =
dunder_getitem_method.call(self.db, &[slice_ty])
else But the result of |
I think you can fix this by making use of one of these two helper methods we have on ruff/crates/red_knot_python_semantic/src/types.rs Lines 851 to 971 in cfd5d63
|
4822d8e
to
544cad1
Compare
@carljm -- I believe all the feedback is resolved with the exception that the dunder diagnostic regressed due to the use of |
ab01f4f
to
00363b3
Compare
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.
Looks great!
00363b3
to
2b0c5d5
Compare
/// Return true if the type is a class or a union of classes. | ||
pub fn is_class(&self, db: &'db dyn Db) -> bool { | ||
match self { | ||
Type::Union(union) => union.elements(db).iter().all(|ty| ty.is_class(db)), | ||
Type::Class(_) => true, | ||
// / TODO include type[X], once we add that type | ||
_ => false, | ||
} | ||
} |
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.
I'm not sure we'll do the right thing with this for something like
flag = True
if flag:
class Foo:
def __class_getitem__(self, x: int) -> str:
pass
else:
class Foo:
pass
Foo[42]
Here we want to:
- Infer a union type for the
Foo
variable (it could be the first class definition, or the second) - Emit a diagnostic because not all items in the union support being subscripted
- Infer
str | Unknown
for the result of the subscription
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.
I think that is working as described? I added a test.
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.
Yeah, I was debating whether to bring this up earlier, or whether it's adequate to just say "not subscriptable" in this case. I agree the behavior you're describing is probably more ideal. It might require explicitly handling unions as a special case here, and calling the class-getitem resolution method per element of the union? I'd also be fine with a TODO for this so this PR can land.
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.
Are you instead thinking of something like this:
flag = True
if flag:
class Foo:
def __class_getitem__(self, x: int) -> str:
pass
else:
Foo = 1
Foo[42]
Where not all members of the union are classes?
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.
Huh, your tests are pretty convincing. Thanks for adding them!
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.
I added a test for the case I described above, which does resolve to Unknown
(with a TODO).
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.
Ahh, I think I did spot a bug but didn't accurately identify where the bug would manifest? Anyway, thanks again, a TODO for now is fine by me
2b0c5d5
to
384fdf4
Compare
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.
Thanks!
384fdf4
to
11acc07
Compare
assert_file_diagnostics( | ||
&db, | ||
"/src/a.py", | ||
&["Cannot subscript object of type 'Literal[Identity] | Literal[1]' with no `__getitem__` method."], |
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 case also highlights that perhaps our "not subscriptable" diagnostic should mention __class_getitem__
instead of __getitem__
if Type::is_class
is true? But I don't think that's critical to fix now.
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
Follow-up to #13562, to add support for "arbitrary" subscript operations.