-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[ty] fix and simplify callable type materializations #22213
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
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-assignment |
1 | 0 | 16 |
invalid-argument-type |
2 | 3 | 10 |
invalid-return-type |
1 | 1 | 9 |
unresolved-attribute |
0 | 0 | 8 |
unused-ignore-comment |
1 | 0 | 0 |
| Total | 5 | 4 | 43 |
But the top materialization of a I think this is an argument in favour of our current display, actually. It means you don't have to think about contravariance. |
I think it's arguable and a bit arbitrary whether you consider the entire |
282b9b5 to
cb68307
Compare
|
The ecosystem-analyzer workflow reruns automatically on new pushes to a labelled PR following 19b1099; there's no need to remove and re-add the label anymore to trigger a rerun :-) |
|
@AlexWaygood I don't feel strongly about the display -- I think it's a bit odd (and more visually confusing) to wrap extra stuff in |
crates/ty_python_semantic/resources/mdtest/type_properties/materialization.md
Outdated
Show resolved
Hide resolved
I do still prefer the current display, I think. I totally see your point that the top materialisation of the return type can always be (and is always!) fully simplified, so it doesn't "need" to be wrapped in I also find the situation with the parentheses in the current display to be a little confusing. |
AlexWaygood
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.
Great catch with the overloads issue!
| parameters: if signature.parameters().is_top() { | ||
| signature.parameters().clone() | ||
| } else { | ||
| Parameters::new( |
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 worry that this PR makes the Parameters::new() method a bit of a footgun. You have to remember never to use it when you're constructing a new Parameters from an existing Parameters, because the existing Parameters might be a top materialisation. Iterating over the existing parameters and passing them to Parameters::new() will discard that information — so maybe Parameters::new() should take an additional parameters_kind parameter, to force us to consider that explicitly every time we construct a new Parameters?
It looks like this issue might already exist for gradual Parameters, though.
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 problem doesn't exist for Gradual, because gradual-parameters can be (and is) automatically determined from the parameters (if they are (*Any, **Any), that's the gradual parameters).
This also makes it awkward to add a kind parameter here, since it would be redundant information in every case except Top.
We could add an is_top parameter, but that is also awkward, because if it's true the provided parameters iterator would be ignored.
I think the most natural API is to provide the existing new(), bottom(), and top() constructors, with their current signatures. Though we could rename new() to from_parameters() to make it seem a bit less general?
I don't see a great solution to avoid the potential for mistakes here -- it's just the case that a Parameters is no longer fully determined by its list of parameters, there's no way around us having to understand that. I don't think that "constructing a parameters from an existing parameters" is going to be a super common task.
Open to suggestions here.
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.
Will go ahead and merge this as-is, open to follow-up.
|
I restored the old display of top callables. I also noticed that we were needlessly parenthesizing overloaded callables (they are wrapped in |
## Summary I only noticed this in the ecosystem report of #22213 after merging it. The change to displaying `Top[]` wrapper around the entire signature instead of just the parameters had the side effect of not showing it at all when displaying a top ParamSpec specialization. This PR fixes that. Marking internal since this is a fixup of a not-released PR. ## Test Plan Added mdtest that fails without this PR.
Summary
A couple things I noticed when taking another look at the callable type materializations.
Never.Really, "top parameters" is something that belongs on the
Parameters, not on the entireCallableType. Conveniently, we already haveParametersKindwhere we can track this, right next to where we already trackParametersKind::Gradual. This saves a bit of memory, fixes the two bugs above, and simplifies the implementation considerably (net removal of 100+ LOC, a bunch of places that shouldn't need to care about topness of a callable no longer need to.)One user-visible change from this is that I now display the "top callable" as
(Top[...]) -> objectinstead ofTop[(...) -> object]. I think this is a (minor) improvement, because it wraps exactly the part inTopthat needs to be, rather than misleadingly wrapping the entire callable type, including the return type (which has already been separately materialized). I think the prior display would be particularly confusing if the return type also has its ownTopin it: previously we could have e.g.Top[(...) -> Top[list[Unknown]]], which I think is less clear than the new(Top[...]) -> Top[list[Unknown]].Test Plan
Added mdtests that failed before this PR and pass after it.
Ecosystem
The changed diagnostics are all either the change to
Topdisplay, or else known non-deterministic output. The added diagnostics are all true positives:The added diagnostic at https://github.com/pytorch/vision/blob/aa35ca1965bea39b9a0996d5d2d7f15d325e54d2/torchvision/transforms/v2/_utils.py#L149 is a true positive that wasn't caught by the previous version.
stris not assignable toCallable[[Any], Any](strings are not callable), nor is the top callable (top callable includes callables that do not take a single required positional argument.)The added diagnostic at https://github.com/Kludex/starlette/blob/081535ad9b46a29ca4d9beea9dea9d422b4c8f7d/starlette/routing.py#L67 is also a (pedantic) true positive. It's the same case as #1567 -- the code assumes that it is impossible for a subclass of
Responseto implement__await__(yielding something other than aResponse).The pytest added diagnostics are also both similar true positives: they make the assumption that an object cannot simultaneously be a
Sequenceand callable, or anIterableand callable.