Skip to content
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

Detect incorrect chaining of if and if let conditions and recover #103405

Merged
merged 2 commits into from
Nov 18, 2022

Conversation

chenyukang
Copy link
Member

Fixes #103381

@rustbot rustbot added A-translation Area: Translation infrastructure, and migrating existing diagnostics to SessionDiagnostic T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 22, 2022
@rust-highfive
Copy link
Collaborator

r? @petrochenkov

(rust-highfive has picked a reviewer for you, use r? to override)

@rustbot
Copy link
Collaborator

rustbot commented Oct 22, 2022

rustc_error_messages was changed

cc @davidtwco, @compiler-errors, @JohnTitor, @estebank, @TaKO8Ki

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 22, 2022
@chenyukang
Copy link
Member Author

r? @estebank

@rust-log-analyzer

This comment has been minimized.

Copy link
Member

@compiler-errors compiler-errors left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks existing code -- if expressions are allowed to be their own conditions within other if statements.

fn main() {
    if true && if let 1 = 1 { true } else { true } {}
}

Before this PR: compiles

After this PR:

error: unexpected `if` in the condition expression
 --> /home/gh-compiler-errors/test.rs:2:16
  |
2 |     if true && if let 1 = 1 { true } else { true } {}
  |                ^^^
  |
help: remove the `if`
  |
2 -     if true && if let 1 = 1 { true } else { true } {}
2 +     if true && let 1 = 1 { true } else { true } {}
  |

error[E0658]: `let` expressions in this position are unstable
 --> /home/gh-compiler-errors/test.rs:2:19
  |
2 |     if true && if let 1 = 1 { true } else { true } {}
  |                   ^^^^^^^^^
  |
  = note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
  = help: add `#![feature(let_chains)]` to the crate attributes to enable

error[E0308]: mismatched types
 --> /home/gh-compiler-errors/test.rs:2:31
  |
2 |     if true && if let 1 = 1 { true } else { true } {}
  |     --------------------------^^^^----------------
  |     |                         |
  |     |                         expected `()`, found `bool`
  |     expected this to be `()`

error[E0308]: mismatched types
 --> /home/gh-compiler-errors/test.rs:2:45
  |
2 |     if true && if let 1 = 1 { true } else { true } {}
  |     ----------------------------------------^^^^--
  |     |                                       |
  |     |                                       expected `()`, found `bool`
  |     expected this to be `()`

error: aborting due to 4 previous errors

Some errors have detailed explanations: E0308, E0658.
For more information about an error, try `rustc --explain E0308`.

@chenyukang
Copy link
Member Author

chenyukang commented Oct 23, 2022

This breaks existing code -- if expressions are allowed to be their own conditions within other if statements.

This code will also compile in current compiler:

fn should_ok() {
    if true && if let x = 1 { true } else { true } {}
}

With this warning:

warning: irrefutable `if let` pattern
  --> /home/cat/code/rust/src/test/ui/parser/issue-103381.rs:13:19
   |
13 |     if true && if let x = 1 { true } else { true } {}
   |                   ^^^^^^^^^
   |
   = note: `#[warn(irrefutable_let_patterns)]` on by default
   = note: this pattern will always match, so the `if let` is useless
   = help: consider replacing the `if let` with a `let`

warning: 1 warning emitted

@compiler-errors
Copy link
Member

Just noting that "irrefutable if let pattern" warning is unrelated to this issue, and is just because let x = 1 is a "irrefutable" binding, i.e. not actually conditional.

@chenyukang
Copy link
Member Author

chenyukang commented Oct 23, 2022

The job x86_64-gnu-llvm-13 failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)

From the UI testing result, we have this syntax:

fn shoule_match_ok() {
    #[cfg(feature = "full")]
    {
        let a = 1;
        let b = 2;
        if match a {
            1 if b == 1 => true,
            _ => false,
        } && if a > 1 { true } else { false }
        {
            true
        }
    }
}

I think it's not a readable code, but it compiled and runnable 😂
Anyway, I plan to restrict this suggestion to limited scenarios:

            // recovery from `if b && if let Some(x)`
            if op == AssocOp::LAnd
                && self.token.is_keyword(kw::If)
                && self.look_ahead(1, |t| t.is_bool_lit())
                || (self.look_ahead(1, |t| t.is_keyword(kw::Let))
                    && self.look_ahead(2, |t| {
                        t.is_ident_named(sym::Some) || t.is_ident_named(sym::None)
                    }))
            {
                // eat the `if`
                self.bump();
                self.sess.emit_err(UnexpectedIfWithIf(
                    self.prev_token.span.to(self.token.span.shrink_to_lo()),
                ));
            }

