From ba0dd8eb026e2dcff27a7ee3b29514a53cc5c1d9 Mon Sep 17 00:00:00 2001 From: "Zack M. Davis" Date: Wed, 21 Mar 2018 22:38:24 -0700 Subject: [PATCH] in which `!` is suggested for erroneous identifier `not` Impressing confused Python users with magical diagnostics is perhaps worth this not-grossly-unreasonable (only 40ish lines) extra complexity in the parser? Thanks to Vadim Petrochenkov for guidance. This resolves #46836. --- src/libsyntax/parse/parser.rs | 43 +++++++++++++++- src/libsyntax/parse/token.rs | 11 +++- ...6836-identifier-not-instead-of-negation.rs | 44 ++++++++++++++++ ...-identifier-not-instead-of-negation.stderr | 50 +++++++++++++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.rs create mode 100644 src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.stderr diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 05c6c1352b2..61de50e8e6a 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -2830,7 +2830,48 @@ pub fn parse_prefix_expr(&mut self, let (span, e) = self.interpolated_or_expr_span(e)?; (lo.to(span), ExprKind::Box(e)) } - _ => return self.parse_dot_or_call_expr(Some(attrs)) + token::Ident(..) if self.token.is_ident_named("not") => { + // `not` is just an ordinary identifier in Rust-the-language, + // but as `rustc`-the-compiler, we can issue clever diagnostics + // for confused users who really want to say `!` + let token_cannot_continue_expr = |t: &token::Token| match *t { + // These tokens can start an expression after `!`, but + // can't continue an expression after an ident + token::Ident(ident, is_raw) => token::ident_can_begin_expr(ident, is_raw), + token::Literal(..) | token::Pound => true, + token::Interpolated(ref nt) => match nt.0 { + token::NtIdent(..) | token::NtExpr(..) | + token::NtBlock(..) | token::NtPath(..) => true, + _ => false, + }, + _ => false + }; + let cannot_continue_expr = self.look_ahead(1, token_cannot_continue_expr); + if cannot_continue_expr { + self.bump(); + // Emit the error ... + let mut err = self.diagnostic() + .struct_span_err(self.span, + &format!("unexpected {} after identifier", + self.this_token_descr())); + // span the `not` plus trailing whitespace to avoid + // trailing whitespace after the `!` in our suggestion + let to_replace = self.sess.codemap() + .span_until_non_whitespace(lo.to(self.span)); + err.span_suggestion_short(to_replace, + "use `!` to perform logical negation", + "!".to_owned()); + err.emit(); + // —and recover! (just as if we were in the block + // for the `token::Not` arm) + let e = self.parse_prefix_expr(None); + let (span, e) = self.interpolated_or_expr_span(e)?; + (lo.to(span), self.mk_unary(UnOp::Not, e)) + } else { + return self.parse_dot_or_call_expr(Some(attrs)); + } + } + _ => { return self.parse_dot_or_call_expr(Some(attrs)); } }; return Ok(self.mk_expr(lo.to(hi), ex, attrs)); } diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs index 6544619af9c..df0ea05005c 100644 --- a/src/libsyntax/parse/token.rs +++ b/src/libsyntax/parse/token.rs @@ -91,7 +91,7 @@ pub fn short_name(&self) -> &'static str { } } -fn ident_can_begin_expr(ident: ast::Ident, is_raw: bool) -> bool { +pub(crate) fn ident_can_begin_expr(ident: ast::Ident, is_raw: bool) -> bool { let ident_token: Token = Ident(ident, is_raw); !ident_token.is_reserved_ident() || @@ -348,6 +348,15 @@ pub fn is_lifetime(&self) -> bool { self.lifetime().is_some() } + /// Returns `true` if the token is a identifier whose name is the given + /// string slice. + pub fn is_ident_named(&self, name: &str) -> bool { + match self.ident() { + Some((ident, _)) => ident.name.as_str() == name, + None => false + } + } + /// Returns `true` if the token is a documentation comment. pub fn is_doc_comment(&self) -> bool { match *self { diff --git a/src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.rs b/src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.rs new file mode 100644 index 00000000000..45265173419 --- /dev/null +++ b/src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.rs @@ -0,0 +1,44 @@ +// 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 or the MIT license +// , 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 unexpected `for_you` after identifier + println!("I couldn't"); + } +} + +fn qualification() { + let the_worst = true; + while not the_worst { + //~^ ERROR unexpected `the_worst` after identifier + println!("still pretty bad"); + } +} + +fn should_we() { + let not = true; + if not // lack of braces is [sic] + println!("Then when?"); + //~^ ERROR expected `{`, found `; + //~| ERROR unexpected `println` after identifier +} + +fn sleepy() { + let resource = not 2; + //~^ ERROR unexpected `2` after identifier +} + +fn main() { + let be_smothered_out_before = true; + let young_souls = not be_smothered_out_before; + //~^ ERROR unexpected `be_smothered_out_before` after identifier +} diff --git a/src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.stderr b/src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.stderr new file mode 100644 index 00000000000..db3478116cb --- /dev/null +++ b/src/test/ui/did_you_mean/issue-46836-identifier-not-instead-of-negation.stderr @@ -0,0 +1,50 @@ +error: unexpected `for_you` after identifier + --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:13:12 + | +LL | if not for_you { + | ----^^^^^^^ + | | + | help: use `!` to perform logical negation + +error: unexpected `the_worst` after identifier + --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:21:15 + | +LL | while not the_worst { + | ----^^^^^^^^^ + | | + | help: use `!` to perform logical negation + +error: unexpected `println` after identifier + --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:30:9 + | +LL | if not // lack of braces is [sic] + | ----- help: use `!` to perform logical negation +LL | println!("Then when?"); + | ^^^^^^^ + +error: expected `{`, found `;` + --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:30:31 + | +LL | if not // lack of braces is [sic] + | -- this `if` statement has a condition, but no block +LL | println!("Then when?"); + | ^ + +error: unexpected `2` after identifier + --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:36:24 + | +LL | let resource = not 2; + | ----^ + | | + | help: use `!` to perform logical negation + +error: unexpected `be_smothered_out_before` after identifier + --> $DIR/issue-46836-identifier-not-instead-of-negation.rs:42:27 + | +LL | let young_souls = not be_smothered_out_before; + | ----^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: use `!` to perform logical negation + +error: aborting due to 6 previous errors + -- 2.44.0