Detect missing else in let statement - #156949
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@bors squash msg="fix issue-156949" |
This comment has been minimized.
This comment has been minimized.
|
🔨 4 commits were squashed into 6bbafec. |
7b541ec to
6bbafec
Compare
6bbafec to
4946c86
Compare
|
@estebank Fixed |
|
@rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
d7ba54d to
c68037d
Compare
This comment has been minimized.
This comment has been minimized.
|
The parser was modified, potentially altering the grammar of (stable) Rust cc @fmease |
7d6bb50 to
47e8efe
Compare
This comment has been minimized.
This comment has been minimized.
47e8efe to
fcb6c44
Compare
This comment has been minimized.
This comment has been minimized.
1243bc5 to
7f175f8
Compare
|
I change the code to error: expected identifier, found keyword `return`
--> $DIR/detect-missing-else-in-let-1.rs:4:23
|
LL | let Some(a) = foo{return;};
| --- ^^^^^^ expected identifier, found keyword
| |
| while parsing this struct
|
help: escape `return` to use it as an identifier
|
LL | let Some(a) = foo{r#return;};
| ++
help: you might have meant to write a diverging block on a refutable `let` statement by using `let-else`
for more information, visit <https://doc.rust-lang.org/beta/rust-by-example/flow_control/let_else.html>
|
LL | let Some(a) = foo else {return;};
| ++++
error: aborting due to 1 previous error@rustbot ready |
This comment has been minimized.
This comment has been minimized.
7f175f8 to
0ea7df6
Compare
This comment has been minimized.
This comment has been minimized.
| snapshot.recover_stmt(); | ||
| snapshot.bump(); | ||
| if might_be_stmt && snapshot.eat(exp!(Semi)) { |
There was a problem hiding this comment.
This doesn't feel quite right. You're recovering the current statement, so if we had foo { return 42; }, it would recover, but I feel like (given that we have a snapshot), we should instead look how we parse the block in let pat = expr else { block };, and call whatever method we call for it.
I'm not entirely sure what we've advanced in each step above. I would write this like this:
// Given `foo { return 42; };`
let mut snapshot = self.create_snapshot_for_diagnostic(); // We've consumed `foo {` already
let might_be_stmt = snapshot.token.is_keyword(kw::Return); // `return`
snapshot.recover_stmt(); // consume `return 42;`
snapshot.bump(); // `}`
if might_be_stmt && snapshot.eat(exp!(Semi)) {Maybe instead of snapshot.recover_stmt() we should do consume_block(), which will eat everything within the foo { .. }, regardless of what it is.
There was a problem hiding this comment.
recover_stmt actually behaves the same as consume_block which consumes everything until reaching } (except we can let consume_block to consume the closing delim), but yes, I think consume_block is better since it makes the intent clearer.
There was a problem hiding this comment.
I actually tried to use parse_block in my first attempt to detect whether the struct field is actually a block, so the compiler will emit the diagnostic for any valid block (currently we only accept single return). If parse_block fails we cancel the error produced by it and skip this diagnostics, but parse_block will try to internally recover instead of returning an error for us to cancel even when I set Recovery to No .
That's why I choose to use is_keyword.🙁
Is this considered a bug that (maybe) a lot of places does not respect Recovery field, or it's just not how Recovery field is used for?
0ea7df6 to
3ee2fb1
Compare
|
@estebank Fixed |
This comment has been minimized.
This comment has been minimized.
3ee2fb1 to
93156ad
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
This comment has been minimized.
This comment has been minimized.
93156ad to
355aec9
Compare
| help: escape `return` to use it as an identifier | ||
| | | ||
| LL | let Some(a) = foo{r#return;}; | ||
| | ++ |
There was a problem hiding this comment.
Future improvement: we should likely keep a list of bindings introduced in the current item and only suggest using identifiers as values (either here for turning keywords into raw identifiers, or in other cases) when that would make sense. I've stopped myself from doing that because it is a worse version of name resolution purely for parsing that will introduce some additional memory pressure, but maybe we should experiment with it and see if it is worthwhile.
| /// Used to detect a missing `else` in a let statement. | ||
| /// e.g. let Some(foo) = bar{return;}; | ||
| const IN_LET = 1 << 6; |
There was a problem hiding this comment.
I am concerned that we're starting to creep on the limits of a byte here, hope we don't have a need to increase the number of flags anytime soon.
Detect missing else in let statement Fixes rust-lang#135857 I add the help message in rustc_resolve. It now will display something like this: ```rust error: expected identifier, found keyword `return` --> ./test.rs:8:24 | 8 | let Some(a) = bar{return;}; | --- ^^^^^^ expected identifier, found keyword | | | while parsing this struct | help: escape `return` to use it as an identifier | 8 | let Some(a) = bar{r#return;}; | ++ error[E0574]: expected struct, variant or union type, found local variable `bar` --> ./test.rs:8:20 | 8 | let Some(a) = bar{return;}; | ^^^ not a struct, variant or union type | help: try adding `else` here: | 8 | let Some(a) = bar else {return;}; | ++++ error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0574`. ``` r? estebank
…uwer Rollup of 13 pull requests Successful merges: - #156949 (Detect missing else in let statement) - #160436 (stabilize `Box::take`) - #160570 (macro_metavar_expr_concat: support concatenating into string literals) - #162837 (Dedicated Display type for CStr::display) - #163099 (Use wrapping arithmetic in `from_str_radix`) - #163166 (Tiny cleanups to deferred liveness) - #161667 (Add `f16` inline ASM support for `nvptx64-nvidia-cuda`) - #163063 (Restore `Send` and `Sync` for `BorrowedCursor`) - #163097 (OpenBSD/sparc64 has switched from GCC to Clang) - #163126 (Skip redundant storage-conflict updates during coroutine layout) - #163135 (librustdoc: remove stale dep on base64) - #163146 (tests: Update `f16b` codegen test for LoongArch and RISC-V) - #163159 (treat inductive cycles as ambig)
Rollup merge of #156949 - bb1yd:detect-missing-else, r=estebank Detect missing else in let statement Fixes #135857 I add the help message in rustc_resolve. It now will display something like this: ```rust error: expected identifier, found keyword `return` --> ./test.rs:8:24 | 8 | let Some(a) = bar{return;}; | --- ^^^^^^ expected identifier, found keyword | | | while parsing this struct | help: escape `return` to use it as an identifier | 8 | let Some(a) = bar{r#return;}; | ++ error[E0574]: expected struct, variant or union type, found local variable `bar` --> ./test.rs:8:20 | 8 | let Some(a) = bar{return;}; | ^^^ not a struct, variant or union type | help: try adding `else` here: | 8 | let Some(a) = bar else {return;}; | ++++ error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0574`. ``` r? estebank
View all comments
Fixes #135857
I add the help message in rustc_resolve. It now will display something like this:
r? estebank