-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Emit error for pattern arguments in trait methods #53051
Emit error for pattern arguments in trait methods #53051
Conversation
288699e
to
934bca5
Compare
934bca5
to
023a714
Compare
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
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 you add a suggestion to replace the tuple with an underscore, or a name which is the underscore concatenate of the tuple idents?
Great improvements I'm just worried about interactions with bad code being edited (like fn foo(u8 x: usize
).
src/libsyntax/parse/parser.rs
Outdated
err.cancel(); | ||
// Recover from attempting to parse the argument as a pattern. This means | ||
// the type is alone, with no name, e.g. `fn foo(u32)`. | ||
mem::replace(self, parser_snapshot_before_pat); |
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 this be checked without rewinding state?proactively looking if a path could be parsed? I'm not against the rewind method in exceptional cases, but it should be a tool of last resort.
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 had also wanted to avoid this, but I wasn't quite sure how to go about it. The problem I had was that parse_pat
and parse_ty
can succeed on the same strings. Ideally we'd parse_pat
(which should be a superset of parse_ty
), try to parse a :
and then convert the pattern to a type if it failed, because we effectively have all the information we need at that stage. Is there any way to reinterpret a pattern as a type, or similar? (Or perhaps there's a simpler method I'm overlooking.)
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 away from a computer now, but I'd think you could inspect the successfully parsed Pat to see if it is a single typed argument.
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.
That seems plausible. I'll give it a go soon.
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.
Rewinding is rare, so it's not very useful to optimize for that, but doing the state saving for every single fn parameter in traits doesn't look good.
I think doing minimal lookahead and avoiding state saving for the common case ident: Type
would be enough.
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.
Okay, I'll try that.
023a714
to
d35252f
Compare
src/libsyntax/parse/parser.rs
Outdated
pat, | ||
id: ast::DUMMY_NODE_ID, | ||
}) | ||
let is_named_argument = self.is_named_argument(); |
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.
Could you move this line into the Err(mut err)
clause where it's needed?
(In the Ok(..)
case it return nonsensical result anyway.)
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, not sure why I left it out there...
let mut err = struct_span_err!(self.session, span, E0642, | ||
"patterns aren't allowed in methods without bodies"); | ||
err.span_suggestion(span, | ||
"use an underscore to ignore the name", "_".to_owned()); |
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 point of this recommendation?
If pattern doesn't work, then it's much more likely that the user will want some name describing the pattern as a whole, rather than no name at all.
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 was a response to #53051 (review). This suggestion only appears somewhere where the name is irrelevant and can't be used anywhere else anyway, so a name is unnecessary. I considered converting the pattern somehow, but I couldn't find an existing method to do something similar, and it seemed like more effort than was worthwhile.
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.
Synthesizing a meaningful name from code like fn foo((x, y): (i32, i32));
can be impossible to get right (should it suggest x_y
? x_and_y
? something else?), which is why I suggested using _
, but the wording could be changed slightly to give this argument a name or use an underscore to ignore it, instead of a tuple pattern
.
Wait a second, this PR has language implications as well. Arbitrary patterns are now accepted in trait methods with body, a feature that we can't provide without backtracking in parser. trait Tr {
fn f((a, b): (u8, u8)) {} // OK
} This PR needs to enforce the restrictions that previously existed on all trait methods, with or without body. |
Oops, is that the case? I didn't realise it would have that implication. There should probably be a test for that. I'll fix that and add 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.
Couple of smaller nitpicks, otherwise lgtm. Leaving @petrochenkov for final r.
src/libsyntax/parse/parser.rs
Outdated
// If we see `ident :`, then we know that the argument is just of the | ||
// form `type`, which means we won't need to recover from parsing a | ||
// pattern and so we don't need to store a parser snapshot. | ||
let parser_snapshot_before_pat = if |
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.
Creative :)
src/librustc_passes/diagnostics.rs
Outdated
// in methods without bodies | ||
} | ||
``` | ||
"##, |
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.
Last nitpick, could you expand the documentation to show the "fixed" code, using maybe x_and_y
as argument name?
src/test/ui/E0642.stderr
Outdated
| | ||
LL | fn bar((x, y): (i32, i32)) {} //~ ERROR patterns aren't allowed in trait methods | ||
| ^^^^^^ | ||
help: give this argument a name or use an underscore to ignore it, instead of a tuple pattern |
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.
reword to "give this argument a name or use an underscore to ignore it instead of using a tuple pattern"
}); | ||
} | ||
} else { | ||
let mut err = struct_span_err!(self.session, span, E0642, |
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.
Ok, now this is a breaking change 😄
Some limited set of pattern (defined by fn is_named_argument
) was previously accepted by parser in methods with body.
Something like
trait Tr {
fn f(&ident: Type) {} // OK
fn g(&&ident: Type) {} // OK
fn h(mut ident: Type) {} // OK
fn k($pat: Type) {} // OK
}
Note, that this compatibility check needs to happen in parser due to $pat
that can look like an arbitrary complex pattern in AST validation, but still needs to be accepted.
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 no! I thought I accounted for that with the branches, but it turns out not! (Why did it even pass? Do we not have any tests for that?)
So will I need to change parser.rs
to fix this, or can I just move the check for the body?
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 think AST validation needs any changes (it already does things right), all the relevant stuff is in the parser.
It would probably be better to:
- Check for
self.is_named_argument()
and parse in the old way if it's true. - Otherwise, parse
PAT: TYPE
with state saving and immediately report a non-fatal error in case of success. - Otherwise, backtrack and parse
TYPE
.
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.
If AST validation reports an unwanted duplicate error, then patterns could be converted into _
in the parser.
"patterns aren't allowed in trait methods"); | ||
let suggestion = "give this argument a name or use an \ | ||
underscore to ignore it instead of using a \ | ||
tuple pattern"; |
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 mention of tuple pattern is overly specific, I'd remove the "instead" part, or used "instead of using a pattern"
3d6eba1
to
96b34c1
Compare
Okay, the errors are now emitted during parsing instead, as per @petrochenkov's suggestion in #53051 (comment). |
96b34c1
to
afe415d
Compare
src/libsyntax/parse/parser.rs
Outdated
// pattern and so we don't need to store a parser snapshot. | ||
let parser_snapshot_before_pat = if | ||
self.look_ahead(1, |t| t.is_ident()) && | ||
self.look_ahead(2, |t| t == &token::Colon) { |
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 would still store a copy of the parser for fn foo(&mut x) {}
and fn bar(&self)
, right?
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.
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.
self
is methods is treated separately outside of parse_arg_general
, so it's not affected.
This comment has been minimized.
This comment has been minimized.
@@ -342,10 +342,10 @@ impl<'a> Visitor<'a> for AstValidator<'a> { | |||
self.session.buffer_lint( | |||
lint::builtin::PATTERNS_IN_FNS_WITHOUT_BODY, | |||
trait_item.id, span, | |||
"patterns aren't allowed in methods without bodies"); | |||
"patterns aren't allowed in trait methods"); |
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.
Patterns are allowed in trait methods with bodies, the old message was correct.
Could you add examples from #53051 (comment) as a test for catching future regressions? |
src/libsyntax/parse/parser.rs
Outdated
}); | ||
(pat, ty) | ||
|
||
// If we see `ident :`, then we know that the argument is not just of the |
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.
ident :
no longer needs to be treated specially because it falls under self.is_named_argument()
above.
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.
In other words, we are already in cold code here and can make the snapshot unconditionally.
LGTM, beside the few remaining comments above. |
The error and check for this already existed, but the parser didn't try to parse trait method arguments as patterns, so the error was never emitted. This surfaces the error, so we get better errors than simple parse errors.
bb12fb0
to
5c814e2
Compare
@bors r+ |
📌 Commit 5c814e2 has been approved by |
🌲 The tree is currently closed for pull requests below priority 10, this pull request will be tested once the tree is reopened |
…ts-error, r=petrochenkov Emit error for pattern arguments in trait methods The error and check for this already existed, but the parser didn't try to parse trait method arguments as patterns, so the error was never emitted. This surfaces the error, so we get better errors than simple parse errors. This improves the error message described in rust-lang#53046.
…=petrochenkov Emit error for pattern arguments in trait methods The error and check for this already existed, but the parser didn't try to parse trait method arguments as patterns, so the error was never emitted. This surfaces the error, so we get better errors than simple parse errors. This improves the error message described in #53046. r? @petrochenkov
☀️ Test successful - status-appveyor, status-travis |
The error and check for this already existed, but the parser didn't try to parse trait method arguments as patterns, so the error was never emitted. This surfaces the error, so we get better errors than simple parse errors.
This improves the error message described in #53046.
r? @petrochenkov