From f1be8d16c55990fff8c265352328fd90555feabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 15 Apr 2019 08:08:46 -0700 Subject: [PATCH] Identify missing ambiguous case with best effort suggestion --- src/librustc_resolve/diagnostics.rs | 128 +++++++++++------- src/libsyntax/parse/parser.rs | 8 +- src/test/ui/struct-literal-variant-in-if.rs | 14 ++ .../ui/struct-literal-variant-in-if.stderr | 33 +++++ 4 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 src/test/ui/struct-literal-variant-in-if.rs create mode 100644 src/test/ui/struct-literal-variant-in-if.stderr diff --git a/src/librustc_resolve/diagnostics.rs b/src/librustc_resolve/diagnostics.rs index 9b02f98164f..b7deb546882 100644 --- a/src/librustc_resolve/diagnostics.rs +++ b/src/librustc_resolve/diagnostics.rs @@ -240,6 +240,56 @@ impl<'a> Resolver<'a> { (err, candidates) } + fn followed_by_brace(&self, span: Span) -> (bool, Option<(Span, String)>) { + // HACK(estebank): find a better way to figure out that this was a + // parser issue where a struct literal is being used on an expression + // where a brace being opened means a block is being started. Look + // ahead for the next text to see if `span` is followed by a `{`. + let sm = self.session.source_map(); + let mut sp = span; + loop { + sp = sm.next_point(sp); + match sm.span_to_snippet(sp) { + Ok(ref snippet) => { + if snippet.chars().any(|c| { !c.is_whitespace() }) { + break; + } + } + _ => break, + } + } + let followed_by_brace = match sm.span_to_snippet(sp) { + Ok(ref snippet) if snippet == "{" => true, + _ => false, + }; + // In case this could be a struct literal that needs to be surrounded + // by parenthesis, find the appropriate span. + let mut i = 0; + let mut closing_brace = None; + loop { + sp = sm.next_point(sp); + match sm.span_to_snippet(sp) { + Ok(ref snippet) => { + if snippet == "}" { + let sp = span.to(sp); + if let Ok(snippet) = sm.span_to_snippet(sp) { + closing_brace = Some((sp, snippet)); + } + break; + } + } + _ => break, + } + i += 1; + // The bigger the span, the more likely we're incorrect -- + // bound it to 100 chars long. + if i > 100 { + break; + } + } + return (followed_by_brace, closing_brace) + } + /// Provides context-dependent help for errors reported by the `smart_resolve_path_fragment` /// function. /// Returns `true` if able to provide context-dependent help. @@ -278,6 +328,8 @@ impl<'a> Resolver<'a> { _ => false, }; + let (followed_by_brace, closing_brace) = self.followed_by_brace(span); + match (def, source) { (Def::Macro(..), _) => { err.span_suggestion( @@ -331,52 +383,6 @@ impl<'a> Resolver<'a> { ); } } else { - // HACK(estebank): find a better way to figure out that this was a - // parser issue where a struct literal is being used on an expression - // where a brace being opened means a block is being started. Look - // ahead for the next text to see if `span` is followed by a `{`. - let sm = self.session.source_map(); - let mut sp = span; - loop { - sp = sm.next_point(sp); - match sm.span_to_snippet(sp) { - Ok(ref snippet) => { - if snippet.chars().any(|c| { !c.is_whitespace() }) { - break; - } - } - _ => break, - } - } - let followed_by_brace = match sm.span_to_snippet(sp) { - Ok(ref snippet) if snippet == "{" => true, - _ => false, - }; - // In case this could be a struct literal that needs to be surrounded - // by parenthesis, find the appropriate span. - let mut i = 0; - let mut closing_brace = None; - loop { - sp = sm.next_point(sp); - match sm.span_to_snippet(sp) { - Ok(ref snippet) => { - if snippet == "}" { - let sp = span.to(sp); - if let Ok(snippet) = sm.span_to_snippet(sp) { - closing_brace = Some((sp, snippet)); - } - break; - } - } - _ => break, - } - i += 1; - // The bigger the span, the more likely we're incorrect -- - // bound it to 100 chars long. - if i > 100 { - break; - } - } match source { PathSource::Expr(Some(parent)) => if !path_sep(err, &parent) { err.span_label( @@ -411,7 +417,35 @@ impl<'a> Resolver<'a> { (Def::Union(..), _) | (Def::Variant(..), _) | (Def::Ctor(_, _, CtorKind::Fictive), _) if ns == ValueNS => { - err.span_label(span, format!("did you mean `{} {{ /* fields */ }}`?", path_str)); + match source { + PathSource::Expr(Some(parent)) => if !path_sep(err, &parent) { + err.span_label( + span, + format!("did you mean `{} {{ /* fields */ }}`?", path_str), + ); + } + PathSource::Expr(None) if followed_by_brace == true => { + if let Some((sp, snippet)) = closing_brace { + err.span_suggestion( + sp, + "surround the struct literal with parenthesis", + format!("({})", snippet), + Applicability::MaybeIncorrect, + ); + } else { + err.span_label( + span, + format!("did you mean `({} {{ /* fields */ }})`?", path_str), + ); + } + }, + _ => { + err.span_label( + span, + format!("did you mean `{} {{ /* fields */ }}`?", path_str), + ); + }, + } } (Def::SelfTy(..), _) if ns == ValueNS => { err.span_label(span, fallback_label); diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 7d130470c6a..a82b1e11a52 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -2856,7 +2856,7 @@ impl<'a> Parser<'a> { hi = self.prev_span; ex = ExprKind::Mac(respan(lo.to(hi), Mac_ { path, tts, delim })); } else if self.check(&token::OpenDelim(token::Brace)) { - if let Some(expr) = self.should_parse_struct_expr(lo, path.clone(), attrs.clone()) { + if let Some(expr) = self.should_parse_struct_expr(lo, &path, &attrs) { return expr; } else { hi = path.span; @@ -2907,8 +2907,8 @@ impl<'a> Parser<'a> { fn should_parse_struct_expr( &mut self, lo: Span, - path: ast::Path, - attrs: ThinVec, + path: &ast::Path, + attrs: &ThinVec, ) -> Option>> { let could_be_struct = self.look_ahead(1, |t| t.is_ident()) && ( self.look_ahead(2, |t| *t == token::Colon) @@ -2924,7 +2924,7 @@ impl<'a> Parser<'a> { parse_struct = true; } if parse_struct { - match self.parse_struct_expr(lo, path, attrs) { + match self.parse_struct_expr(lo, path.clone(), attrs.clone()) { Err(err) => return Some(Err(err)), Ok(expr) => { if bad_struct { diff --git a/src/test/ui/struct-literal-variant-in-if.rs b/src/test/ui/struct-literal-variant-in-if.rs new file mode 100644 index 00000000000..2d87c4ca73d --- /dev/null +++ b/src/test/ui/struct-literal-variant-in-if.rs @@ -0,0 +1,14 @@ +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum E { + V { field: bool } +} +fn test_E(x: E) { + let field = true; + if x == E::V { field } {} + //~^ ERROR expected value, found struct variant `E::V` + //~| ERROR mismatched types + let y: usize = (); + //~^ ERROR mismatched types +} + +fn main() {} diff --git a/src/test/ui/struct-literal-variant-in-if.stderr b/src/test/ui/struct-literal-variant-in-if.stderr new file mode 100644 index 00000000000..e38eb0d61e0 --- /dev/null +++ b/src/test/ui/struct-literal-variant-in-if.stderr @@ -0,0 +1,33 @@ +error[E0423]: expected value, found struct variant `E::V` + --> $DIR/struct-literal-variant-in-if.rs:7:13 + | +LL | if x == E::V { field } {} + | ^^^^---------- + | | + | help: surround the struct literal with parenthesis: `(E::V { field })` + +error[E0308]: mismatched types + --> $DIR/struct-literal-variant-in-if.rs:7:20 + | +LL | fn test_E(x: E) { + | - help: try adding a return type: `-> bool` +LL | let field = true; +LL | if x == E::V { field } {} + | ^^^^^ expected (), found bool + | + = note: expected type `()` + found type `bool` + +error[E0308]: mismatched types + --> $DIR/struct-literal-variant-in-if.rs:10:20 + | +LL | let y: usize = (); + | ^^ expected usize, found () + | + = note: expected type `usize` + found type `()` + +error: aborting due to 3 previous errors + +Some errors occurred: E0308, E0423. +For more information about an error, try `rustc --explain E0308`.