Not sure whether there is a better way.

@compiler-errors
Copy link
Member

compiler-errors commented Oct 23, 2022

@chenyukang, this approach still has issues:

fn main() {
    if true && if true { true } else { false } {}
}

This fails to compile with your new changes:

error: unexpected `if` in the condition expression
 --> /home/gh-compiler-errors/test.rs:2:16
  |
2 |     if true && if true { true } else { false } {}
  |                ^^^
  |
help: remove the `if`
  |
2 -     if true && if true { true } else { false } {}
2 +     if true && true { true } else { false } {}
  |

error[E0308]: mismatched types
 --> /home/gh-compiler-errors/test.rs:2:26
  |
2 |     if true && if true { true } else { false } {}
  |     ---------------------^^^^-----------------
  |     |                    |
  |     |                    expected `()`, found `bool`
  |     expected this to be `()`

error[E0308]: mismatched types
 --> /home/gh-compiler-errors/test.rs:2:40
  |
2 |     if true && if true { true } else { false } {}
  |     -----------------------------------^^^^^--
  |     |                                  |
  |     |                                  expected `()`, found `bool`
  |     expected this to be `()`

error: aborting due to 3 previous errors

I'm pretty sure it's impossible to detect #103381 inside of parse_assoc_expr_with -- you need to add a suggestion to remove if only after the parser has already detected an error, probably by passing the correct state down to parse_if_after_cond or some similar function that is involved in parsing if statements.

This may be hard to do, or not worth the changes to the parser to allow this state to be tracked.

@chenyukang
Copy link
Member Author

chenyukang commented Oct 23, 2022 via email

@rust-log-analyzer

This comment has been minimized.

@Noratrieb
Copy link
Member

Hi, I've seen you changed some diagnostic structs in your PR. After #103345, the way we refer to fluent messages changed. They are now in a flat namespace with the same identifier as in the fluent file. For example, parser::cool_thing is now parser_cool_thing and parser::suggestion just suggestion.
You should rebase to the latest master and change your fluent message references as described above. Thanks!

@chenyukang
Copy link
Member Author

chenyukang commented Oct 23, 2022

This may be hard to do, or not worth the changes to the parser to allow this state to be tracked.

Take some time to investigate it.
Yes, seems it is not easy since we need to rollback too much.
I think we'd better don't try to suggest and fix this case right now, it's easy for developer fix without diagnostics:

 let _ = if true && if true { true } else { false };
 let _ = if true && if false { true } else { false };

Only add check for && if let Option...

@compiler-errors compiler-errors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 2, 2022
@chenyukang
Copy link
Member Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Nov 9, 2022
&& self.token.is_keyword(kw::If)
&& self.look_ahead(1, |t| t.is_keyword(kw::Let))
&& self
.look_ahead(2, |t| t.is_ident_named(sym::Some) || t.is_ident_named(sym::None))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following code is perfectly valid:

if true
    && if let Some(1) = Some(1) { true } else { true }
{
    todo!()
}

In general, it is always possible to write valid code containing if <some condition> && if let <some pattern>.

I don't think it's possible to detect the scenario you're looking for until after you parse the inner if let .. expression.

@compiler-errors compiler-errors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 10, 2022
@compiler-errors
Copy link
Member

compiler-errors commented Nov 10, 2022

@chenyukang, camsteffen is correct, and as I noted in my previous comment, I'm pretty sure it's impossible to detect #103381 inside of parse_assoc_expr_with. Marking as S-waiting-on-author until this PR is reformulated.

This check needs to live in another function using a different strategy, after parsing has detected a failure. Be very careful about adding parser recoveries before we've detected an error, since it's very1 easy2 to accidentally introduce changes in parser behavior while improving error messages during parsing...

@chenyukang
Copy link
Member Author

@chenyukang, camsteffen is correct, and as I noted in my previous comment, I'm pretty sure it's impossible to detect #103381 inside of parse_assoc_expr_with. Marking as S-waiting-on-author until this PR is reformulated.

This check needs to live in another function using a different strategy, after parsing has detected a failure. Be very careful about adding parser recoveries before we've detected an error, since it's very1 easy2 to accidentally introduce changes in parser behavior while improving error messages during parsing...

Got it, thanks!
I didn't understand the trivial differences between those code pattern.

@chenyukang
Copy link
Member Author

I think we'd better don't recover from these scenarios, but give proper diagnostics 😁
Code updated.

@estebank
Copy link
Contributor

r? @compiler-errors as you're already familiar with the changes :)

@rustbot rustbot assigned compiler-errors and unassigned estebank Nov 10, 2022
Comment on lines +25 to +34
error: unexpected `if` in the condition expression
--> $DIR/issue-103381.rs:19:15
|
LL | if true && if true { true } else { false };
| ^^^^
|
help: remove the `if`
|
LL - if true && if true { true } else { false };
LL + if true && true { true } else { false };
Copy link
Contributor

@estebank estebank Nov 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens for if true && if true { true } else { false } { true } else { false };?

Given where you call the recovery it should succeed, right? In that case I believe the code changes look fine because the error is only ever precluding another, so there are no changes of regressions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given where you call the recovery it should succeed, right?

yeah, this is my thinking. I'll give this a better review later, but I much prefer this approach since it's already deep in the "bad path".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this code will compile successfully, I have added it as a UI.

@compiler-errors
Copy link
Member

@rustbot ready

I will get around to reviewing this soon.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Nov 15, 2022
@bors
Copy link
Contributor

bors commented Nov 15, 2022

☔ The latest upstream changes (presumably #104418) made this pull request unmergeable. Please resolve the merge conflicts.

@chenyukang
Copy link
Member Author

@rustbot ready

conflict resolved.

@compiler-errors
Copy link
Member

@bors r+

@bors
Copy link
Contributor

bors commented Nov 16, 2022

📌 Commit cd2bde1 has been approved by compiler-errors

It is now in the queue for this repository.

@bors
Copy link
Contributor

bors commented Nov 16, 2022

🌲 The tree is currently closed for pull requests below priority 1. This pull request will be tested once the tree is reopened.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 16, 2022
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 18, 2022
…iaskrgr

Rollup of 8 pull requests

Successful merges:

 - rust-lang#101162 (Migrate rustc_resolve to use SessionDiagnostic, part # 1)
 - rust-lang#103386 (Don't allow `CoerceUnsized` into `dyn*` (except for trait upcasting))
 - rust-lang#103405 (Detect incorrect chaining of if and if let conditions and recover)
 - rust-lang#103594 (Fix non-associativity of `Instant` math on `aarch64-apple-darwin` targets)
 - rust-lang#104006 (Add variant_name function to `LangItem`)
 - rust-lang#104494 (Migrate GUI test to use functions)
 - rust-lang#104516 (rustdoc: clean up sidebar width CSS)
 - rust-lang#104550 (fix a typo)

Failed merges:

 - rust-lang#104554 (Use `ErrorGuaranteed::unchecked_claim_error_was_emitted` less)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 3efbf30 into rust-lang:master Nov 18, 2022
@rustbot rustbot added this to the 1.67.0 milestone Nov 18, 2022
flip1995 pushed a commit to flip1995/rust that referenced this pull request Nov 21, 2022
…iaskrgr

Rollup of 8 pull requests

Successful merges:

 - rust-lang#101162 (Migrate rustc_resolve to use SessionDiagnostic, part # 1)
 - rust-lang#103386 (Don't allow `CoerceUnsized` into `dyn*` (except for trait upcasting))
 - rust-lang#103405 (Detect incorrect chaining of if and if let conditions and recover)
 - rust-lang#103594 (Fix non-associativity of `Instant` math on `aarch64-apple-darwin` targets)
 - rust-lang#104006 (Add variant_name function to `LangItem`)
 - rust-lang#104494 (Migrate GUI test to use functions)
 - rust-lang#104516 (rustdoc: clean up sidebar width CSS)
 - rust-lang#104550 (fix a typo)

Failed merges:

 - rust-lang#104554 (Use `ErrorGuaranteed::unchecked_claim_error_was_emitted` less)

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-translation Area: Translation infrastructure, and migrating existing diagnostics to SessionDiagnostic S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Detect incorrect chaining of if and if let conditions and recover gracefully
10 participants