-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[syntax-errors] Type parameter lists before Python 3.12 #16479
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
Summary -- Another simple one, just detect type parameter lists in `type` statements, functions, and classes. [pyright] only reports the `type` statement in the alias case, whereas we'll currently report both diagnostics, in combination with Test Plan -- Inline tests. [pyright]: https://pyright-play.net/?pythonVersion=3.8&strict=true&code=C4TwDgpgBAHg2gFQLpQLxQJYDthA
|
| Match, | ||
| Walrus, | ||
| ExceptStar, | ||
| TypeParams, |
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.
Should this be TypeParameterList or should we rename the message to type parameters?
It might also be helpful to start documenting the variants. E.g. by adding a link to the Python documentation https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists
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.
TypeParameterList sounds good. I'll start documenting them too, I think that's a great idea. As in #16404, I included something similar to ruff rule docs in case we want to show these to users eventually.
I think this is because the |
| // test_ok type_params_py312 | ||
| // # parse_options: {"target-version": "3.12"} | ||
| // type List[T] = list | set | ||
| // def foo[T](): ... | ||
| // class Foo[T]: ... | ||
|
|
||
| // test_err type_params_py311 | ||
| // # parse_options: {"target-version": "3.11"} | ||
| // type List[T] = list | set | ||
| // def foo[T](): ... | ||
| // class Foo[T]: ... |
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 add a case with multiple type parameters? Or, we can just mix them up by using single type parameter in function and multiple type parameters in class.
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 an example from the UP046 tests with bounds and constraints, as well as an unpacked TypeVarTuple and a ParamSpec.
We just mark the whole list at once, so there aren't additional errors for each type parameter.
I think that's fair. I can just hoist the check to where |
| // Only emit the `ParseError` for an empty parameter list instead of also including an | ||
| // `UnsupportedSyntaxError`. | ||
| if !type_params.is_empty() { |
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.
What's the reason for this condition? As a user I'd find it confusing if Ruff told me to first add the type parameters and then tell me to remove the entire list. Sorry if I missed this in my initial review if it was already present.
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 that's a good point... I was just trying to avoid duplicates, but if anything, it might make sense to suppress the ParseError in this case. Removing the condition is certainly easiest though.
And you didn't miss it, I added it here in the revision when I added the empty test cases.
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 I'd prefer keep it both. I don't think we should suppress any syntax errors.
I know this goes against what I said earlier in #16479 (comment) but my argument for that would be that it's a new statement altogether. Maybe for now we can just avoid suppressing any syntax errors based on other syntax errors and we can act on it based on user feedback? Happy to hear others thoughts on this.
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 parser does have some logic to avoid multiple errors at the same location for invalid syntax errors.
ruff/crates/ruff_python_parser/src/parser/mod.rs
Lines 424 to 438 in 37fbe58
| fn inner(errors: &mut Vec<ParseError>, error: ParseErrorType, range: TextRange) { | |
| // Avoid flagging multiple errors at the same location | |
| let is_same_location = errors | |
| .last() | |
| .is_some_and(|last| last.location.start() == range.start()); | |
| if !is_same_location { | |
| errors.push(ParseError { | |
| error, | |
| location: range, | |
| }); | |
| } | |
| } | |
| inner(&mut self.errors, error, ranged.range()); |
But I agree that we should solve this holisticly because it will otherwise be very error prone to avoid all possible errors at the same location
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.
Ah nice! I'll remove these checks for now, and then maybe we can add UnsupportedSyntaxErrors to that check or a similar one later.
| // Only emit the `ParseError` for an empty parameter list instead of also including an | ||
| // `UnsupportedSyntaxError`. | ||
| if !type_params.is_empty() { |
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.
Same as above.
| /// [type parameter list]: | ||
| /// https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists |
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.
Does this work? Shouldn't the link be after [..]: on the same line?
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.
It appeared to work in Emacs, but I can put it on one line to be safe!
| // Only emit the `ParseError` for an empty parameter list instead of also including an | ||
| // `UnsupportedSyntaxError`. | ||
| if !type_params.is_empty() { |
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 parser does have some logic to avoid multiple errors at the same location for invalid syntax errors.
ruff/crates/ruff_python_parser/src/parser/mod.rs
Lines 424 to 438 in 37fbe58
| fn inner(errors: &mut Vec<ParseError>, error: ParseErrorType, range: TextRange) { | |
| // Avoid flagging multiple errors at the same location | |
| let is_same_location = errors | |
| .last() | |
| .is_some_and(|last| last.location.start() == range.start()); | |
| if !is_same_location { | |
| errors.push(ParseError { | |
| error, | |
| location: range, | |
| }); | |
| } | |
| } | |
| inner(&mut self.errors, error, ranged.range()); |
But I agree that we should solve this holisticly because it will otherwise be very error prone to avoid all possible errors at the same location
Summary
Another simple one, just detect type parameter lists in
typestatements, functions, and classes. pyright only reports thetypestatement in the alias case, whereas we'll currently report both diagnostics, in combination with #16478.Test Plan
Inline tests.