-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Avoid ever-growing default types #20991
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
614fcfd
to
e81e35d
Compare
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
We do also check whether the type of the default value is assignable to the type of the annotation (if there is an annotation), right? It looks like we still emit an error for something like this on your branch, but it might be worth adding a test for it: class A:
def __init__(self: "A"):
def f(a: int = self.f): ...
self.f = f |
Yes, correct. Type inference for the actual default value did not change. And we still perform that check. Only the type inside the generated signature changes. |
e81e35d
to
ba74e6d
Compare
Done |
Is this a too-many-iterations panic? |
Yes |
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 don't have a good sense for whether there's a better solution but i agree with the sentiment, that it's probably not worth spending too much time on.
I was worried that it could regress signature completions in the LSP but this isn't the case
I think the better solution (which David gestures at in his PR description) would be not to store the type of the parameter default as part of the function signature at all. I think our current UX here is somewhat bad: if you have a function like this in your source code: def f(x: bool = True) -> None: ... Why do we display the signature on hover as This PR doesn't regress UX, so I'm okay with it as a temporary measure. But it does also add some additional complexity to our codebase (by introducing a new |
That's not ideal either, I think. Consider: def outer(x: int):
def inner(y: int=x):
pass Do you want to see a default value of I can attempt to implement that instead of this PR. |
Not sure I understand the example -- none of the parameters have default values in this snippet? Did you mean |
yes, fixed |
I don't think this will do a great job for things like ruff/crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi Lines 2739 to 2743 in e1cada1
And what about parameter defaults that typeshed faithfully gives to us in octal, so that they will be nicely readable for IDE users?
While I don't think it ever occurs in typeshed, I think things like |
A compromise that might work quite well: if the function comes from a stub file, just grab a string slice from the source code (typeshed, for example, actually has pretty strict lint rules to stop us from using arbitrary expressions in default values; we just use |
The issue isn't just about values the user doesn't have control over. It also means that comments, or line breaks within the default value are preserved. I'm not sure if this is worth prioritizing right now. It seems there are enough details at play that are worth considering in a separate PR (and later) |
Yes, it does seem like pyright/pylance is doing something quite sophisticated here. For this function: def f(x=[
1,
2,
# some comment,
4
]): ... Pyright/pylance gives this signature when I hover over it: But pyright/pylance doesn't do a great job for the |
Seeing this discussion, I feel like this change should be done separately, and probably at a later time. I'd love to work on this, but it's honestly not high priority. And it seems like there will be some things to decide. So I'm going to merge this (because solving that panic is a high priority), even if I fully agree with Alex's comment above. It looks to me like it'll be easy to rip this out once we switch to the value-based representation. I'll write down a todo task for me and/or create a ticket. |
Thank you! |
Summary
We currently panic in the seemingly rare case where the type of a default value of a parameter depends on the callable itself:
Types of default values are only used for display reasons, and it's unclear if we even want to track them (or if we should rather track the actual value). So it didn't seem to me that we should spend a lot of effort (and runtime) trying to achieve a theoretically correct type here (which would be infinite).
Instead, we simply replace nested default types with
Unknown
, i.e. only if the type of the default value is a callable itself.closes astral-sh/ty#1402
Test Plan
Regression tests