diff options
Diffstat (limited to 'compiler/rustc_parse')
| -rw-r--r-- | compiler/rustc_parse/messages.ftl | 19 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/errors.rs | 31 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/attr.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/attr_wrapper.rs | 53 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/item.rs | 114 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/mod.rs | 73 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/tests.rs | 121 |
7 files changed, 283 insertions, 134 deletions
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index e4c75ac1145..02c3c87313b 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -1,9 +1,5 @@ parse_add_paren = try adding parentheses -parse_ambiguous_missing_keyword_for_item_definition = missing `fn` or `struct` for function or struct definition - .suggestion = if you meant to call a macro, try - .help = if you meant to call a macro, remove the `pub` and add a trailing `!` after the identifier - parse_ambiguous_range_pattern = the range pattern here has ambiguous interpretation .suggestion = add parentheses to clarify the precedence @@ -528,14 +524,23 @@ parse_missing_comma_after_match_arm = expected `,` following `match` arm parse_missing_const_type = missing type for `{$kind}` item .suggestion = provide a type for the item +parse_missing_enum_for_enum_definition = missing `enum` for enum definition + .suggestion = add `enum` here to parse `{$ident}` as an enum + +parse_missing_enum_or_struct_for_item_definition = missing `enum` or `struct` for enum or struct definition + parse_missing_expression_in_for_loop = missing expression to iterate on in `for` loop .suggestion = try adding an expression to the `for` loop parse_missing_fn_for_function_definition = missing `fn` for function definition - .suggestion = add `fn` here to parse `{$ident}` as a public function + .suggestion = add `fn` here to parse `{$ident}` as a function parse_missing_fn_for_method_definition = missing `fn` for method definition - .suggestion = add `fn` here to parse `{$ident}` as a public method + .suggestion = add `fn` here to parse `{$ident}` as a method + +parse_missing_fn_or_struct_for_item_definition = missing `fn` or `struct` for function or struct definition + .suggestion = if you meant to call a macro, try + .help = if you meant to call a macro, remove the `pub` and add a trailing `!` after the identifier parse_missing_fn_params = missing parameters for function definition .suggestion = add a parameter list @@ -555,7 +560,7 @@ parse_missing_semicolon_before_array = expected `;`, found `[` .suggestion = consider adding `;` here parse_missing_struct_for_struct_definition = missing `struct` for struct definition - .suggestion = add `struct` here to parse `{$ident}` as a public struct + .suggestion = add `struct` here to parse `{$ident}` as a struct parse_missing_trait_in_trait_impl = missing trait in a trait impl .suggestion_add_trait = add a trait here diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 6894f470d88..3ae9b6dad99 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -1612,28 +1612,44 @@ pub(crate) struct DefaultNotFollowedByItem { #[derive(Diagnostic)] pub(crate) enum MissingKeywordForItemDefinition { + #[diag(parse_missing_enum_for_enum_definition)] + Enum { + #[primary_span] + span: Span, + #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "enum ")] + insert_span: Span, + ident: Ident, + }, + #[diag(parse_missing_enum_or_struct_for_item_definition)] + EnumOrStruct { + #[primary_span] + span: Span, + }, #[diag(parse_missing_struct_for_struct_definition)] Struct { #[primary_span] - #[suggestion(style = "short", applicability = "maybe-incorrect", code = " struct ")] span: Span, + #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "struct ")] + insert_span: Span, ident: Ident, }, #[diag(parse_missing_fn_for_function_definition)] Function { #[primary_span] - #[suggestion(style = "short", applicability = "maybe-incorrect", code = " fn ")] span: Span, + #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "fn ")] + insert_span: Span, ident: Ident, }, #[diag(parse_missing_fn_for_method_definition)] Method { #[primary_span] - #[suggestion(style = "short", applicability = "maybe-incorrect", code = " fn ")] span: Span, + #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "fn ")] + insert_span: Span, ident: Ident, }, - #[diag(parse_ambiguous_missing_keyword_for_item_definition)] + #[diag(parse_missing_fn_or_struct_for_item_definition)] Ambiguous { #[primary_span] span: Span, @@ -1644,7 +1660,12 @@ pub(crate) enum MissingKeywordForItemDefinition { #[derive(Subdiagnostic)] pub(crate) enum AmbiguousMissingKwForItemSub { - #[suggestion(parse_suggestion, applicability = "maybe-incorrect", code = "{snippet}!")] + #[suggestion( + parse_suggestion, + style = "verbose", + applicability = "maybe-incorrect", + code = "{snippet}!" + )] SuggestMacro { #[primary_span] span: Span, diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 58fef9b6c45..a8fe35f45b3 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -282,7 +282,7 @@ impl<'a> Parser<'a> { pub fn parse_inner_attributes(&mut self) -> PResult<'a, ast::AttrVec> { let mut attrs = ast::AttrVec::new(); loop { - let start_pos: u32 = self.num_bump_calls.try_into().unwrap(); + let start_pos = self.num_bump_calls; // Only try to parse if it is an inner attribute (has `!`). let attr = if self.check(&token::Pound) && self.look_ahead(1, |t| t == &token::Not) { Some(self.parse_attribute(InnerAttrPolicy::Permitted)?) @@ -303,7 +303,7 @@ impl<'a> Parser<'a> { None }; if let Some(attr) = attr { - let end_pos: u32 = self.num_bump_calls.try_into().unwrap(); + let end_pos = self.num_bump_calls; // If we are currently capturing tokens, mark the location of this inner attribute. // If capturing ends up creating a `LazyAttrTokenStream`, we will include // this replace range with it, removing the inner attribute from the final @@ -313,7 +313,7 @@ impl<'a> Parser<'a> { // corresponding macro). let range = start_pos..end_pos; if let Capturing::Yes = self.capture_state.capturing { - self.capture_state.inner_attr_ranges.insert(attr.id, (range, vec![])); + self.capture_state.inner_attr_ranges.insert(attr.id, (range, None)); } attrs.push(attr); } else { diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs index 13a647adfe3..38f18022e3c 100644 --- a/compiler/rustc_parse/src/parser/attr_wrapper.rs +++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs @@ -1,6 +1,6 @@ use super::{Capturing, FlatToken, ForceCollect, Parser, ReplaceRange, TokenCursor, TrailingToken}; use rustc_ast::token::{self, Delimiter, Token, TokenKind}; -use rustc_ast::tokenstream::{AttrTokenStream, AttrTokenTree, AttributesData, DelimSpacing}; +use rustc_ast::tokenstream::{AttrTokenStream, AttrTokenTree, AttrsTarget, DelimSpacing}; use rustc_ast::tokenstream::{DelimSpan, LazyAttrTokenStream, Spacing, ToAttrTokenStream}; use rustc_ast::{self as ast}; use rustc_ast::{AttrVec, Attribute, HasAttrs, HasTokens}; @@ -8,7 +8,6 @@ use rustc_errors::PResult; use rustc_session::parse::ParseSess; use rustc_span::{sym, Span, DUMMY_SP}; -use std::ops::Range; use std::{iter, mem}; /// A wrapper type to ensure that the parser handles outer attributes correctly. @@ -88,7 +87,6 @@ fn has_cfg_or_cfg_attr(attrs: &[Attribute]) -> bool { // // This also makes `Parser` very cheap to clone, since // there is no intermediate collection buffer to clone. -#[derive(Clone)] struct LazyAttrTokenStreamImpl { start_token: (Token, Spacing), cursor_snapshot: TokenCursor, @@ -146,24 +144,23 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl { // start position, we ensure that any replace range which encloses // another replace range will capture the *replaced* tokens for the inner // range, not the original tokens. - for (range, new_tokens) in replace_ranges.into_iter().rev() { + for (range, target) in replace_ranges.into_iter().rev() { assert!(!range.is_empty(), "Cannot replace an empty range: {range:?}"); - // Replace ranges are only allowed to decrease the number of tokens. - assert!( - range.len() >= new_tokens.len(), - "Range {range:?} has greater len than {new_tokens:?}" - ); - - // Replace any removed tokens with `FlatToken::Empty`. - // This keeps the total length of `tokens` constant throughout the - // replacement process, allowing us to use all of the `ReplaceRanges` entries - // without adjusting indices. - let filler = iter::repeat((FlatToken::Empty, Spacing::Alone)) - .take(range.len() - new_tokens.len()); + // Replace the tokens in range with zero or one `FlatToken::AttrsTarget`s, plus + // enough `FlatToken::Empty`s to fill up the rest of the range. This keeps the + // total length of `tokens` constant throughout the replacement process, allowing + // us to use all of the `ReplaceRanges` entries without adjusting indices. + let target_len = target.is_some() as usize; tokens.splice( (range.start as usize)..(range.end as usize), - new_tokens.into_iter().chain(filler), + target + .into_iter() + .map(|target| (FlatToken::AttrsTarget(target), Spacing::Alone)) + .chain( + iter::repeat((FlatToken::Empty, Spacing::Alone)) + .take(range.len() - target_len), + ), ); } make_attr_token_stream(tokens.into_iter(), self.break_last_token) @@ -316,7 +313,7 @@ impl<'a> Parser<'a> { .iter() .cloned() .chain(inner_attr_replace_ranges.iter().cloned()) - .map(|(range, tokens)| ((range.start - start_pos)..(range.end - start_pos), tokens)) + .map(|(range, data)| ((range.start - start_pos)..(range.end - start_pos), data)) .collect() }; @@ -346,18 +343,14 @@ impl<'a> Parser<'a> { && matches!(self.capture_state.capturing, Capturing::Yes) && has_cfg_or_cfg_attr(final_attrs) { - let attr_data = AttributesData { attrs: final_attrs.iter().cloned().collect(), tokens }; + assert!(!self.break_last_token, "Should not have unglued last token with cfg attr"); - // Replace the entire AST node that we just parsed, including attributes, - // with a `FlatToken::AttrTarget`. If this AST node is inside an item - // that has `#[derive]`, then this will allow us to cfg-expand this - // AST node. + // Replace the entire AST node that we just parsed, including attributes, with + // `target`. If this AST node is inside an item that has `#[derive]`, then this will + // allow us to cfg-expand this AST node. let start_pos = if has_outer_attrs { attrs.start_pos } else { start_pos }; - let new_tokens = vec![(FlatToken::AttrTarget(attr_data), Spacing::Alone)]; - - assert!(!self.break_last_token, "Should not have unglued last token with cfg attr"); - let range: Range<u32> = (start_pos.try_into().unwrap())..(end_pos.try_into().unwrap()); - self.capture_state.replace_ranges.push((range, new_tokens)); + let target = AttrsTarget { attrs: final_attrs.iter().cloned().collect(), tokens }; + self.capture_state.replace_ranges.push((start_pos..end_pos, Some(target))); self.capture_state.replace_ranges.extend(inner_attr_replace_ranges); } @@ -419,11 +412,11 @@ fn make_attr_token_stream( .expect("Bottom token frame is missing!") .inner .push(AttrTokenTree::Token(token, spacing)), - FlatToken::AttrTarget(data) => stack + FlatToken::AttrsTarget(target) => stack .last_mut() .expect("Bottom token frame is missing!") .inner - .push(AttrTokenTree::Attributes(data)), + .push(AttrTokenTree::AttrsTarget(target)), FlatToken::Empty => {} } token_and_spacing = iter.next(); diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index abb6b51cebd..d2fea5583b8 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -239,6 +239,7 @@ impl<'a> Parser<'a> { self.recover_const_impl(const_span, attrs, def_())? } else { self.recover_const_mut(const_span); + self.recover_missing_kw_before_item()?; let (ident, generics, ty, expr) = self.parse_const_item()?; ( ident, @@ -311,6 +312,9 @@ impl<'a> Parser<'a> { Case::Insensitive, ); } else if macros_allowed && self.check_path() { + if self.isnt_macro_invocation() { + self.recover_missing_kw_before_item()?; + } // MACRO INVOCATION ITEM (Ident::empty(), ItemKind::MacCall(P(self.parse_item_macro(vis)?))) } else { @@ -374,25 +378,25 @@ impl<'a> Parser<'a> { self.check_ident() && self.look_ahead(1, |t| *t != token::Not && *t != token::PathSep) } - /// Recover on encountering a struct or method definition where the user - /// forgot to add the `struct` or `fn` keyword after writing `pub`: `pub S {}`. + /// Recover on encountering a struct, enum, or method definition where the user + /// forgot to add the `struct`, `enum`, or `fn` keyword fn recover_missing_kw_before_item(&mut self) -> PResult<'a, ()> { - // Space between `pub` keyword and the identifier - // - // pub S {} - // ^^^ `sp` points here - let sp = self.prev_token.span.between(self.token.span); - let full_sp = self.prev_token.span.to(self.token.span); - let ident_sp = self.token.span; - - let ident = if self.look_ahead(1, |t| { - [ - token::Lt, - token::OpenDelim(Delimiter::Brace), - token::OpenDelim(Delimiter::Parenthesis), - ] - .contains(&t.kind) - }) { + let is_pub = self.prev_token.is_keyword(kw::Pub); + let is_const = self.prev_token.is_keyword(kw::Const); + let ident_span = self.token.span; + let span = if is_pub { self.prev_token.span.to(ident_span) } else { ident_span }; + let insert_span = ident_span.shrink_to_lo(); + + let ident = if self.token.is_ident() + && (!is_const || self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Parenthesis))) + && self.look_ahead(1, |t| { + [ + token::Lt, + token::OpenDelim(Delimiter::Brace), + token::OpenDelim(Delimiter::Parenthesis), + ] + .contains(&t.kind) + }) { self.parse_ident().unwrap() } else { return Ok(()); @@ -406,46 +410,56 @@ impl<'a> Parser<'a> { } let err = if self.check(&token::OpenDelim(Delimiter::Brace)) { - // possible public struct definition where `struct` was forgotten - Some(errors::MissingKeywordForItemDefinition::Struct { span: sp, ident }) + // possible struct or enum definition where `struct` or `enum` was forgotten + if self.look_ahead(1, |t| *t == token::CloseDelim(Delimiter::Brace)) { + // `S {}` could be unit enum or struct + Some(errors::MissingKeywordForItemDefinition::EnumOrStruct { span }) + } else if self.look_ahead(2, |t| *t == token::Colon) + || self.look_ahead(3, |t| *t == token::Colon) + { + // `S { f:` or `S { pub f:` + Some(errors::MissingKeywordForItemDefinition::Struct { span, insert_span, ident }) + } else { + Some(errors::MissingKeywordForItemDefinition::Enum { span, insert_span, ident }) + } } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { - // possible public function or tuple struct definition where `fn`/`struct` was - // forgotten + // possible function or tuple struct definition where `fn` or `struct` was forgotten self.bump(); // `(` let is_method = self.recover_self_param(); self.consume_block(Delimiter::Parenthesis, ConsumeClosingDelim::Yes); - let err = - if self.check(&token::RArrow) || self.check(&token::OpenDelim(Delimiter::Brace)) { - self.eat_to_tokens(&[&token::OpenDelim(Delimiter::Brace)]); - self.bump(); // `{` - self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes); - if is_method { - errors::MissingKeywordForItemDefinition::Method { span: sp, ident } - } else { - errors::MissingKeywordForItemDefinition::Function { span: sp, ident } - } - } else if self.check(&token::Semi) { - errors::MissingKeywordForItemDefinition::Struct { span: sp, ident } + let err = if self.check(&token::RArrow) + || self.check(&token::OpenDelim(Delimiter::Brace)) + { + self.eat_to_tokens(&[&token::OpenDelim(Delimiter::Brace)]); + self.bump(); // `{` + self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes); + if is_method { + errors::MissingKeywordForItemDefinition::Method { span, insert_span, ident } } else { - errors::MissingKeywordForItemDefinition::Ambiguous { - span: sp, - subdiag: if found_generics { - None - } else if let Ok(snippet) = self.span_to_snippet(ident_sp) { - Some(errors::AmbiguousMissingKwForItemSub::SuggestMacro { - span: full_sp, - snippet, - }) - } else { - Some(errors::AmbiguousMissingKwForItemSub::HelpMacro) - }, - } - }; + errors::MissingKeywordForItemDefinition::Function { span, insert_span, ident } + } + } else if is_pub && self.check(&token::Semi) { + errors::MissingKeywordForItemDefinition::Struct { span, insert_span, ident } + } else { + errors::MissingKeywordForItemDefinition::Ambiguous { + span, + subdiag: if found_generics { + None + } else if let Ok(snippet) = self.span_to_snippet(ident_span) { + Some(errors::AmbiguousMissingKwForItemSub::SuggestMacro { + span: ident_span, + snippet, + }) + } else { + Some(errors::AmbiguousMissingKwForItemSub::HelpMacro) + }, + } + }; Some(err) } else if found_generics { - Some(errors::MissingKeywordForItemDefinition::Ambiguous { span: sp, subdiag: None }) + Some(errors::MissingKeywordForItemDefinition::Ambiguous { span, subdiag: None }) } else { None }; @@ -796,7 +810,7 @@ impl<'a> Parser<'a> { self.dcx().struct_span_err(non_item_span, "non-item in item list"); self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes); if is_let { - err.span_suggestion( + err.span_suggestion_verbose( non_item_span, "consider using `const` instead of `let` for associated const", "const", diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 5f16a3e1f37..ef9b3aabc61 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -20,7 +20,7 @@ use path::PathStyle; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, IdentIsRaw, Nonterminal, Token, TokenKind}; -use rustc_ast::tokenstream::{AttributesData, DelimSpacing, DelimSpan, Spacing}; +use rustc_ast::tokenstream::{AttrsTarget, DelimSpacing, DelimSpan, Spacing}; use rustc_ast::tokenstream::{TokenStream, TokenTree, TokenTreeCursor}; use rustc_ast::util::case::Case; use rustc_ast::{ @@ -203,13 +203,13 @@ struct ClosureSpans { } /// Indicates a range of tokens that should be replaced by -/// the tokens in the provided vector. This is used in two +/// the tokens in the provided `AttrsTarget`. This is used in two /// places during token collection: /// /// 1. During the parsing of an AST node that may have a `#[derive]` /// attribute, we parse a nested AST node that has `#[cfg]` or `#[cfg_attr]` /// In this case, we use a `ReplaceRange` to replace the entire inner AST node -/// with `FlatToken::AttrTarget`, allowing us to perform eager cfg-expansion +/// with `FlatToken::AttrsTarget`, allowing us to perform eager cfg-expansion /// on an `AttrTokenStream`. /// /// 2. When we parse an inner attribute while collecting tokens. We @@ -219,7 +219,7 @@ struct ClosureSpans { /// the first macro inner attribute to invoke a proc-macro). /// When create a `TokenStream`, the inner attributes get inserted /// into the proper place in the token stream. -type ReplaceRange = (Range<u32>, Vec<(FlatToken, Spacing)>); +type ReplaceRange = (Range<u32>, Option<AttrsTarget>); /// Controls how we capture tokens. Capturing can be expensive, /// so we try to avoid performing capturing in cases where @@ -1118,41 +1118,37 @@ impl<'a> Parser<'a> { return looker(&self.token); } - if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last() - && delim != Delimiter::Invisible - { - // We are not in the outermost token stream, and the token stream - // we are in has non-skipped delimiters. Look for skipped - // delimiters in the lookahead range. - let tree_cursor = &self.token_cursor.tree_cursor; - let all_normal = (0..dist).all(|i| { - let token = tree_cursor.look_ahead(i); - !matches!(token, Some(TokenTree::Delimited(.., Delimiter::Invisible, _))) - }); - if all_normal { - // There were no skipped delimiters. Do lookahead by plain indexing. - return match tree_cursor.look_ahead(dist - 1) { - Some(tree) => { - // Indexing stayed within the current token stream. - match tree { - TokenTree::Token(token, _) => looker(token), - TokenTree::Delimited(dspan, _, delim, _) => { - looker(&Token::new(token::OpenDelim(*delim), dspan.open)) - } + // Typically around 98% of the `dist > 0` cases have `dist == 1`, so we + // have a fast special case for that. + if dist == 1 { + // The index is zero because the tree cursor's index always points + // to the next token to be gotten. + match self.token_cursor.tree_cursor.look_ahead(0) { + Some(tree) => { + // Indexing stayed within the current token tree. + return match tree { + TokenTree::Token(token, _) => looker(token), + TokenTree::Delimited(dspan, _, delim, _) => { + looker(&Token::new(token::OpenDelim(*delim), dspan.open)) } + }; + } + None => { + // The tree cursor lookahead went (one) past the end of the + // current token tree. Try to return a close delimiter. + if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last() + && delim != Delimiter::Invisible + { + // We are not in the outermost token stream, so we have + // delimiters. Also, those delimiters are not skipped. + return looker(&Token::new(token::CloseDelim(delim), span.close)); } - None => { - // Indexing went past the end of the current token - // stream. Use the close delimiter, no matter how far - // ahead `dist` went. - looker(&Token::new(token::CloseDelim(delim), span.close)) - } - }; + } } } - // We are in a more complex case. Just clone the token cursor and use - // `next`, skipping delimiters as necessary. Slow but simple. + // Just clone the token cursor and use `next`, skipping delimiters as + // necessary. Slow but simple. let mut cursor = self.token_cursor.clone(); let mut i = 0; let mut token = Token::dummy(); @@ -1608,11 +1604,10 @@ enum FlatToken { /// A token - this holds both delimiter (e.g. '{' and '}') /// and non-delimiter tokens Token(Token), - /// Holds the `AttributesData` for an AST node. The - /// `AttributesData` is inserted directly into the - /// constructed `AttrTokenStream` as - /// an `AttrTokenTree::Attributes`. - AttrTarget(AttributesData), + /// Holds the `AttrsTarget` for an AST node. The `AttrsTarget` is inserted + /// directly into the constructed `AttrTokenStream` as an + /// `AttrTokenTree::AttrsTarget`. + AttrsTarget(AttrsTarget), /// A special 'empty' token that is ignored during the conversion /// to an `AttrTokenStream`. This is used to simplify the /// handling of replace ranges. diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs index 42392ad2163..5b2d119e42b 100644 --- a/compiler/rustc_parse/src/parser/tests.rs +++ b/compiler/rustc_parse/src/parser/tests.rs @@ -1376,6 +1376,127 @@ fn ttdelim_span() { }); } +// Uses a macro rather than a function so that failure messages mention the +// correct line in the test function. +macro_rules! look { + ($p:ident, $dist:literal, $kind:expr) => { + $p.look_ahead($dist, |tok| assert_eq!($kind, tok.kind)); + }; +} + +#[test] +fn look_ahead() { + create_default_session_globals_then(|| { + let sym_f = Symbol::intern("f"); + let sym_x = Symbol::intern("x"); + #[allow(non_snake_case)] + let sym_S = Symbol::intern("S"); + let raw_no = IdentIsRaw::No; + + let psess = psess(); + let mut p = string_to_parser(&psess, "fn f(x: u32) { x } struct S;".to_string()); + + // Current position is the `fn`. + look!(p, 0, token::Ident(kw::Fn, raw_no)); + look!(p, 1, token::Ident(sym_f, raw_no)); + look!(p, 2, token::OpenDelim(Delimiter::Parenthesis)); + look!(p, 3, token::Ident(sym_x, raw_no)); + look!(p, 4, token::Colon); + look!(p, 5, token::Ident(sym::u32, raw_no)); + look!(p, 6, token::CloseDelim(Delimiter::Parenthesis)); + look!(p, 7, token::OpenDelim(Delimiter::Brace)); + look!(p, 8, token::Ident(sym_x, raw_no)); + look!(p, 9, token::CloseDelim(Delimiter::Brace)); + look!(p, 10, token::Ident(kw::Struct, raw_no)); + look!(p, 11, token::Ident(sym_S, raw_no)); + look!(p, 12, token::Semi); + // Any lookahead past the end of the token stream returns `Eof`. + look!(p, 13, token::Eof); + look!(p, 14, token::Eof); + look!(p, 15, token::Eof); + look!(p, 100, token::Eof); + + // Move forward to the first `x`. + for _ in 0..3 { + p.bump(); + } + look!(p, 0, token::Ident(sym_x, raw_no)); + look!(p, 1, token::Colon); + look!(p, 2, token::Ident(sym::u32, raw_no)); + look!(p, 3, token::CloseDelim(Delimiter::Parenthesis)); + look!(p, 4, token::OpenDelim(Delimiter::Brace)); + look!(p, 5, token::Ident(sym_x, raw_no)); + look!(p, 6, token::CloseDelim(Delimiter::Brace)); + look!(p, 7, token::Ident(kw::Struct, raw_no)); + look!(p, 8, token::Ident(sym_S, raw_no)); + look!(p, 9, token::Semi); + look!(p, 10, token::Eof); + look!(p, 11, token::Eof); + look!(p, 100, token::Eof); + + // Move forward to the `;`. + for _ in 0..9 { + p.bump(); + } + look!(p, 0, token::Semi); + // Any lookahead past the end of the token stream returns `Eof`. + look!(p, 1, token::Eof); + look!(p, 100, token::Eof); + + // Move one past the `;`, i.e. past the end of the token stream. + p.bump(); + look!(p, 0, token::Eof); + look!(p, 1, token::Eof); + look!(p, 100, token::Eof); + + // Bumping after Eof is idempotent. + p.bump(); + look!(p, 0, token::Eof); + look!(p, 1, token::Eof); + look!(p, 100, token::Eof); + }); +} + +/// There used to be some buggy behaviour when using `look_ahead` not within +/// the outermost token stream, which this test covers. +#[test] +fn look_ahead_non_outermost_stream() { + create_default_session_globals_then(|| { + let sym_f = Symbol::intern("f"); + let sym_x = Symbol::intern("x"); + #[allow(non_snake_case)] + let sym_S = Symbol::intern("S"); + let raw_no = IdentIsRaw::No; + + let psess = psess(); + let mut p = string_to_parser(&psess, "mod m { fn f(x: u32) { x } struct S; }".to_string()); + + // Move forward to the `fn`, which is not within the outermost token + // stream (because it's inside the `mod { ... }`). + for _ in 0..3 { + p.bump(); + } + look!(p, 0, token::Ident(kw::Fn, raw_no)); + look!(p, 1, token::Ident(sym_f, raw_no)); + look!(p, 2, token::OpenDelim(Delimiter::Parenthesis)); + look!(p, 3, token::Ident(sym_x, raw_no)); + look!(p, 4, token::Colon); + look!(p, 5, token::Ident(sym::u32, raw_no)); + look!(p, 6, token::CloseDelim(Delimiter::Parenthesis)); + look!(p, 7, token::OpenDelim(Delimiter::Brace)); + look!(p, 8, token::Ident(sym_x, raw_no)); + look!(p, 9, token::CloseDelim(Delimiter::Brace)); + look!(p, 10, token::Ident(kw::Struct, raw_no)); + look!(p, 11, token::Ident(sym_S, raw_no)); + look!(p, 12, token::Semi); + look!(p, 13, token::CloseDelim(Delimiter::Brace)); + // Any lookahead past the end of the token stream returns `Eof`. + look!(p, 14, token::Eof); + look!(p, 15, token::Eof); + look!(p, 100, token::Eof); + }); +} + // This tests that when parsing a string (rather than a file) we don't try // and read in a file for a module declaration and just parse a stub. // See `recurse_into_file_modules` in the parser. |
