diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 5a2540f05c53b..ff5e6f29170af 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -1243,43 +1243,19 @@ impl<'a> Parser<'a> { ) -> PResult<'a, ErrorGuaranteed> { if let ExprKind::Binary(binop, _, _) = &expr.kind && let ast::BinOpKind::Lt = binop.node - && self.eat(exp!(Comma)) + && self.parse_mistyped_turbofish_generic_args() { - let x = self.parse_seq_to_before_end( - exp!(Gt), - SeqSep::trailing_allowed(exp!(Comma)), - |p| match p.parse_generic_arg(None)? { - Some(arg) => Ok(arg), - // If we didn't eat a generic arg, then we should error. - None => p.unexpected_any(), - }, - ); - match x { - Ok((_, _, Recovered::No)) => { - if self.eat(exp!(Gt)) { - // We made sense of it. Improve the error message. - e.span_suggestion_verbose( - binop.span.shrink_to_lo(), - msg!("use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments"), - "::", - Applicability::MaybeIncorrect, - ); - match self.parse_expr() { - Ok(_) => { - // The subsequent expression is valid. Mark - // `expr` as erroneous and emit `e` now, but - // return `Ok` so parsing can continue. - let guar = e.emit(); - *expr = self.mk_expr_err(expr.span.to(self.prev_token.span), guar); - return Ok(guar); - } - Err(err) => { - err.cancel(); - } - } - } + // We made sense of it. Improve the error message. + sugg_missing_turbofish(&mut e, binop.span); + match self.parse_expr() { + Ok(_) => { + // The subsequent expression is valid. Mark + // `expr` as erroneous and emit `e` now, but + // return `Ok` so parsing can continue. + let guar = e.emit(); + *expr = self.mk_expr_err(expr.span.to(self.prev_token.span), guar); + return Ok(guar); } - Ok((_, _, Recovered::Yes(_))) => {} Err(err) => { err.cancel(); } @@ -1288,6 +1264,50 @@ impl<'a> Parser<'a> { Err(e) } + /// Parses the `, T, U>` tail of a `Foo` whose turbofish `::` is missing, so it parsed + /// as a comparison. On failure the parser is left mid-way, so callers must snapshot first. + fn parse_mistyped_turbofish_generic_args(&mut self) -> bool { + if !self.eat(exp!(Comma)) { + return false; + } + match self.parse_seq_to_before_end(exp!(Gt), SeqSep::trailing_allowed(exp!(Comma)), |p| { + match p.parse_generic_arg(None)? { + Some(arg) => Ok(arg), + None => p.unexpected_any(), + } + }) { + Ok((_, _, Recovered::No)) => self.eat(exp!(Gt)), + Ok((_, _, Recovered::Yes(_))) => false, + Err(err) => { + err.cancel(); + false + } + } + } + + /// Check whether a call argument that parsed as a `<` comparison is really a path missing its + /// turbofish, i.e. the generic args are followed by `::` or a call. Leaves the parser after + /// what it managed to read, so callers must snapshot first. + pub(super) fn probe_missing_turbofish(&mut self) -> bool { + self.with_recovery(super::Recovery::Forbidden, |this| { + this.parse_mistyped_turbofish_generic_args() + && match this.token.kind { + token::PathSep => { + this.bump(); + match this.parse_expr() { + Ok(_) => true, + Err(err) => { + err.cancel(); + false + } + } + } + token::OpenParen => this.consume_fn_args().is_ok(), + _ => false, + } + }) + } + /// Suggest add the missing `let` before the identifier in stmt /// `a: Ty = 1` -> `let a: Ty = 1` pub(super) fn suggest_add_missing_let_for_stmt(&mut self, err: &mut Diag<'a>) { @@ -3172,3 +3192,13 @@ impl<'a> Parser<'a> { }) } } + +/// Suggest inserting the `::` of a turbofish before the `<` that parsed as a comparison. +pub(super) fn sugg_missing_turbofish(err: &mut Diag<'_>, binop_span: Span) { + err.span_suggestion_verbose( + binop_span.shrink_to_lo(), + msg!("use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments"), + "::", + Applicability::MaybeIncorrect, + ); +} diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 12de4957e99c2..249e1bde6cf43 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -27,7 +27,7 @@ use rustc_span::{BytePos, ErrorGuaranteed, Ident, Pos, Span, Spanned, Symbol, kw use thin_vec::{ThinVec, thin_vec}; use tracing::instrument; -use super::diagnostics::SnapshotParser; +use super::diagnostics::{SnapshotParser, sugg_missing_turbofish}; use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma}; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{ @@ -87,7 +87,37 @@ impl<'a> Parser<'a> { /// Parses a sequence of expressions delimited by parentheses. fn parse_expr_paren_seq(&mut self) -> PResult<'a, ThinVec>> { - self.parse_paren_comma_seq(Self::parse_expr).map(|(r, _)| r) + let mut candidates = Vec::new(); + + self.parse_paren_comma_seq(|p| match p.parse_expr() { + Ok(expr) => { + if p.may_recover() + && let ExprKind::Binary(binop, lhs, _) = &expr.kind + && binop.node == BinOpKind::Lt + && matches!(lhs.kind, ExprKind::Path(..)) + { + candidates.push((p.create_snapshot_for_diagnostic(), binop.span)); + } + Ok(expr) + } + Err(mut err) => { + if candidates.is_empty() { + return Err(err); + } + let failed = p.create_snapshot_for_diagnostic(); + let failed_pos = p.approx_token_stream_pos(); + while let Some((snapshot, binop_span)) = candidates.pop() { + p.restore_snapshot(snapshot); + if p.probe_missing_turbofish() && p.approx_token_stream_pos() > failed_pos { + sugg_missing_turbofish(&mut err, binop_span); + break; + } + } + p.restore_snapshot(failed); + Err(err) + } + }) + .map(|(r, _)| r) } /// Parses an expression, subject to the given restrictions. diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 80c1eeb4ef041..5f9b9c7d1b0b7 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -866,6 +866,9 @@ impl<'a> Parser<'a> { break; } Err(mut expect_err) => { + if !self.may_recover() { + return Err(expect_err); + } let sp = self.prev_token.span.shrink_to_hi(); let token_str = pprust::token_kind_to_string(&exp.tok); diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 6bc7195b8371c..4a02227fa6a77 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -948,7 +948,7 @@ impl<'a> Parser<'a> { } } else if self.token.is_keyword(kw::Const) { return self.recover_const_param_declaration(ty_generics); - } else { + } else if self.may_recover() { // Fall back by trying to parse a const-expr expression. If we successfully do so, // then we should report an error that it needs to be wrapped in braces. let snapshot = self.create_snapshot_for_diagnostic(); @@ -965,6 +965,8 @@ impl<'a> Parser<'a> { return Ok(None); } } + } else { + return Ok(None); }; Ok(Some(arg)) diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.fixed b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.fixed new file mode 100644 index 0000000000000..ba30e150b9f85 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.fixed @@ -0,0 +1,39 @@ +//@ run-rustfix +#![allow(dead_code)] + +struct S; + +struct Many { + a: A, + b: B, + c: C, + d: D, +} +impl Many { + fn new() -> Self { + todo!() + } +} +fn bar(_: Many) {} + +struct Tuple(A, B, C, D); +fn baz(_: Tuple) {} + +fn take_two(_: bool, _: bool) {} +fn take_three(_: bool, _: bool, _: Many, i32, i32>) {} + +fn main() { + let _ = bar(Many::, i32, i32>::new()); + //~^ ERROR expected expression + baz(Tuple::, i32, i32>(1, Many::new(), 2, 3)); + //~^ ERROR expected expression + + // These are unambiguously comparisons and must keep compiling. + let (a, b, c, d) = (1, 2, 3, 4); + take_two(a < b, c > (d)); + take_two(a < b, c > ::std::primitive::i32::MAX); + + // An argument preceded by genuine comparisons. + take_three(a < b, c > (d), Many::, i32, i32>::new()); + //~^ ERROR expected expression +} diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs new file mode 100644 index 0000000000000..f20e72cc97546 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs @@ -0,0 +1,39 @@ +//@ run-rustfix +#![allow(dead_code)] + +struct S; + +struct Many { + a: A, + b: B, + c: C, + d: D, +} +impl Many { + fn new() -> Self { + todo!() + } +} +fn bar(_: Many) {} + +struct Tuple(A, B, C, D); +fn baz(_: Tuple) {} + +fn take_two(_: bool, _: bool) {} +fn take_three(_: bool, _: bool, _: Many, i32, i32>) {} + +fn main() { + let _ = bar(Many, i32, i32>::new()); + //~^ ERROR expected expression + baz(Tuple, i32, i32>(1, Many::new(), 2, 3)); + //~^ ERROR expected expression + + // These are unambiguously comparisons and must keep compiling. + let (a, b, c, d) = (1, 2, 3, 4); + take_two(a < b, c > (d)); + take_two(a < b, c > ::std::primitive::i32::MAX); + + // An argument preceded by genuine comparisons. + take_three(a < b, c > (d), Many, i32, i32>::new()); + //~^ ERROR expected expression +} diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr new file mode 100644 index 0000000000000..d5649e6f3db35 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr @@ -0,0 +1,35 @@ +error: expected expression, found `,` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:26:46 + | +LL | let _ = bar(Many, i32, i32>::new()); + | ^ expected expression + | +help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + | +LL | let _ = bar(Many::, i32, i32>::new()); + | ++ + +error: expected expression, found `,` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:28:39 + | +LL | baz(Tuple, i32, i32>(1, Many::new(), 2, 3)); + | ^ expected expression + | +help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + | +LL | baz(Tuple::, i32, i32>(1, Many::new(), 2, 3)); + | ++ + +error: expected expression, found `,` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:37:61 + | +LL | take_three(a < b, c > (d), Many, i32, i32>::new()); + | ^ expected expression + | +help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + | +LL | take_three(a < b, c > (d), Many::, i32, i32>::new()); + | ++ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/suggestions/suggest-turbofish-unrelated-parse-error.rs b/tests/ui/suggestions/suggest-turbofish-unrelated-parse-error.rs new file mode 100644 index 0000000000000..adecbf17cbc6e --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-unrelated-parse-error.rs @@ -0,0 +1,38 @@ +// An argument that fails to parse for an unrelated reason must not make us blame an earlier +// comparison for a missing turbofish. + +fn take_three(_: bool, _: bool, _: ()) {} + +fn take_generic(_: bool, _: T, _: ()) {} + +struct S; +struct Many(A, B, C, D); +impl Many { + fn new() -> Self { + todo!() + } +} + +fn main() { + let (a, b, c, d) = (1, 2, 3, 4); + take_three(a < b, c > (d), @); + //~^ ERROR expected expression, found `@` +} + +fn closure_argument() { + let (a, b) = (1, 2); + take_generic(a < b, || 0, @); + //~^ ERROR expected expression, found `@` +} + +fn missing_comma() { + let (a, b, c, d) = (1, 2, 3, 4); + take_three(a < b, c d, @); + //~^ ERROR expected one of `!`, `)`, `,`, `.`, `::`, `?`, `{`, or an operator, found `d` + //~| ERROR expected expression, found `@` +} + +fn non_path_lhs() { + take_generic(1 < 2, Many<(), i32, S, S>, i32, i32>::new(), ()); + //~^ ERROR expected expression, found `,` +} diff --git a/tests/ui/suggestions/suggest-turbofish-unrelated-parse-error.stderr b/tests/ui/suggestions/suggest-turbofish-unrelated-parse-error.stderr new file mode 100644 index 0000000000000..1af584b5824c0 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-unrelated-parse-error.stderr @@ -0,0 +1,34 @@ +error: expected expression, found `@` + --> $DIR/suggest-turbofish-unrelated-parse-error.rs:18:32 + | +LL | take_three(a < b, c > (d), @); + | ^ expected expression + +error: expected expression, found `@` + --> $DIR/suggest-turbofish-unrelated-parse-error.rs:24:31 + | +LL | take_generic(a < b, || 0, @); + | ^ expected expression + +error: expected one of `!`, `)`, `,`, `.`, `::`, `?`, `{`, or an operator, found `d` + --> $DIR/suggest-turbofish-unrelated-parse-error.rs:30:25 + | +LL | take_three(a < b, c d, @); + | -^ expected one of 8 possible tokens + | | + | help: missing `,` + +error: expected expression, found `@` + --> $DIR/suggest-turbofish-unrelated-parse-error.rs:30:28 + | +LL | take_three(a < b, c d, @); + | ^ expected expression + +error: expected expression, found `,` + --> $DIR/suggest-turbofish-unrelated-parse-error.rs:36:44 + | +LL | take_generic(1 < 2, Many<(), i32, S, S>, i32, i32>::new(), ()); + | ^ expected expression + +error: aborting due to 5 previous errors +