Skip to content
Open
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
76 changes: 76 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
42 changes: 42 additions & 0 deletions tests/ui/parser/recover/missing-turbofish-lifetime.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Regression test for <https://github.com/rust-lang/rust/issues/162656>
//!
//! 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() {}
42 changes: 42 additions & 0 deletions tests/ui/parser/recover/missing-turbofish-lifetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Regression test for <https://github.com/rust-lang/rust/issues/162656>
//!
//! 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() {}
46 changes: 46 additions & 0 deletions tests/ui/parser/recover/missing-turbofish-lifetime.stderr
Original file line number Diff line number Diff line change
@@ -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

6 changes: 2 additions & 4 deletions tests/ui/parser/require-parens-for-chained-comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>;
//~^ ERROR comparison operators cannot be chained
Expand Down
30 changes: 10 additions & 20 deletions tests/ui/parser/require-parens-for-chained-comparison.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -86,37 +86,27 @@ 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<u8>;
| ^ ^
|
= 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`.
Loading