-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Improve diagnostics for assert_type and assert_never
#18050
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
|
|
Thanks for looking into this. I think I'd prefer if we don't repeat the inferred type in the sub diagnostic message and diagnostic. I'm leaning towards only having it in the annotation but I'm not sure if that's possible. The other issue that I see is that the actual type is now omitted when using |
|
Hmm, your feedback points contradict each other @MichaReiser 😆
I agree the duplication seems unfortunate, but I was worried that doing what you suggest here would make the diagnostic summary (which is what is displayed for the concise output format) pretty useless. I do think we should care about what
Yeah, I agree it's not ideal (see above). We do already have this issue for lots of other diagnostic, as you say, though. |
I don't think it changes the concise output because the concise output only displays information from the primary diagnostic, not from the secondary diagnostic (as you can see in the mdtests)
We can improve this. Again, the actual type isn't part of the primary message. That's why I think we shouldn't repeat the actual type in the sub-diagnostic either by removing it from the annotation or from the message (I'd prefer that) |
|
@MichaReiser what would you think about this change (relative to my PR): Diff--- a/crates/ty_python_semantic/src/types/infer.rs
+++ b/crates/ty_python_semantic/src/types/infer.rs
@@ -4814,14 +4814,8 @@ impl<'db> TypeInferenceBuilder<'db> {
asserted_ty.display(self.db())
));
- let mut subdiagnostic = SubDiagnostic::new(
- Severity::Info,
- format_args!(
- "`{}` is not an equivalent type to `{}`",
- actual_ty.display(self.db()),
- asserted_ty.display(self.db())
- ),
- );
+ let mut subdiagnostic =
+ SubDiagnostic::new(Severity::Info, "");
subdiagnostic.annotate(
Annotation::primary(self.context.span(
&call_expression.arguments.args[0],
@@ -4848,13 +4842,8 @@ impl<'db> TypeInferenceBuilder<'db> {
"Argument does not have expected type `Never`",
);
- let mut subdiagnostic = SubDiagnostic::new(
- Severity::Info,
- format_args!(
- "`{}` is not an equivalent type to `Never`",
- actual_ty.display(self.db()),
- ),
- );
+ let mut subdiagnostic =
+ SubDiagnostic::new(Severity::Info, "");
subdiagnostic.annotate(
Annotation::primary(self.context.span(
&call_expression.arguments.args[0],which would render like this? |
|
To make sure I understand the screenshot: Does the first diagnostic show the old format? Now that I'm seeing it, it sort of feels odd that the sub diagnostic has no message. Maybe it is better to remove the annotation message? Do you have @BurntSushi any recommendation here? |
no, both diagnostics in the new screenshot are what would be shown with the changes to my PR that I proposed in that diff. One diagnostic is for |
|
Hmm okay. I'm a bit confused because it still repeats the actual (and even expected) type for I think I'd go with:
But curious to hear from Andrew. |
|
@MichaReiser I pushed some updates which I think (at least partially) might address your comments? WDYT? I've edited the new screenshots into the PR description |
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.
(none of the changes in this file are substantive. Just some driveby simplifications.)
|
Thanks |
MichaReiser
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.
Thanks
BurntSushi
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.
I left a suggestion that I think might help here.
As for the concise message format, I think it will prove very challenging to use one set of messages that balances both the concise and the verbose formats. They have fundamentally different goals and present information very different, and I worry that by trying to balance the two, we will wind up in a situation where both outputs are bad. Instead, I'd rather accept that the concise output is less helpful than it could be.
| 8 | assert_never("") # error: [type-assertion-failure] | ||
| 9 | assert_never(None) # error: [type-assertion-failure] | ||
| | | ||
| info: rule `type-assertion-failure` is enabled by default |
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 this output is not ideal. In particular, it repeats the entire code snippet here. I'd suggest just this:
error[type-assertion-failure]: Argument does not have expected type `Never`
--> src/mdtest_snippet.py:7:5
|
5 | assert_never(never) # fine
6 |
7 | assert_never(0) # error: [type-assertion-failure]
| ^ Inferred type is `Literal[0]`
8 | assert_never("") # error: [type-assertion-failure]
9 | assert_never(None) # error: [type-assertion-failure]
|
I think this has better information density, has less duplication and contains all relevant context.
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.
Yes, I agree that there's some unfortunate duplication here. I'd love to only have a single annotation that only highlights the arguments passed in rather than the whole call.
Did you see the concern I mentioned in my PR summary about how diagnostic ranges interact with suppression comments? That's why I held off from doing that in this PR. If the range of the diagnostic is only the range of the arguments passed into the call, rather than the whole call, it's going to mean that comments like this will not suppress the diagnostic:
assert_type( # type: ignore
very_very_very_long_variable_name,
very_very_very_long_type,
)I think it's especially a concern for assert_type, which requires two arguments rather than one (so the call is more likely to be multiline).
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.
Oh I see, I did miss that. In particular:
I feel like in general we might want to separate the concepts of "range the diagnostic has for suppression purposes" and "primary range that is highlighted when the diagnostic is rendered on the terminal"? But that's out of scope for this PR.
I would tentatively agree with that... But there's definitely downsides with that because it makes creating a diagnostic somewhat confusing.
What I'd suggest instead is to have two annotations on the top-level diagnostic. The primary annotation can be the range you want for suppression comments to work well. Then a secondary annotation for the specific argument that is invalid. IDK exactly how it would render, but something like this:
error[type-assertion-failure]: Argument does not have expected type `Never`
--> src/mdtest_snippet.py:7:5
|
5 | assert_never(never) # fine
6 |
7 | assert_never(0) # error: [type-assertion-failure]
| ^^^^^^^^^^^^^^^
| |
| |
| ------^ Inferred type is `Literal[0]`
8 | assert_never("") # error: [type-assertion-failure]
9 | assert_never(None) # error: [type-assertion-failure]
|
In this case, I left the message on the primary annotation blank. I'm not sure if that's the right call or not, but it seems reasonable.
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.
BurntSushi
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.
Yeah I agree the rendering is not ideal here. But I think it's probably the least bad option available to us at the moment.
eda8ae2 to
9585434
Compare
…eep-dish * origin/main: [ty] __file__ is always a string inside a Python module (#18071) [ty] Recognize submodules in self-referential imports (#18005) [ty] Add a note to the diagnostic if a new builtin is used on an old Python version (#18068) [ty] Add tests for `else` branches of `hasattr()` narrowing (#18067) [ty] Improve diagnostics for `assert_type` and `assert_never` (#18050) [ty] contribution guide (#18061) [ty] Implement `DataClassInstance` protocol for dataclasses. (#18018) [ruff_python_ast] Fix redundant visitation of test expressions in elif clause statements (#18064)



Summary
Further work towards astral-sh/ty#209.
I didn't make exactly the change suggested in that issue (only highlight the argument range) because I was worried that the behaviour of
type: ignorecomments might be confusing if the range of the diagnostic only covered part of the call. E.g. I'd expect this to work, as a user:I feel like in general we might want to separate the concepts of "range the diagnostic has for suppression purposes" and "primary range that is highlighted when the diagnostic is rendered on the terminal"? But that's out of scope for this PR.
Instead, this PR adds a subdiagnostic that draws the user's attention to the range of the argument passed in.
Test Plan
Before:
After:
(I also added snapshots)