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
Recover from if (let ...)
in parsing, instead of lowering
#83407
Conversation
r? @davidtwco (rust-highfive has picked a reviewer for you, use r? to override) |
Looks like there's an intersection with #80357 here. |
@petrochenkov do you see any reason not to go ahead with this change (beyond the potential merge conflicts inflicted on the other PR, given that it is already in conflict with master)? |
After a more detailed look, this PR goes exactly 180 degrees away from the right direction. #80357 on the other hand does the right thing - keeps the feature gate while moving the implementation of |
Presenting both errors is pretty terrible user experience. Complaining about the feature gate in a case that is an easy typo to make (surrounding parens around the let expression) can be incredibly frustrating/confusing to a newcomer.
I looked at the other PR and it produces no changes in output for src/test/ui/rfc-2497-if-let-chains/feature-gate.rs, so they seem to be somewhat independent of each other in their effects. If we want to unconditionally remove the error for the specific case of |
let cond = self.parse_cond_expr()?; | ||
let cond = self.parse_cond_expr()?.into_inner(); | ||
|
||
let cond = if let ExprKind::Paren(paren) = &cond.kind { |
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 should only be done if the gate is disabled, otherwise there's no way to accept this syntax ever, which is valid when let chains are enabled.
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.
Hm.. in the current version, we lower if (let ...)
to if let
unconditionally, so that's what I did here as well.
This should only be done if the gate is disabled
Which gate do you mean?
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.
let_chains
. I think you can access it with self.sess.features_untracked().or_patterns
. If that expr is true, then we shouldn't emit this error. I think this is a bug introduced by me when I added the targeted error.
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 we do this, then parsing of if (let ...)
will be different depending on whether let_chains
is enabled or not, right? Should feature gates be able to change parsing? I'd expect not.
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 feature gates would change what the resulting AST is interpreted as. if (let pat = expr)
is valid when the feature gate is enabled. This is why you get the error we're trying to silence. All of this is appropriate behavior, but it makes for a bad experience. Because of this ideally the feature gate error would suggest removing the parens for this case, but that would mean ferrying more information into the linting machinery so that it can know that it was this case. I find it easier to treat it more locally.
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 it's not possible to check for gates in parser. The data is not available in ParseSess
.
I think for this information to be available when parsing you would need to first parse the crate root for the attributes, then parse other files. Perhaps the parser is not implemented this way currently? (though I just saw the Edition
field, which can be updated with an attribute at the crate root..)
I think you misunderstand what this PR is doing. When the user writes This PR maintains the same assumption and does not add any new assumptions. Only difference is we no longer generate the invalid feature gate error when user types Does this make sense now? |
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.
After resolving @estebank's comments, this seems fine to me.
r? @estebank |
That is a bug though I introduced. We don't just assume it meant |
When we recover from `if (let ...)` in lowering we can't "ungate" the syntax after generating the error invalid parentheses around `let` expression in `if let` as the feature_gate::check_crate pass has already run. This is mentioned in these comments in the old code > Ideally, we'd remove the feature gating of a `let` expression since we > are already complaining about it here, but `feature_gate::check_crate` > has already run by now We now recover from `if (let ...)` in parsing, generate the error as before, and "ungate" the syntax. For example, in this code: fn main() { let x = Some(3); if (let Some(y) = x) {} } We would previously generate: error[E0658]: `let` expressions in this position are experimental --> test.rs:3:9 | 3 | if (let Some(y) = x) {} | ^^^^^^^^^^^^^^^ | = note: see issue rust-lang#53667 <rust-lang#53667> for more information = help: add `#![feature(let_chains)]` to the crate attributes to enable = help: you can write `matches!(<expr>, <pattern>)` instead of `let <pattern> = <expr>` error: invalid parentheses around `let` expression in `if let` --> test.rs:3:8 | 3 | if (let Some(y) = x) {} | ^ ^ | help: `if let` needs to be written without parentheses | 3 | if let Some(y) = x {} | -- -- error: aborting due to 2 previous errors The first error doesn't make sense. With the "ungating" we now generate just the second one: error: invalid parentheses around `let` expression in `if let` --> test.rs:3:8 | 3 | if (let Some(y) = x) {} | ^ ^ | help: `if let` needs to be written without parentheses | 3 | if let Some(y) = x {} | -- -- error: aborting due to previous error Fixes rust-lang#83274
@@ -0,0 +1,7 @@ | |||
// check-fail |
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 another copy of this file with // check-pass
and the gate enabled to catch any regressions?
let cond = self.parse_cond_expr()?; | ||
let mut cond = self.parse_cond_expr()?.into_inner(); | ||
|
||
if let ExprKind::Paren(paren) = &cond.kind { |
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.
We still need the feature gate check we talked about here, 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.
That's blocked currently. Have you seen my comment above? #83407 (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.
Hadn't, looking into it. (The parser is indeed not supposed to be able to use that to avoid diverging parsing based on feature gates.)
@@ -1765,7 +1765,20 @@ impl<'a> Parser<'a> { | |||
/// Parses an `if` expression (`if` token already eaten). | |||
fn parse_if_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { | |||
let lo = self.prev_token.span; | |||
let cond = self.parse_cond_expr()?; | |||
let mut cond = self.parse_cond_expr()?.into_inner(); |
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.
Is the into_inner
necessary? IIUC, you need that to be able to reassing, but wouldn't changing line 1777 to be cond = P(cond.peel_parens_owned());
work as well and make this "unwrapping/rewrapping" unnecessary in the happy path? I'm just concerned about doing unnecessary work that might marginally slow down compile times.
@osa1 Ping from triage, could you address the comments above? Thanks! |
Sorry for late response. I think this is currently stuck as we don't have the information we need in parse time, see this thread: #83407 (comment) I'd like to keep this open for a bit more and think about it more before closing. |
Ping from triage: |
When we recover from
if (let ...)
in lowering we can't "ungate" thesyntax after generating the error
as the feature_gate::check_crate pass has already run. This is mentioned
in these comments in the old code
We now recover from
if (let ...)
in parsing, generate the error asbefore, and "ungate" the syntax. For example, in this code:
We would previously generate:
The first error doesn't make sense. With the "ungating" we now generate
just the second one:
Fixes #83274