Rollup merge of #151171 - issue-141436, r=davidtwco
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. Fix rust-lang/rust#141436.
This commit is contained in:
commit
1968df85fb
8 changed files with 107 additions and 34 deletions
|
|
@ -625,12 +625,12 @@ impl TokenKind {
|
|||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new(kind: TokenKind, span: Span) -> Self {
|
||||
pub const fn new(kind: TokenKind, span: Span) -> Self {
|
||||
Token { kind, span }
|
||||
}
|
||||
|
||||
/// Some token that will be thrown away later.
|
||||
pub fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
Token::new(TokenKind::Question, DUMMY_SP)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,9 +204,11 @@ impl<'a> Parser<'a> {
|
|||
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 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
// 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 @@ impl<'a> Parser<'a> {
|
|||
break;
|
||||
}
|
||||
}
|
||||
self.parsing_generics = prev;
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -175,17 +175,17 @@ pub enum Recovery {
|
|||
pub struct Parser<'a> {
|
||||
pub psess: &'a ParseSess,
|
||||
/// The current token.
|
||||
pub token: Token,
|
||||
pub token: Token = Token::dummy(),
|
||||
/// The spacing for the current token.
|
||||
token_spacing: Spacing,
|
||||
token_spacing: Spacing = Spacing::Alone,
|
||||
/// The previous token.
|
||||
pub prev_token: Token,
|
||||
pub capture_cfg: bool,
|
||||
restrictions: Restrictions,
|
||||
expected_token_types: TokenTypeSet,
|
||||
pub prev_token: Token = Token::dummy(),
|
||||
pub capture_cfg: bool = false,
|
||||
restrictions: Restrictions = Restrictions::empty(),
|
||||
expected_token_types: TokenTypeSet = TokenTypeSet::new(),
|
||||
token_cursor: TokenCursor,
|
||||
// The number of calls to `bump`, i.e. the position in the token stream.
|
||||
num_bump_calls: u32,
|
||||
num_bump_calls: u32 = 0,
|
||||
// During parsing we may sometimes need to "unglue" a glued token into two
|
||||
// or three component tokens (e.g. `>>` into `>` and `>`, or `>>=` into `>`
|
||||
// and `>` and `=`), so the parser can consume them one at a time. This
|
||||
|
|
@ -204,25 +204,27 @@ pub struct Parser<'a> {
|
|||
//
|
||||
// This value is always 0, 1, or 2. It can only reach 2 when splitting
|
||||
// `>>=` or `<<=`.
|
||||
break_last_token: u32,
|
||||
break_last_token: u32 = 0,
|
||||
/// This field is used to keep track of how many left angle brackets we have seen. This is
|
||||
/// required in order to detect extra leading left angle brackets (`<` characters) and error
|
||||
/// appropriately.
|
||||
///
|
||||
/// See the comments in the `parse_path_segment` function for more details.
|
||||
unmatched_angle_bracket_count: u16,
|
||||
angle_bracket_nesting: u16,
|
||||
unmatched_angle_bracket_count: u16 = 0,
|
||||
angle_bracket_nesting: u16 = 0,
|
||||
/// Keep track of when we're within `<...>` for proper error recovery.
|
||||
parsing_generics: bool = false,
|
||||
|
||||
last_unexpected_token_span: Option<Span>,
|
||||
last_unexpected_token_span: Option<Span> = None,
|
||||
/// If present, this `Parser` is not parsing Rust code but rather a macro call.
|
||||
subparser_name: Option<&'static str>,
|
||||
capture_state: CaptureState,
|
||||
/// This allows us to recover when the user forget to add braces around
|
||||
/// multiple statements in the closure body.
|
||||
current_closure: Option<ClosureSpans>,
|
||||
current_closure: Option<ClosureSpans> = None,
|
||||
/// Whether the parser is allowed to do recovery.
|
||||
/// This is disabled when parsing macro arguments, see #103534
|
||||
recovery: Recovery,
|
||||
recovery: Recovery = Recovery::Allowed,
|
||||
}
|
||||
|
||||
// This type is used a lot, e.g. it's cloned when matching many declarative macro rules with
|
||||
|
|
@ -351,18 +353,7 @@ impl<'a> Parser<'a> {
|
|||
) -> Self {
|
||||
let mut parser = Parser {
|
||||
psess,
|
||||
token: Token::dummy(),
|
||||
token_spacing: Spacing::Alone,
|
||||
prev_token: Token::dummy(),
|
||||
capture_cfg: false,
|
||||
restrictions: Restrictions::empty(),
|
||||
expected_token_types: TokenTypeSet::new(),
|
||||
token_cursor: TokenCursor { curr: TokenTreeCursor::new(stream), stack: Vec::new() },
|
||||
num_bump_calls: 0,
|
||||
break_last_token: 0,
|
||||
unmatched_angle_bracket_count: 0,
|
||||
angle_bracket_nesting: 0,
|
||||
last_unexpected_token_span: None,
|
||||
subparser_name,
|
||||
capture_state: CaptureState {
|
||||
capturing: Capturing::No,
|
||||
|
|
@ -370,8 +361,7 @@ impl<'a> Parser<'a> {
|
|||
inner_attr_parser_ranges: Default::default(),
|
||||
seen_attrs: IntervalSet::new(u32::MAX as usize),
|
||||
},
|
||||
current_closure: None,
|
||||
recovery: Recovery::Allowed,
|
||||
..
|
||||
};
|
||||
|
||||
// Make parser point to the first token.
|
||||
|
|
|
|||
|
|
@ -585,7 +585,7 @@ macro_rules! exp {
|
|||
pub(super) struct TokenTypeSet(u128);
|
||||
|
||||
impl TokenTypeSet {
|
||||
pub(super) fn new() -> TokenTypeSet {
|
||||
pub(super) const fn new() -> TokenTypeSet {
|
||||
TokenTypeSet(0)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1488,14 +1488,44 @@ impl<'a> Parser<'a> {
|
|||
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 @@ impl<'a> Parser<'a> {
|
|||
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),
|
||||
|
|
|
|||
10
tests/ui/parser/missing-closing-generics-bracket.fixed
Normal file
10
tests/ui/parser/missing-closing-generics-bracket.fixed
Normal file
|
|
@ -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() {}
|
||||
10
tests/ui/parser/missing-closing-generics-bracket.rs
Normal file
10
tests/ui/parser/missing-closing-generics-bracket.rs
Normal file
|
|
@ -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() {}
|
||||
13
tests/ui/parser/missing-closing-generics-bracket.stderr
Normal file
13
tests/ui/parser/missing-closing-generics-bracket.stderr
Normal file
|
|
@ -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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue