diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 9e74a7b1ab38c..121e634faa18a 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -203,6 +203,82 @@ impl<'a> Parser<'a> { lhs = match op.node { AssocOp::Binary(ast_op) => { + if ast_op == ast::BinOpKind::Lt + && let Some((ident, kind)) = self.token.lifetime() + && self.look_ahead(1, |t| *t == token::Gt) + && let ExprKind::Path(..) = &lhs.kind + { + let ExprKind::Path(qself, mut path) = lhs.kind.clone() else { + unreachable!("matched above") + }; + + // Intercept missing turbofish (e.g. `Struct<'_>`, `Struct<'a>`). + // Match normal label parsing diagnostics: reserved keywords emit + // `KeywordLabel`, named lifetimes emit `UnexpectedTokenAfterLabel`. + let mut err = if kind == IdentKind::Normal + && ident.without_first_quote().is_reserved() + { + self.dcx() + .create_err(crate::diagnostics::KeywordLabel { span: ident.span }) + } else { + self.dcx().create_err(crate::diagnostics::UnexpectedTokenAfterLabel { + span: self.look_ahead(1, |t| t.span), // the `>` token + remove_label: None, + enclose_in_block: None, + }) + }; + + let op_span = op.span; + err.span_suggestion( + op_span.shrink_to_lo(), + format!( + "use `::<...>` instead of `<...>` to specify lifetime arguments for `{}`", + path.segments.last().expect("a Path always has at least one segment").ident + ), + "::", + Applicability::MachineApplicable, + ); + err.emit(); + + self.bump(); // consume lifetime + let gt_span = self.token.span; + self.bump(); // consume `>` + + // Graft the lifetime onto the `lhs` path. + if let Some(last_segment) = path.segments.last_mut() { + let arg = ast::GenericArg::Lifetime(ast::Lifetime { + id: ast::DUMMY_NODE_ID, + ident, + }); + let args = ast::AngleBracketedArgs { + span: op_span.to(gt_span), + args: thin_vec::thin_vec![ast::AngleBracketedArg::Arg(arg)], + }; + last_segment.args = + Some(Box::new(ast::GenericArgs::AngleBracketed(args))); + } + + // Resume parsing as a struct literal. + if self.token == token::OpenBrace { + if let Some(expr) = self.maybe_parse_struct_expr(&qself, &path) { + lhs = expr?; + continue; + } + } + + // Otherwise, resume parsing as a postfix expression (e.g. function call). + let mut lhs_expr = *lhs; + lhs_expr.kind = ExprKind::Path(qself, path); + let lhs_span = lhs_expr.span; + let recovered_lhs = Box::new(lhs_expr); + lhs = self.parse_expr_dot_or_call_with( + ast::AttrVec::new(), + recovered_lhs, + lhs_span, + )?; + continue; + } + let (rhs, span) = finish_parsing_bin_op(self)?; self.mk_expr(span, self.mk_binary(respan(op.span, ast_op), lhs, rhs)) } diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.fixed b/tests/ui/parser/recover/missing-turbofish-lifetime.fixed new file mode 100644 index 0000000000000..0e1ebca73d0cc --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.fixed @@ -0,0 +1,42 @@ +//! Regression test for +//! +//! Test that omitting the turbofish when passing a lifetime to an expression +//! emits a targeted suggestion rather than a cascading syntax error. + +#![allow(dead_code)] + +//@ run-rustfix + +struct Struct<'a> { + string: &'a str, +} + +fn struct_with_reserved_lifetime() { + let _ = Struct::<'_> { + //~^ ERROR labels cannot use keyword names + string: "", + }; +} + +fn struct_with_named_lifetime<'a>() { + let _ = Struct::<'a> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + string: "", + }; +} + +fn struct_with_multichar_lifetime<'abc>() { + let _ = Struct::<'abc> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + string: "", + }; +} + +struct TupleStruct<'a>(&'a str); + +fn tuple_struct_with_reserved_lifetime() { + let _ = TupleStruct::<'_>(""); + //~^ ERROR labels cannot use keyword names +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.rs b/tests/ui/parser/recover/missing-turbofish-lifetime.rs new file mode 100644 index 0000000000000..330c72fa849bd --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.rs @@ -0,0 +1,42 @@ +//! Regression test for +//! +//! Test that omitting the turbofish when passing a lifetime to an expression +//! emits a targeted suggestion rather than a cascading syntax error. + +#![allow(dead_code)] + +//@ run-rustfix + +struct Struct<'a> { + string: &'a str, +} + +fn struct_with_reserved_lifetime() { + let _ = Struct<'_> { + //~^ ERROR labels cannot use keyword names + string: "", + }; +} + +fn struct_with_named_lifetime<'a>() { + let _ = Struct<'a> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + string: "", + }; +} + +fn struct_with_multichar_lifetime<'abc>() { + let _ = Struct<'abc> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + string: "", + }; +} + +struct TupleStruct<'a>(&'a str); + +fn tuple_struct_with_reserved_lifetime() { + let _ = TupleStruct<'_>(""); + //~^ ERROR labels cannot use keyword names +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.stderr b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr new file mode 100644 index 0000000000000..f9256ea2f1574 --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr @@ -0,0 +1,46 @@ +error: labels cannot use keyword names + --> $DIR/missing-turbofish-lifetime.rs:15:20 + | +LL | let _ = Struct<'_> { + | ^^ + | +help: use `::<...>` instead of `<...>` to specify lifetime arguments for `Struct` + | +LL | let _ = Struct::<'_> { + | ++ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:22:22 + | +LL | let _ = Struct<'a> { + | ^ expected `while`, `for`, `loop` or `{` after a label + | +help: use `::<...>` instead of `<...>` to specify lifetime arguments for `Struct` + | +LL | let _ = Struct::<'a> { + | ++ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:29:24 + | +LL | let _ = Struct<'abc> { + | ^ expected `while`, `for`, `loop` or `{` after a label + | +help: use `::<...>` instead of `<...>` to specify lifetime arguments for `Struct` + | +LL | let _ = Struct::<'abc> { + | ++ + +error: labels cannot use keyword names + --> $DIR/missing-turbofish-lifetime.rs:38:25 + | +LL | let _ = TupleStruct<'_>(""); + | ^^ + | +help: use `::<...>` instead of `<...>` to specify lifetime arguments for `TupleStruct` + | +LL | let _ = TupleStruct::<'_>(""); + | ++ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/parser/require-parens-for-chained-comparison.rs b/tests/ui/parser/require-parens-for-chained-comparison.rs index 6152fff6c0365..f2f81e0318e2b 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.rs +++ b/tests/ui/parser/require-parens-for-chained-comparison.rs @@ -27,11 +27,9 @@ fn main() { //~| ERROR labels cannot use keyword names f<'_>(); - //~^ ERROR comparison operators cannot be chained - //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments - //~| ERROR expected - //~| HELP add `'` to close the char literal + //~^ ERROR cannot find function `f` in this scope //~| ERROR labels cannot use keyword names + //~| HELP use `::<...>` instead of `<...>` to specify lifetime arguments for `f` let _ = f; //~^ ERROR comparison operators cannot be chained diff --git a/tests/ui/parser/require-parens-for-chained-comparison.stderr b/tests/ui/parser/require-parens-for-chained-comparison.stderr index 9edfae3625027..df1195ab5c455 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.stderr +++ b/tests/ui/parser/require-parens-for-chained-comparison.stderr @@ -86,31 +86,14 @@ error: labels cannot use keyword names | LL | f<'_>(); | ^^ - -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/require-parens-for-chained-comparison.rs:29:9 - | -LL | f<'_>(); - | ^ expected `while`, `for`, `loop` or `{` after a label | -help: add `'` to close the char literal - | -LL | f<'_'>(); - | + - -error: comparison operators cannot be chained - --> $DIR/require-parens-for-chained-comparison.rs:29:6 - | -LL | f<'_>(); - | ^ ^ - | -help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +help: use `::<...>` instead of `<...>` to specify lifetime arguments for `f` | LL | f::<'_>(); | ++ error: comparison operators cannot be chained - --> $DIR/require-parens-for-chained-comparison.rs:36:14 + --> $DIR/require-parens-for-chained-comparison.rs:34:14 | LL | let _ = f; | ^ ^ @@ -118,5 +101,12 @@ LL | let _ = f; = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments = help: or use `(...)` if you meant to specify fn arguments -error: aborting due to 12 previous errors +error[E0425]: cannot find function `f` in this scope + --> $DIR/require-parens-for-chained-comparison.rs:29:5 + | +LL | f<'_>(); + | ^ not found in this scope + +error: aborting due to 11 previous errors +For more information about this error, try `rustc --explain E0425`.