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
8 changes: 8 additions & 0 deletions compiler/rustc_parse/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,14 @@ pub(crate) struct GenericParamsWithoutAngleBrackets {
pub sugg: GenericParamsWithoutAngleBracketsSugg,
}

#[derive(Diagnostic)]
#[diag("use `::<...>` instead of `<...>` to specify lifetime arguments")]
pub(crate) struct MissingTurbofishForLifetime {
#[primary_span]
#[suggestion("add `::`", code = "::", applicability = "machine-applicable", style = "verbose")]
pub span: Span,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion(
"surround the type parameters with angle brackets",
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,12 @@ impl<'a> Parser<'a> {
}
}

pub(super) fn recover_missing_turbofish_for_lifetimes(&self, lt_span: Span) {
self.dcx().emit_err(crate::diagnostics::MissingTurbofishForLifetime {
span: lt_span.shrink_to_lo(),
});
}

/// When writing a turbofish with multiple type parameters missing the leading `::`, we will
/// encounter a parse error when encountering the first `,`.
pub(super) fn check_mistyped_turbofish_with_multiple_type_params(
Expand Down
17 changes: 17 additions & 0 deletions compiler/rustc_parse/src/parser/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,20 @@ impl<'a> Parser<'a> {
is_args_start(&this.token)
};

// A lifetime followed by `>` or `,` is never a valid less-than expression.
// Recovering here avoids an `ExprKind::Err` that breaks postfix operators (e.g. `?`).
let is_missing_turbofish_for_lifetime = |this: &mut Self| {
this.token == token::Lt
&& this.may_recover()
&& style == PathStyle::Expr
&& this.look_ahead(1, |t| t.is_lifetime())
&& this.look_ahead(2, |t| matches!(t.kind, token::Gt | token::Comma))
};

Ok(
if style == PathStyle::Type && check_args_start(self)
|| style != PathStyle::Mod && self.check_path_sep_and_look_ahead(is_args_start)
|| is_missing_turbofish_for_lifetime(self)
{
// We use `style == PathStyle::Expr` to check if this is in a recursion or not. If
// it isn't, then we reset the unmatched angle bracket count as we're about to start
Expand All @@ -314,12 +325,18 @@ impl<'a> Parser<'a> {
self.unmatched_angle_bracket_count = 0;
}

let missing_turbofish =
style == PathStyle::Expr && !self.check_path_sep_and_look_ahead(is_args_start);

// Generic arguments are found - `<`, `(`, `::<` or `::(`.
// First, eat `::` if it exists.
let _ = self.eat_path_sep();

let lo = self.token.span;
let args = if self.eat_lt() {
if missing_turbofish {
self.recover_missing_turbofish_for_lifetimes(lo);
}
// `<'a, T, A = U>`
let args = self.parse_angle_args_with_leading_angle_bracket_recovery(
style,
Expand Down
8 changes: 6 additions & 2 deletions tests/ui/parser/issues/issue-93282.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
fn main() {
//~^ HELP consider introducing lifetime `'a` here
f<'a,>
//~^ ERROR expected
//~| ERROR expected
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
//~| ERROR use of undeclared lifetime name `'a`
//~| ERROR cannot find value `f` in this scope
}

fn bar(a: usize, b: usize) -> usize {
Expand All @@ -12,5 +15,6 @@ fn foo() {
let x = 1;
bar('y, x);
//~^ ERROR expected
//~| HELP add `'` to close the char literal
//~| ERROR mismatched types
}
47 changes: 27 additions & 20 deletions tests/ui/parser/issues/issue-93282.stderr
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
error: expected `while`, `for`, `loop` or `{` after a label
--> $DIR/issue-93282.rs:2:9
|
LL | f<'a,>
| ^ expected `while`, `for`, `loop` or `{` after a label
|
help: add `'` to close the char literal
|
LL | f<'a',>
| +

error: expected one of `.`, `:`, `;`, `?`, `for`, `loop`, `while`, `}`, or an operator, found `,`
--> $DIR/issue-93282.rs:2:9
error: use `::<...>` instead of `<...>` to specify lifetime arguments
--> $DIR/issue-93282.rs:3:6
|
LL | f<'a,>
| ^ expected one of 9 possible tokens
| ^
|
help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
help: add `::`
|
LL | f::<'a,>
| ++

error: expected `while`, `for`, `loop` or `{` after a label
--> $DIR/issue-93282.rs:13:11
--> $DIR/issue-93282.rs:16:11
|
LL | bar('y, x);
| ^ expected `while`, `for`, `loop` or `{` after a label
Expand All @@ -31,20 +20,38 @@ help: add `'` to close the char literal
LL | bar('y', x);
| +

error[E0261]: use of undeclared lifetime name `'a`
--> $DIR/issue-93282.rs:3:7
|
LL | f<'a,>
| ^^ undeclared lifetime
|
help: consider introducing lifetime `'a` here
|
LL | fn main<'a>() {
| ++++

error[E0425]: cannot find value `f` in this scope
--> $DIR/issue-93282.rs:3:5
|
LL | f<'a,>
| ^ not found in this scope

error[E0308]: mismatched types
--> $DIR/issue-93282.rs:13:9
--> $DIR/issue-93282.rs:16:9
|
LL | bar('y, x);
| --- ^^ expected `usize`, found `char`
| |
| arguments to this function are incorrect
|
note: function defined here
--> $DIR/issue-93282.rs:7:4
--> $DIR/issue-93282.rs:10:4
|
LL | fn bar(a: usize, b: usize) -> usize {
| ^^^ --------

error: aborting due to 4 previous errors
error: aborting due to 5 previous errors

For more information about this error, try `rustc --explain E0308`.
Some errors have detailed explanations: E0261, E0308, E0425.
For more information about an error, try `rustc --explain E0261`.
16 changes: 16 additions & 0 deletions tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
macro_rules! m {
($e:expr) => { "expr" };
(a < $l:lifetime >) => { "lifetime" };
}

fn test_macro() {
// Ensure turbofish recovery is disabled during macro matching to avoid spurious diagnostics.
// The second arm does NOT match; the expr-fragment error is a hard error.
let _ = m!(a < 'x >);
//~^ ERROR comparison operators cannot be chained
//~| HELP use `::<...>` instead of `<...>`
//~| HELP or use `(...)`
//~| ERROR expected `while`, `for`, `loop` or `{` after a label
}

fn main() {}
20 changes: 20 additions & 0 deletions tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: expected `while`, `for`, `loop` or `{` after a label
--> $DIR/missing-turbofish-lifetime-macro.rs:9:23
|
LL | let _ = m!(a < 'x >);
| ^ expected `while`, `for`, `loop` or `{` after a label

error: comparison operators cannot be chained
--> $DIR/missing-turbofish-lifetime-macro.rs:9:18
|
LL | ($e:expr) => { "expr" };
| ------- while parsing argument for this `expr` macro fragment
...
LL | let _ = m!(a < 'x >);
| ^ ^
|
= 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 2 previous errors

87 changes: 87 additions & 0 deletions tests/ui/parser/recover/missing-turbofish-lifetime.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! 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 use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
string: "",
};
}

fn struct_with_named_lifetime<'a>() {
let _ = Struct::<'a> {
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
string: "",
};
}

fn struct_with_multichar_lifetime<'abc>() {
let _ = Struct::<'abc> {
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
string: "",
};
}

struct TupleStruct<'a>(&'a str);

fn tuple_struct_with_reserved_lifetime() {
let _ = TupleStruct::<'_>("");
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
}

struct Wrapper<'a>(&'a str);
impl<'a> Wrapper<'a> { fn new(s: &'a str) -> Self { Self(s) } }

struct Struct2<'a> { x: i32, _p: std::marker::PhantomData<&'a ()> }
impl<'a> Struct2<'a> { fn method(&self) -> Option<()> { Some(()) } }

fn f<'a: 'a>() {}

struct StructT<'a, T> { a: &'a str, b: T }
struct Struct3<'a, 'b> { a: &'a str, b: &'b str }

fn chaining_cases<'a, 'b, T>() -> Option<()> {
let _ = Wrapper::<'a>::new("hi");
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method();
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method()?;
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

f::<'_>();
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

// struct with two lifetimes
let _ = Struct3::<'a, 'b> { a: "hi", b: "hi" };
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

// struct with lifetime and type
let _ = StructT::<'a, &str> { a: "hi", b: "hi" };
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

Some(())
}

fn main() {}
87 changes: 87 additions & 0 deletions tests/ui/parser/recover/missing-turbofish-lifetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! 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 use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
string: "",
};
}

fn struct_with_named_lifetime<'a>() {
let _ = Struct<'a> {
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
string: "",
};
}

fn struct_with_multichar_lifetime<'abc>() {
let _ = Struct<'abc> {
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
string: "",
};
}

struct TupleStruct<'a>(&'a str);

fn tuple_struct_with_reserved_lifetime() {
let _ = TupleStruct<'_>("");
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`
}

struct Wrapper<'a>(&'a str);
impl<'a> Wrapper<'a> { fn new(s: &'a str) -> Self { Self(s) } }

struct Struct2<'a> { x: i32, _p: std::marker::PhantomData<&'a ()> }
impl<'a> Struct2<'a> { fn method(&self) -> Option<()> { Some(()) } }

fn f<'a: 'a>() {}

struct StructT<'a, T> { a: &'a str, b: T }
struct Struct3<'a, 'b> { a: &'a str, b: &'b str }

fn chaining_cases<'a, 'b, T>() -> Option<()> {
let _ = Wrapper<'a>::new("hi");
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method();
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method()?;
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

f<'_>();
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

// struct with two lifetimes
let _ = Struct3<'a, 'b> { a: "hi", b: "hi" };
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

// struct with lifetime and type
let _ = StructT<'a, &str> { a: "hi", b: "hi" };
//~^ ERROR use `::<...>` instead of `<...>` to specify lifetime arguments
//~| HELP add `::`

Some(())
}

fn main() {}
Loading
Loading