Do not recover from Trait() if generic list is unterminated

If we encounter `fn foo<T: Trait()`, the recovery logic would it as if `Trait` was intended to use the Fn-like trait syntax, but if we don't know for certain that we've parsed a full trait bound (`fn foo<T: Trait()>`), we bail from the recovery as more likely there could have been a missing closing `>` and the `(` corresponds to the start of the fn parameter list.
This commit is contained in:
Esteban Küber
2026-01-15 19:32:39 +00:00
parent 7704328ba5
commit d194795f14
6 changed files with 91 additions and 5 deletions
+11 -2
View File
@@ -204,9 +204,11 @@ pub(crate) fn recover_const_param_with_mistyped_const(
pub(super) fn parse_generic_params(&mut self) -> PResult<'a, ThinVec<ast::GenericParam>> {
let mut params = ThinVec::new();
let mut done = false;
let prev = self.parsing_generics;
self.parsing_generics = true;
while !done {
let attrs = self.parse_outer_attributes()?;
let param = self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
let param = match self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
if this.eat_keyword_noexpect(kw::SelfUpper) {
// `Self` as a generic param is invalid. Here we emit the diagnostic and continue parsing
// as if `Self` never existed.
@@ -288,7 +290,13 @@ pub(super) fn parse_generic_params(&mut self) -> PResult<'a, ThinVec<ast::Generi
}
// We just ate the comma, so no need to capture the trailing token.
Ok((param, Trailing::No, UsePreAttrPos::No))
})?;
}) {
Ok(param) => param,
Err(err) => {
self.parsing_generics = prev;
return Err(err);
}
};
if let Some(param) = param {
params.push(param);
@@ -296,6 +304,7 @@ pub(super) fn parse_generic_params(&mut self) -> PResult<'a, ThinVec<ast::Generi
break;
}
}
self.parsing_generics = prev;
Ok(params)
}
+3
View File
@@ -212,6 +212,8 @@ pub struct Parser<'a> {
/// See the comments in the `parse_path_segment` function for more details.
unmatched_angle_bracket_count: u16,
angle_bracket_nesting: u16,
/// Keep track of when we're within `<...>` for proper error recovery.
parsing_generics: bool = false,
last_unexpected_token_span: Option<Span>,
/// If present, this `Parser` is not parsing Rust code but rather a macro call.
@@ -372,6 +374,7 @@ pub fn new(
},
current_closure: None,
recovery: Recovery::Allowed,
..
};
// Make parser point to the first token.
+44 -3
View File
@@ -1488,14 +1488,44 @@ fn recover_fn_trait_with_lifetime_params(
return Ok(());
}
let snapshot = if self.parsing_generics {
// The snapshot is only relevant if we're parsing the generics of an `fn` to avoid
// incorrect recovery.
Some(self.create_snapshot_for_diagnostic())
} else {
None
};
// Parse `(T, U) -> R`.
let inputs_lo = self.token.span;
let mode =
FnParseMode { req_name: |_, _| false, context: FnContext::Free, req_body: false };
let inputs: ThinVec<_> =
self.parse_fn_params(&mode)?.into_iter().map(|input| input.ty).collect();
let params = match self.parse_fn_params(&mode) {
Ok(params) => params,
Err(err) => {
if let Some(snapshot) = snapshot {
self.restore_snapshot(snapshot);
err.cancel();
return Ok(());
} else {
return Err(err);
}
}
};
let inputs: ThinVec<_> = params.into_iter().map(|input| input.ty).collect();
let inputs_span = inputs_lo.to(self.prev_token.span);
let output = self.parse_ret_ty(AllowPlus::No, RecoverQPath::No, RecoverReturnSign::No)?;
let output = match self.parse_ret_ty(AllowPlus::No, RecoverQPath::No, RecoverReturnSign::No)
{
Ok(output) => output,
Err(err) => {
if let Some(snapshot) = snapshot {
self.restore_snapshot(snapshot);
err.cancel();
return Ok(());
} else {
return Err(err);
}
}
};
let args = ast::ParenthesizedArgs {
span: fn_path_segment.span().to(self.prev_token.span),
inputs,
@@ -1503,6 +1533,17 @@ fn recover_fn_trait_with_lifetime_params(
output,
}
.into();
if let Some(snapshot) = snapshot
&& ![token::Comma, token::Gt, token::Plus].contains(&self.token.kind)
{
// We would expect another bound or the end of type params by now. Most likely we've
// encountered a `(` *not* representing `Trait()`, but rather the start of the `fn`'s
// argument list where the generic param list wasn't properly closed.
self.restore_snapshot(snapshot);
return Ok(());
}
*fn_path_segment = ast::PathSegment {
ident: fn_path_segment.ident,
args: Some(args),
@@ -0,0 +1,10 @@
// Issue #141436
//@ run-rustfix
#![allow(dead_code)]
trait Trait<'a> {}
fn foo<T: Trait<'static>>() {}
//~^ ERROR expected one of
fn main() {}
@@ -0,0 +1,10 @@
// Issue #141436
//@ run-rustfix
#![allow(dead_code)]
trait Trait<'a> {}
fn foo<T: Trait<'static>() {}
//~^ ERROR expected one of
fn main() {}
@@ -0,0 +1,13 @@
error: expected one of `+`, `,`, `::`, `=`, or `>`, found `(`
--> $DIR/missing-closing-generics-bracket.rs:7:25
|
LL | fn foo<T: Trait<'static>() {}
| ^ expected one of `+`, `,`, `::`, `=`, or `>`
|
help: you might have meant to end the type parameters here
|
LL | fn foo<T: Trait<'static>>() {}
| +
error: aborting due to 1 previous error