Skip to content

suggest ! instead of erroneous not on if/while block parse failure #48858

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3211,6 +3211,21 @@ impl<'a> Parser<'a> {
}
}

/// If the condition of a `while` or `if` expression was an identifier
/// named `not`, that's a clue that the confused user likely meant `!`.
fn is_identifier_not(&self, cond: &Expr) -> bool {
if let ExprKind::Path(_, ref path) = cond.node {
if path.segments.len() == 1 { // just `not`, not `not::etc`
if let Some(segment) = path.segments.iter().next() {
if segment.identifier.name.as_str() == "not" {
return true;
}
}
}
}
false
}

/// Parse an 'if' or 'if let' expression ('if' token already eaten)
pub fn parse_if_expr(&mut self, attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
if self.check_keyword(keywords::Let) {
Expand All @@ -3232,6 +3247,11 @@ impl<'a> Parser<'a> {
}
let not_block = self.token != token::OpenDelim(token::Brace);
let thn = self.parse_block().map_err(|mut err| {
if self.is_identifier_not(&cond) {
err.span_suggestion(cond.span, // FIXME: replaced span doesn't incl. whitespace
Copy link
Contributor

Choose a reason for hiding this comment

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

re: span, same as below

"try replacing identifier `not` with the negation operator",
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks a bit too wordy for a label, I'd personally use something "try replacing not with !", but as a minimum the word "identifier" can be removed here.

Copy link
Member Author

Choose a reason for hiding this comment

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

The motivation was to emphasize that not has no special meaning in Rust-the-language (it's just another ordinary variable/identifier), even if rustc-the-compiler is (after this PR) nice enough to try to figure out notice what you meant, but I agree that ceteris paribus, longer messages are worse.

"!".to_owned());
}
if not_block {
err.span_label(lo, "this `if` statement has a condition, but no block");
}
Expand Down Expand Up @@ -3342,7 +3362,15 @@ impl<'a> Parser<'a> {
return self.parse_while_let_expr(opt_label, span_lo, attrs);
}
let cond = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
let (iattrs, body) = self.parse_inner_attrs_and_block()?;
let (iattrs, body) = self.parse_inner_attrs_and_block()
.map_err(|mut err| {
if self.is_identifier_not(&cond) {
let msg = "try replacing identifier `not` with the negation operator";
// FIXME: replaced span doesn't include trailing whitespace
Copy link
Contributor

Choose a reason for hiding this comment

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

You could merge the cond.span with the next span, get the snippet and synthesize a span that does include the whitespace (in a similar way as suggested in #47574 (comment)).

err.span_suggestion(cond.span, msg, "!".to_owned());
}
err
})?;
attrs.extend(iattrs);
let span = span_lo.to(body.span);
return Ok(self.mk_expr(span, ExprKind::While(cond, body, opt_label), attrs));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

fn gratitude() {
let for_you = false;
if not for_you {
//~^ ERROR expected `{`
println!("I couldn't");
//~^ ERROR expected one of
}
}

fn qualification() {
let the_worst = true;
while not the_worst {
//~^ ERROR expected one of
println!("still pretty bad");
}
}

fn defer() {
let department = false;
// `not` as one segment of a longer path doesn't trigger the smart help
if not::my department {
//~^ ERROR expected `{`
println!("pass");
//~^ ERROR expected one of
}
}

fn should_we() {
let not = true;
if not // lack of braces is [sic]
println!("Then when?");
//~^ ERROR expected `{`
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
error: expected one of `,` or `}`, found `!`
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:15:16
|
LL | println!("I couldn't");
| ^ expected one of `,` or `}` here

error: expected `{`, found `for_you`
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:13:12
|
LL | if not for_you {
| -- ^^^^^^^
| |
| this `if` statement has a condition, but no block
help: try placing this code inside a block
|
LL | if not { for_you{println,}; }
| ^^^^^^^^^^^^^^^^^^^^^^
help: try replacing identifier `not` with the negation operator
|
LL | if ! for_you {
| ^

error: expected one of `!`, `.`, `::`, `?`, `{`, or an operator, found `the_worst`
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:22:15
|
LL | while not the_worst {
| --- ^^^^^^^^^ expected one of `!`, `.`, `::`, `?`, `{`, or an operator here
| |
| help: try replacing identifier `not` with the negation operator: `!`

error: expected one of `,` or `}`, found `!`
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:33:16
|
LL | println!("pass");
| ^ expected one of `,` or `}` here

error: expected `{`, found `department`
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:31:16
|
LL | if not::my department {
| -- -^^^^^^^^^
| _____|__________|
| | |
| | this `if` statement has a condition, but no block
LL | | //~^ ERROR expected `{`
LL | | println!("pass");
LL | | //~^ ERROR expected one of
LL | | }
| |_____- help: try placing this code inside a block: `{ department{println,}; }`
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also modify parse_block so that this suggestion is not given if the next token is an open brace, like in these cases? The suggestion as is is more likely to be wrong than right.


error: expected `{`, found `println`
--> $DIR/issue-46836-identifier-not-instead-of-negation.rs:41:9
|
LL | if not // lack of braces is [sic]
| -- this `if` statement has a condition, but no block
LL | println!("Then when?");
| ^^^^^^^
help: try placing this code inside a block
|
LL | { println!("Then when?") }
|
help: try replacing identifier `not` with the negation operator
|
LL | if ! // lack of braces is [sic]
| ^

error: aborting due to 6 previous errors