| use std::mem::take; | |
| use std::ops::{Deref, DerefMut}; | |
| use ast::token::IdentIsRaw; | |
| use rustc_ast::token::{self, Lit, LitKind, Token, TokenKind}; | |
| use rustc_ast::util::parser::AssocOp; | |
| use rustc_ast::{ | |
| self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingMode, | |
| Block, BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind, | |
| MgcaDisambiguation, Param, Pat, PatKind, Path, PathSegment, QSelf, Recovered, Ty, TyKind, | |
| }; | |
| use rustc_ast_pretty::pprust; | |
| use rustc_data_structures::fx::FxHashSet; | |
| use rustc_errors::{ | |
| Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, PResult, Subdiagnostic, Suggestions, msg, | |
| pluralize, | |
| }; | |
| use rustc_session::errors::ExprParenthesesNeeded; | |
| use rustc_span::symbol::used_keywords; | |
| use rustc_span::{BytePos, DUMMY_SP, Ident, Span, SpanSnippetError, Spanned, Symbol, kw, sym}; | |
| use thin_vec::{ThinVec, thin_vec}; | |
| use tracing::{debug, trace}; | |
| use super::pat::Expected; | |
| use super::{ | |
| BlockMode, CommaRecoveryMode, ExpTokenPair, Parser, PathStyle, Restrictions, SemiColonMode, | |
| SeqSep, TokenType, | |
| }; | |
| use crate::errors::{ | |
| AddParen, AmbiguousPlus, AsyncMoveBlockIn2015, AsyncUseBlockIn2015, AttributeOnParamType, | |
| AwaitSuggestion, BadQPathStage2, BadTypePlus, BadTypePlusSub, ColonAsSemi, | |
| ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg, | |
| DocCommentDoesNotDocumentAnything, DocCommentOnParamType, DoubleColonInBound, | |
| ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, GenericParamsWithoutAngleBrackets, | |
| GenericParamsWithoutAngleBracketsSugg, HelpIdentifierStartsWithNumber, HelpUseLatestEdition, | |
| InInTypo, IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, IncorrectUseOfUse, | |
| MisspelledKw, PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, | |
| SelfParamNotFirst, StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, | |
| SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma, TernaryOperator, | |
| TernaryOperatorSuggestion, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, | |
| UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType, | |
| }; | |
| use crate::exp; | |
| use crate::parser::FnContext; | |
| use crate::parser::attr::InnerAttrPolicy; | |
| use crate::parser::item::IsDotDotDot; | |
| /// Creates a placeholder argument. | |
| pub(super) fn dummy_arg(ident: Ident, guar: ErrorGuaranteed) -> Param { | |
| let pat = Box::new(Pat { | |
| id: ast::DUMMY_NODE_ID, | |
| kind: PatKind::Ident(BindingMode::NONE, ident, None), | |
| span: ident.span, | |
| tokens: None, | |
| }); | |
| let ty = Ty { kind: TyKind::Err(guar), span: ident.span, id: ast::DUMMY_NODE_ID, tokens: None }; | |
| Param { | |
| attrs: AttrVec::default(), | |
| id: ast::DUMMY_NODE_ID, | |
| pat, | |
| span: ident.span, | |
| ty: Box::new(ty), | |
| is_placeholder: false, | |
| } | |
| } | |
| pub(super) trait RecoverQPath: Sized + 'static { | |
| const PATH_STYLE: PathStyle = PathStyle::Expr; | |
| fn to_ty(&self) -> Option<Box<Ty>>; | |
| fn recovered(qself: Option<Box<QSelf>>, path: ast::Path) -> Self; | |
| } | |
| impl<T: RecoverQPath> RecoverQPath for Box<T> { | |
| const PATH_STYLE: PathStyle = T::PATH_STYLE; | |
| fn to_ty(&self) -> Option<Box<Ty>> { | |
| T::to_ty(self) | |
| } | |
| fn recovered(qself: Option<Box<QSelf>>, path: ast::Path) -> Self { | |
| Box::new(T::recovered(qself, path)) | |
| } | |
| } | |
| impl RecoverQPath for Ty { | |
| const PATH_STYLE: PathStyle = PathStyle::Type; | |
| fn to_ty(&self) -> Option<Box<Ty>> { | |
| Some(Box::new(self.clone())) | |
| } | |
| fn recovered(qself: Option<Box<QSelf>>, path: ast::Path) -> Self { | |
| Self { | |
| span: path.span, | |
| kind: TyKind::Path(qself, path), | |
| id: ast::DUMMY_NODE_ID, | |
| tokens: None, | |
| } | |
| } | |
| } | |
| impl RecoverQPath for Pat { | |
| const PATH_STYLE: PathStyle = PathStyle::Pat; | |
| fn to_ty(&self) -> Option<Box<Ty>> { | |
| self.to_ty() | |
| } | |
| fn recovered(qself: Option<Box<QSelf>>, path: ast::Path) -> Self { | |
| Self { | |
| span: path.span, | |
| kind: PatKind::Path(qself, path), | |
| id: ast::DUMMY_NODE_ID, | |
| tokens: None, | |
| } | |
| } | |
| } | |
| impl RecoverQPath for Expr { | |
| fn to_ty(&self) -> Option<Box<Ty>> { | |
| self.to_ty() | |
| } | |
| fn recovered(qself: Option<Box<QSelf>>, path: ast::Path) -> Self { | |
| Self { | |
| span: path.span, | |
| kind: ExprKind::Path(qself, path), | |
| attrs: AttrVec::new(), | |
| id: ast::DUMMY_NODE_ID, | |
| tokens: None, | |
| } | |
| } | |
| } | |
| /// Control whether the closing delimiter should be consumed when calling `Parser::consume_block`. | |
| pub(crate) enum ConsumeClosingDelim { | |
| Yes, | |
| No, | |
| } | |
| #[derive(Clone, Copy)] | |
| pub enum AttemptLocalParseRecovery { | |
| Yes, | |
| No, | |
| } | |
| impl AttemptLocalParseRecovery { | |
| pub(super) fn yes(&self) -> bool { | |
| match self { | |
| AttemptLocalParseRecovery::Yes => true, | |
| AttemptLocalParseRecovery::No => false, | |
| } | |
| } | |
| pub(super) fn no(&self) -> bool { | |
| match self { | |
| AttemptLocalParseRecovery::Yes => false, | |
| AttemptLocalParseRecovery::No => true, | |
| } | |
| } | |
| } | |
| /// Information for emitting suggestions and recovering from | |
| /// C-style `i++`, `--i`, etc. | |
| #[derive(Debug, Copy, Clone)] | |
| struct IncDecRecovery { | |
| /// Is this increment/decrement its own statement? | |
| standalone: IsStandalone, | |
| /// Is this an increment or decrement? | |
| op: IncOrDec, | |
| /// Is this pre- or postfix? | |
| fixity: UnaryFixity, | |
| } | |
| /// Is an increment or decrement expression its own statement? | |
| #[derive(Debug, Copy, Clone)] | |
| enum IsStandalone { | |
| /// It's standalone, i.e., its own statement. | |
| Standalone, | |
| /// It's a subexpression, i.e., *not* standalone. | |
| Subexpr, | |
| } | |
| #[derive(Debug, Copy, Clone, PartialEq, Eq)] | |
| enum IncOrDec { | |
| Inc, | |
| Dec, | |
| } | |
| #[derive(Debug, Copy, Clone, PartialEq, Eq)] | |
| enum UnaryFixity { | |
| Pre, | |
| Post, | |
| } | |
| impl IncOrDec { | |
| fn chr(&self) -> char { | |
| match self { | |
| Self::Inc => '+', | |
| Self::Dec => '-', | |
| } | |
| } | |
| fn name(&self) -> &'static str { | |
| match self { | |
| Self::Inc => "increment", | |
| Self::Dec => "decrement", | |
| } | |
| } | |
| } | |
| impl std::fmt::Display for UnaryFixity { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| match self { | |
| Self::Pre => write!(f, "prefix"), | |
| Self::Post => write!(f, "postfix"), | |
| } | |
| } | |
| } | |
| /// Checks if the given `lookup` identifier is similar to any keyword symbol in `candidates`. | |
| /// | |
| /// This is a specialized version of [`Symbol::find_similar`] that constructs an error when a | |
| /// candidate is found. | |
| fn find_similar_kw(lookup: Ident, candidates: &[Symbol]) -> Option<MisspelledKw> { | |
| lookup.name.find_similar(candidates).map(|(similar_kw, is_incorrect_case)| MisspelledKw { | |
| similar_kw: similar_kw.to_string(), | |
| is_incorrect_case, | |
| span: lookup.span, | |
| }) | |
| } | |
| struct MultiSugg { | |
| msg: String, | |
| patches: Vec<(Span, String)>, | |
| applicability: Applicability, | |
| } | |
| impl MultiSugg { | |
| fn emit(self, err: &mut Diag<'_>) { | |
| err.multipart_suggestion(self.msg, self.patches, self.applicability); | |
| } | |
| fn emit_verbose(self, err: &mut Diag<'_>) { | |
| err.multipart_suggestion(self.msg, self.patches, self.applicability); | |
| } | |
| } | |
| /// SnapshotParser is used to create a snapshot of the parser | |
| /// without causing duplicate errors being emitted when the `Parser` | |
| /// is dropped. | |
| pub struct SnapshotParser<'a> { | |
| parser: Parser<'a>, | |
| } | |
| impl<'a> Deref for SnapshotParser<'a> { | |
| type Target = Parser<'a>; | |
| fn deref(&self) -> &Self::Target { | |
| &self.parser | |
| } | |
| } | |
| impl<'a> DerefMut for SnapshotParser<'a> { | |
| fn deref_mut(&mut self) -> &mut Self::Target { | |
| &mut self.parser | |
| } | |
| } | |
| impl<'a> Parser<'a> { | |
| pub fn dcx(&self) -> DiagCtxtHandle<'a> { | |
| self.psess.dcx() | |
| } | |
| /// Replace `self` with `snapshot.parser`. | |
| pub fn restore_snapshot(&mut self, snapshot: SnapshotParser<'a>) { | |
| *self = snapshot.parser; | |
| } | |
| /// Create a snapshot of the `Parser`. | |
| pub fn create_snapshot_for_diagnostic(&self) -> SnapshotParser<'a> { | |
| let snapshot = self.clone(); | |
| SnapshotParser { parser: snapshot } | |
| } | |
| pub(super) fn span_to_snippet(&self, span: Span) -> Result<String, SpanSnippetError> { | |
| self.psess.source_map().span_to_snippet(span) | |
| } | |
| /// Emits an error with suggestions if an identifier was expected but not found. | |
| /// | |
| /// Returns a possibly recovered identifier. | |
| pub(super) fn expected_ident_found( | |
| &mut self, | |
| recover: bool, | |
| ) -> PResult<'a, (Ident, IdentIsRaw)> { | |
| let valid_follow = &[ | |
| TokenKind::Eq, | |
| TokenKind::Colon, | |
| TokenKind::Comma, | |
| TokenKind::Semi, | |
| TokenKind::PathSep, | |
| TokenKind::OpenBrace, | |
| TokenKind::OpenParen, | |
| TokenKind::CloseBrace, | |
| TokenKind::CloseParen, | |
| ]; | |
| if let TokenKind::DocComment(..) = self.prev_token.kind | |
| && valid_follow.contains(&self.token.kind) | |
| { | |
| let err = self.dcx().create_err(DocCommentDoesNotDocumentAnything { | |
| span: self.prev_token.span, | |
| missing_comma: None, | |
| }); | |
| return Err(err); | |
| } | |
| let mut recovered_ident = None; | |
| // we take this here so that the correct original token is retained in | |
| // the diagnostic, regardless of eager recovery. | |
| let bad_token = self.token; | |
| // suggest prepending a keyword in identifier position with `r#` | |
| let suggest_raw = if let Some((ident, IdentIsRaw::No)) = self.token.ident() | |
| && ident.is_raw_guess() | |
| && self.look_ahead(1, |t| valid_follow.contains(&t.kind)) | |
| { | |
| recovered_ident = Some((ident, IdentIsRaw::Yes)); | |
| // `Symbol::to_string()` is different from `Symbol::into_diag_arg()`, | |
| // which uses `Symbol::to_ident_string()` and "helpfully" adds an implicit `r#` | |
| let ident_name = ident.name.to_string(); | |
| Some(SuggEscapeIdentifier { span: ident.span.shrink_to_lo(), ident_name }) | |
| } else { | |
| None | |
| }; | |
| let suggest_remove_comma = | |
| if self.token == token::Comma && self.look_ahead(1, |t| t.is_ident()) { | |
| if recover { | |
| self.bump(); | |
| recovered_ident = self.ident_or_err(false).ok(); | |
| }; | |
| Some(SuggRemoveComma { span: bad_token.span }) | |
| } else { | |
| None | |
| }; | |
| let help_cannot_start_number = self.is_lit_bad_ident().map(|(len, valid_portion)| { | |
| let (invalid, valid) = self.token.span.split_at(len as u32); | |
| recovered_ident = Some((Ident::new(valid_portion, valid), IdentIsRaw::No)); | |
| HelpIdentifierStartsWithNumber { num_span: invalid } | |
| }); | |
| let err = ExpectedIdentifier { | |
| span: bad_token.span, | |
| token: bad_token, | |
| suggest_raw, | |
| suggest_remove_comma, | |
| help_cannot_start_number, | |
| }; | |
| let mut err = self.dcx().create_err(err); | |
| // if the token we have is a `<` | |
| // it *might* be a misplaced generic | |
| // FIXME: could we recover with this? | |
| if self.token == token::Lt { | |
| // all keywords that could have generic applied | |
| let valid_prev_keywords = | |
| [kw::Fn, kw::Type, kw::Struct, kw::Enum, kw::Union, kw::Trait]; | |
| // If we've expected an identifier, | |
| // and the current token is a '<' | |
| // if the previous token is a valid keyword | |
| // that might use a generic, then suggest a correct | |
| // generic placement (later on) | |
| let maybe_keyword = self.prev_token; | |
| if valid_prev_keywords.into_iter().any(|x| maybe_keyword.is_keyword(x)) { | |
| // if we have a valid keyword, attempt to parse generics | |
| // also obtain the keywords symbol | |
| match self.parse_generics() { | |
| Ok(generic) => { | |
| if let TokenKind::Ident(symbol, _) = maybe_keyword.kind { | |
| let ident_name = symbol; | |
| // at this point, we've found something like | |
| // `fn <T>id` | |
| // and current token should be Ident with the item name (i.e. the function name) | |
| // if there is a `<` after the fn name, then don't show a suggestion, show help | |
| if !self.look_ahead(1, |t| *t == token::Lt) | |
| && let Ok(snippet) = | |
| self.psess.source_map().span_to_snippet(generic.span) | |
| { | |
| err.multipart_suggestion( | |
| format!("place the generic parameter name after the {ident_name} name"), | |
| vec![ | |
| (self.token.span.shrink_to_hi(), snippet), | |
| (generic.span, String::new()) | |
| ], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } else { | |
| err.help(format!( | |
| "place the generic parameter name after the {ident_name} name" | |
| )); | |
| } | |
| } | |
| } | |
| Err(err) => { | |
| // if there's an error parsing the generics, | |
| // then don't do a misplaced generics suggestion | |
| // and emit the expected ident error instead; | |
| err.cancel(); | |
| } | |
| } | |
| } | |
| } | |
| if let Some(recovered_ident) = recovered_ident | |
| && recover | |
| { | |
| err.emit(); | |
| Ok(recovered_ident) | |
| } else { | |
| Err(err) | |
| } | |
| } | |
| pub(super) fn expected_ident_found_err(&mut self) -> Diag<'a> { | |
| self.expected_ident_found(false).unwrap_err() | |
| } | |
| /// Checks if the current token is a integer or float literal and looks like | |
| /// it could be a invalid identifier with digits at the start. | |
| /// | |
| /// Returns the number of characters (bytes) composing the invalid portion | |
| /// of the identifier and the valid portion of the identifier. | |
| pub(super) fn is_lit_bad_ident(&mut self) -> Option<(usize, Symbol)> { | |
| // ensure that the integer literal is followed by a *invalid* | |
| // suffix: this is how we know that it is a identifier with an | |
| // invalid beginning. | |
| if let token::Literal(Lit { | |
| kind: token::LitKind::Integer | token::LitKind::Float, | |
| symbol, | |
| suffix: Some(suffix), // no suffix makes it a valid literal | |
| }) = self.token.kind | |
| && rustc_ast::MetaItemLit::from_token(&self.token).is_none() | |
| { | |
| Some((symbol.as_str().len(), suffix)) | |
| } else { | |
| None | |
| } | |
| } | |
| pub(super) fn expected_one_of_not_found( | |
| &mut self, | |
| edible: &[ExpTokenPair], | |
| inedible: &[ExpTokenPair], | |
| ) -> PResult<'a, ErrorGuaranteed> { | |
| debug!("expected_one_of_not_found(edible: {:?}, inedible: {:?})", edible, inedible); | |
| fn tokens_to_string(tokens: &[TokenType]) -> String { | |
| let mut i = tokens.iter(); | |
| // This might be a sign we need a connect method on `Iterator`. | |
| let b = i.next().map_or_else(String::new, |t| t.to_string()); | |
| i.enumerate().fold(b, |mut b, (i, a)| { | |
| if tokens.len() > 2 && i == tokens.len() - 2 { | |
| b.push_str(", or "); | |
| } else if tokens.len() == 2 && i == tokens.len() - 2 { | |
| b.push_str(" or "); | |
| } else { | |
| b.push_str(", "); | |
| } | |
| b.push_str(&a.to_string()); | |
| b | |
| }) | |
| } | |
| for exp in edible.iter().chain(inedible.iter()) { | |
| self.expected_token_types.insert(exp.token_type); | |
| } | |
| let mut expected: Vec<_> = self.expected_token_types.iter().collect(); | |
| expected.sort_by_cached_key(|x| x.to_string()); | |
| expected.dedup(); | |
| let sm = self.psess.source_map(); | |
| // Special-case "expected `;`" errors. | |
| if expected.contains(&TokenType::Semi) { | |
| // If the user is trying to write a ternary expression, recover it and | |
| // return an Err to prevent a cascade of irrelevant diagnostics. | |
| if self.prev_token == token::Question | |
| && let Err(e) = self.maybe_recover_from_ternary_operator(None) | |
| { | |
| return Err(e); | |
| } | |
| if self.token.span == DUMMY_SP || self.prev_token.span == DUMMY_SP { | |
| // Likely inside a macro, can't provide meaningful suggestions. | |
| } else if !sm.is_multiline(self.prev_token.span.until(self.token.span)) { | |
| // The current token is in the same line as the prior token, not recoverable. | |
| } else if [token::Comma, token::Colon].contains(&self.token.kind) | |
| && self.prev_token == token::CloseParen | |
| { | |
| // Likely typo: The current token is on a new line and is expected to be | |
| // `.`, `;`, `?`, or an operator after a close delimiter token. | |
| // | |
| // let a = std::process::Command::new("echo") | |
| // .arg("1") | |
| // ,arg("2") | |
| // ^ | |
| // https://github.com/rust-lang/rust/issues/72253 | |
| } else if self.look_ahead(1, |t| { | |
| t == &token::CloseBrace || t.can_begin_expr() && *t != token::Colon | |
| }) && [token::Comma, token::Colon].contains(&self.token.kind) | |
| { | |
| // Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is | |
| // either `,` or `:`, and the next token could either start a new statement or is a | |
| // block close. For example: | |
| // | |
| // let x = 32: | |
| // let y = 42; | |
| let guar = self.dcx().emit_err(ExpectedSemi { | |
| span: self.token.span, | |
| token: self.token, | |
| unexpected_token_label: None, | |
| sugg: ExpectedSemiSugg::ChangeToSemi(self.token.span), | |
| }); | |
| self.bump(); | |
| return Ok(guar); | |
| } else if self.look_ahead(0, |t| { | |
| t == &token::CloseBrace | |
| || ((t.can_begin_expr() || t.can_begin_item()) | |
| && t != &token::Semi | |
| && t != &token::Pound) | |
| // Avoid triggering with too many trailing `#` in raw string. | |
| || (sm.is_multiline( | |
| self.prev_token.span.shrink_to_hi().until(self.token.span.shrink_to_lo()), | |
| ) && t == &token::Pound) | |
| }) && !expected.contains(&TokenType::Comma) | |
| { | |
| // Missing semicolon typo. This is triggered if the next token could either start a | |
| // new statement or is a block close. For example: | |
| // | |
| // let x = 32 | |
| // let y = 42; | |
| let span = self.prev_token.span.shrink_to_hi(); | |
| let guar = self.dcx().emit_err(ExpectedSemi { | |
| span, | |
| token: self.token, | |
| unexpected_token_label: Some(self.token.span), | |
| sugg: ExpectedSemiSugg::AddSemi(span), | |
| }); | |
| return Ok(guar); | |
| } | |
| } | |
| if self.token == TokenKind::EqEq | |
| && self.prev_token.is_ident() | |
| && expected.contains(&TokenType::Eq) | |
| { | |
| // Likely typo: `=` → `==` in let expr or enum item | |
| return Err(self.dcx().create_err(UseEqInstead { span: self.token.span })); | |
| } | |
| if (self.token.is_keyword(kw::Move) || self.token.is_keyword(kw::Use)) | |
| && self.prev_token.is_keyword(kw::Async) | |
| { | |
| // The 2015 edition is in use because parsing of `async move` or `async use` has failed. | |
| let span = self.prev_token.span.to(self.token.span); | |
| if self.token.is_keyword(kw::Move) { | |
| return Err(self.dcx().create_err(AsyncMoveBlockIn2015 { span })); | |
| } else { | |
| // kw::Use | |
| return Err(self.dcx().create_err(AsyncUseBlockIn2015 { span })); | |
| } | |
| } | |
| let expect = tokens_to_string(&expected); | |
| let actual = super::token_descr(&self.token); | |
| let (msg_exp, (label_sp, label_exp)) = if expected.len() > 1 { | |
| let fmt = format!("expected one of {expect}, found {actual}"); | |
| let short_expect = if expected.len() > 6 { | |
| format!("{} possible tokens", expected.len()) | |
| } else { | |
| expect | |
| }; | |
| (fmt, (self.prev_token.span.shrink_to_hi(), format!("expected one of {short_expect}"))) | |
| } else if expected.is_empty() { | |
| ( | |
| format!("unexpected token: {actual}"), | |
| (self.prev_token.span, "unexpected token after this".to_string()), | |
| ) | |
| } else { | |
| ( | |
| format!("expected {expect}, found {actual}"), | |
| (self.prev_token.span.shrink_to_hi(), format!("expected {expect}")), | |
| ) | |
| }; | |
| self.last_unexpected_token_span = Some(self.token.span); | |
| // FIXME: translation requires list formatting (for `expect`) | |
| let mut err = self.dcx().struct_span_err(self.token.span, msg_exp); | |
| self.label_expected_raw_ref(&mut err); | |
| // Look for usages of '=>' where '>=' was probably intended | |
| if self.token == token::FatArrow | |
| && expected.iter().any(|tok| matches!(tok, TokenType::Operator | TokenType::Le)) | |
| && !expected.iter().any(|tok| matches!(tok, TokenType::FatArrow | TokenType::Comma)) | |
| { | |
| err.span_suggestion( | |
| self.token.span, | |
| "you might have meant to write a \"greater than or equal to\" comparison", | |
| ">=", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| if let TokenKind::Ident(symbol, _) = &self.prev_token.kind { | |
| if ["def", "fun", "func", "function"].contains(&symbol.as_str()) { | |
| err.span_suggestion_short( | |
| self.prev_token.span, | |
| format!("write `fn` instead of `{symbol}` to declare a function"), | |
| "fn", | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| } | |
| if let TokenKind::Ident(prev, _) = &self.prev_token.kind | |
| && let TokenKind::Ident(cur, _) = &self.token.kind | |
| { | |
| let concat = Symbol::intern(&format!("{prev}{cur}")); | |
| let ident = Ident::new(concat, DUMMY_SP); | |
| if ident.is_used_keyword() || ident.is_reserved() || ident.is_raw_guess() { | |
| let concat_span = self.prev_token.span.to(self.token.span); | |
| err.span_suggestion_verbose( | |
| concat_span, | |
| format!("consider removing the space to spell keyword `{concat}`"), | |
| concat, | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| } | |
| // Try to detect an intended c-string literal while using a pre-2021 edition. The heuristic | |
| // here is to identify a cooked, uninterpolated `c` id immediately followed by a string, or | |
| // a cooked, uninterpolated `cr` id immediately followed by a string or a `#`, in an edition | |
| // where c-string literals are not allowed. There is the very slight possibility of a false | |
| // positive for a `cr#` that wasn't intended to start a c-string literal, but identifying | |
| // that in the parser requires unbounded lookahead, so we only add a hint to the existing | |
| // error rather than replacing it entirely. | |
| if ((self.prev_token == TokenKind::Ident(sym::character('c'), IdentIsRaw::No) | |
| && matches!(&self.token.kind, TokenKind::Literal(token::Lit { kind: token::Str, .. }))) | |
| || (self.prev_token == TokenKind::Ident(sym::cr, IdentIsRaw::No) | |
| && matches!( | |
| &self.token.kind, | |
| TokenKind::Literal(token::Lit { kind: token::Str, .. }) | token::Pound | |
| ))) | |
| && self.prev_token.span.hi() == self.token.span.lo() | |
| && !self.token.span.at_least_rust_2021() | |
| { | |
| err.note("you may be trying to write a c-string literal"); | |
| err.note("c-string literals require Rust 2021 or later"); | |
| err.subdiagnostic(HelpUseLatestEdition::new()); | |
| } | |
| // `pub` may be used for an item or `pub(crate)` | |
| if self.prev_token.is_ident_named(sym::public) | |
| && (self.token.can_begin_item() || self.token == TokenKind::OpenParen) | |
| { | |
| err.span_suggestion_short( | |
| self.prev_token.span, | |
| "write `pub` instead of `public` to make the item public", | |
| "pub", | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| if let token::DocComment(kind, style, _) = self.token.kind { | |
| // This is to avoid suggesting converting a doc comment to a regular comment | |
| // when missing a comma before the doc comment in lists (#142311): | |
| // | |
| // ``` | |
| // enum Foo{ | |
| // A /// xxxxxxx | |
| // B, | |
| // } | |
| // ``` | |
| if !expected.contains(&TokenType::Comma) { | |
| // We have something like `expr //!val` where the user likely meant `expr // !val` | |
| let pos = self.token.span.lo() + BytePos(2); | |
| let span = self.token.span.with_lo(pos).with_hi(pos); | |
| err.span_suggestion_verbose( | |
| span, | |
| format!( | |
| "add a space before {} to write a regular comment", | |
| match (kind, style) { | |
| (token::CommentKind::Line, ast::AttrStyle::Inner) => "`!`", | |
| (token::CommentKind::Block, ast::AttrStyle::Inner) => "`!`", | |
| (token::CommentKind::Line, ast::AttrStyle::Outer) => "the last `/`", | |
| (token::CommentKind::Block, ast::AttrStyle::Outer) => "the last `*`", | |
| }, | |
| ), | |
| " ".to_string(), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } | |
| let sp = if self.token == token::Eof { | |
| // This is EOF; don't want to point at the following char, but rather the last token. | |
| self.prev_token.span | |
| } else { | |
| label_sp | |
| }; | |
| if self.check_too_many_raw_str_terminators(&mut err) { | |
| if expected.contains(&TokenType::Semi) && self.eat(exp!(Semi)) { | |
| let guar = err.emit(); | |
| return Ok(guar); | |
| } else { | |
| return Err(err); | |
| } | |
| } | |
| if self.prev_token.span == DUMMY_SP { | |
| // Account for macro context where the previous span might not be | |
| // available to avoid incorrect output (#54841). | |
| err.span_label(self.token.span, label_exp); | |
| } else if !sm.is_multiline(self.token.span.shrink_to_hi().until(sp.shrink_to_lo())) { | |
| // When the spans are in the same line, it means that the only content between | |
| // them is whitespace, point at the found token in that case: | |
| // | |
| // X | () => { syntax error }; | |
| // | ^^^^^ expected one of 8 possible tokens here | |
| // | |
| // instead of having: | |
| // | |
| // X | () => { syntax error }; | |
| // | -^^^^^ unexpected token | |
| // | | | |
| // | expected one of 8 possible tokens here | |
| err.span_label(self.token.span, label_exp); | |
| } else { | |
| err.span_label(sp, label_exp); | |
| err.span_label(self.token.span, "unexpected token"); | |
| } | |
| // Check for misspelled keywords if there are no suggestions added to the diagnostic. | |
| if let Suggestions::Enabled(list) = &err.suggestions | |
| && list.is_empty() | |
| { | |
| self.check_for_misspelled_kw(&mut err, &expected); | |
| } | |
| Err(err) | |
| } | |
| /// Adds a label when `&raw EXPR` was written instead of `&raw const EXPR`/`&raw mut EXPR`. | |
| /// | |
| /// Given that not all parser diagnostics flow through `expected_one_of_not_found`, this | |
| /// label may need added to other diagnostics emission paths as needed. | |
| pub(super) fn label_expected_raw_ref(&mut self, err: &mut Diag<'_>) { | |
| if self.prev_token.is_keyword(kw::Raw) | |
| && self.expected_token_types.contains(TokenType::KwMut) | |
| && self.expected_token_types.contains(TokenType::KwConst) | |
| && self.token.can_begin_expr() | |
| { | |
| err.span_suggestions( | |
| self.prev_token.span.shrink_to_hi(), | |
| "`&raw` must be followed by `const` or `mut` to be a raw reference expression", | |
| [" const".to_string(), " mut".to_string()], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } | |
| /// Checks if the current token or the previous token are misspelled keywords | |
| /// and adds a helpful suggestion. | |
| fn check_for_misspelled_kw(&self, err: &mut Diag<'_>, expected: &[TokenType]) { | |
| let Some((curr_ident, _)) = self.token.ident() else { | |
| return; | |
| }; | |
| let expected_token_types: &[TokenType] = | |
| expected.len().checked_sub(10).map_or(&expected, |index| &expected[index..]); | |
| let expected_keywords: Vec<Symbol> = | |
| expected_token_types.iter().filter_map(|token| token.is_keyword()).collect(); | |
| // When there are a few keywords in the last ten elements of `self.expected_token_types` | |
| // and the current token is an identifier, it's probably a misspelled keyword. This handles | |
| // code like `async Move {}`, misspelled `if` in match guard, misspelled `else` in | |
| // `if`-`else` and misspelled `where` in a where clause. | |
| if !expected_keywords.is_empty() | |
| && !curr_ident.is_used_keyword() | |
| && let Some(misspelled_kw) = find_similar_kw(curr_ident, &expected_keywords) | |
| { | |
| err.subdiagnostic(misspelled_kw); | |
| // We don't want other suggestions to be added as they are most likely meaningless | |
| // when there is a misspelled keyword. | |
| err.seal_suggestions(); | |
| } else if let Some((prev_ident, _)) = self.prev_token.ident() | |
| && !prev_ident.is_used_keyword() | |
| { | |
| // We generate a list of all keywords at runtime rather than at compile time | |
| // so that it gets generated only when the diagnostic needs it. | |
| // Also, it is unlikely that this list is generated multiple times because the | |
| // parser halts after execution hits this path. | |
| let all_keywords = used_keywords(|| prev_ident.span.edition()); | |
| // Otherwise, check the previous token with all the keywords as possible candidates. | |
| // This handles code like `Struct Human;` and `While a < b {}`. | |
| // We check the previous token only when the current token is an identifier to avoid | |
| // false positives like suggesting keyword `for` for `extern crate foo {}`. | |
| if let Some(misspelled_kw) = find_similar_kw(prev_ident, &all_keywords) { | |
| err.subdiagnostic(misspelled_kw); | |
| // We don't want other suggestions to be added as they are most likely meaningless | |
| // when there is a misspelled keyword. | |
| err.seal_suggestions(); | |
| } | |
| } | |
| } | |
| /// The user has written `#[attr] expr` which is unsupported. (#106020) | |
| pub(super) fn attr_on_non_tail_expr(&self, expr: &Expr) -> ErrorGuaranteed { | |
| // Missing semicolon typo error. | |
| let span = self.prev_token.span.shrink_to_hi(); | |
| let mut err = self.dcx().create_err(ExpectedSemi { | |
| span, | |
| token: self.token, | |
| unexpected_token_label: Some(self.token.span), | |
| sugg: ExpectedSemiSugg::AddSemi(span), | |
| }); | |
| let attr_span = match &expr.attrs[..] { | |
| [] => unreachable!(), | |
| [only] => only.span, | |
| [first, rest @ ..] => { | |
| for attr in rest { | |
| err.span_label(attr.span, ""); | |
| } | |
| first.span | |
| } | |
| }; | |
| err.span_label( | |
| attr_span, | |
| format!( | |
| "only `;` terminated statements or tail expressions are allowed after {}", | |
| if expr.attrs.len() == 1 { "this attribute" } else { "these attributes" }, | |
| ), | |
| ); | |
| if self.token == token::Pound && self.look_ahead(1, |t| *t == token::OpenBracket) { | |
| // We have | |
| // #[attr] | |
| // expr | |
| // #[not_attr] | |
| // other_expr | |
| err.span_label(span, "expected `;` here"); | |
| err.multipart_suggestion( | |
| "alternatively, consider surrounding the expression with a block", | |
| vec![ | |
| (expr.span.shrink_to_lo(), "{ ".to_string()), | |
| (expr.span.shrink_to_hi(), " }".to_string()), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| // Special handling for `#[cfg(...)]` chains | |
| let mut snapshot = self.create_snapshot_for_diagnostic(); | |
| if let [attr] = &expr.attrs[..] | |
| && let ast::AttrKind::Normal(attr_kind) = &attr.kind | |
| && let [segment] = &attr_kind.item.path.segments[..] | |
| && segment.ident.name == sym::cfg | |
| && let Some(args_span) = attr_kind.item.args.span() | |
| && let next_attr = match snapshot.parse_attribute(InnerAttrPolicy::Forbidden(None)) | |
| { | |
| Ok(next_attr) => next_attr, | |
| Err(inner_err) => { | |
| inner_err.cancel(); | |
| return err.emit(); | |
| } | |
| } | |
| && let ast::AttrKind::Normal(next_attr_kind) = next_attr.kind | |
| && let Some(next_attr_args_span) = next_attr_kind.item.args.span() | |
| && let [next_segment] = &next_attr_kind.item.path.segments[..] | |
| && segment.ident.name == sym::cfg | |
| { | |
| let next_expr = match snapshot.parse_expr() { | |
| Ok(next_expr) => next_expr, | |
| Err(inner_err) => { | |
| inner_err.cancel(); | |
| return err.emit(); | |
| } | |
| }; | |
| // We have for sure | |
| // #[cfg(..)] | |
| // expr | |
| // #[cfg(..)] | |
| // other_expr | |
| // So we suggest using `if cfg!(..) { expr } else if cfg!(..) { other_expr }`. | |
| let margin = self.psess.source_map().span_to_margin(next_expr.span).unwrap_or(0); | |
| let sugg = vec![ | |
| (attr.span.with_hi(segment.span().hi()), "if cfg!".to_string()), | |
| (args_span.shrink_to_hi().with_hi(attr.span.hi()), " {".to_string()), | |
| (expr.span.shrink_to_lo(), " ".to_string()), | |
| ( | |
| next_attr.span.with_hi(next_segment.span().hi()), | |
| "} else if cfg!".to_string(), | |
| ), | |
| ( | |
| next_attr_args_span.shrink_to_hi().with_hi(next_attr.span.hi()), | |
| " {".to_string(), | |
| ), | |
| (next_expr.span.shrink_to_lo(), " ".to_string()), | |
| (next_expr.span.shrink_to_hi(), format!("\n{}}}", " ".repeat(margin))), | |
| ]; | |
| err.multipart_suggestion( | |
| "it seems like you are trying to provide different expressions depending on \ | |
| `cfg`, consider using `if cfg!(..)`", | |
| sugg, | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| } | |
| err.emit() | |
| } | |
| fn check_too_many_raw_str_terminators(&mut self, err: &mut Diag<'_>) -> bool { | |
| let sm = self.psess.source_map(); | |
| match (&self.prev_token.kind, &self.token.kind) { | |
| ( | |
| TokenKind::Literal(Lit { | |
| kind: LitKind::StrRaw(n_hashes) | LitKind::ByteStrRaw(n_hashes), | |
| .. | |
| }), | |
| TokenKind::Pound, | |
| ) if !sm.is_multiline( | |
| self.prev_token.span.shrink_to_hi().until(self.token.span.shrink_to_lo()), | |
| ) => | |
| { | |
| let n_hashes: u8 = *n_hashes; | |
| err.primary_message("too many `#` when terminating raw string"); | |
| let str_span = self.prev_token.span; | |
| let mut span = self.token.span; | |
| let mut count = 0; | |
| while self.token == TokenKind::Pound | |
| && !sm.is_multiline(span.shrink_to_hi().until(self.token.span.shrink_to_lo())) | |
| { | |
| span = span.with_hi(self.token.span.hi()); | |
| self.bump(); | |
| count += 1; | |
| } | |
| err.span(span); | |
| err.span_suggestion( | |
| span, | |
| format!("remove the extra `#`{}", pluralize!(count)), | |
| "", | |
| Applicability::MachineApplicable, | |
| ); | |
| err.span_label( | |
| str_span, | |
| format!("this raw string started with {n_hashes} `#`{}", pluralize!(n_hashes)), | |
| ); | |
| true | |
| } | |
| _ => false, | |
| } | |
| } | |
| pub(super) fn maybe_suggest_struct_literal( | |
| &mut self, | |
| lo: Span, | |
| s: BlockCheckMode, | |
| maybe_struct_name: token::Token, | |
| ) -> Option<PResult<'a, Box<Block>>> { | |
| if self.token.is_ident() && self.look_ahead(1, |t| t == &token::Colon) { | |
| // We might be having a struct literal where people forgot to include the path: | |
| // fn foo() -> Foo { | |
| // field: value, | |
| // } | |
| debug!(?maybe_struct_name, ?self.token); | |
| let mut snapshot = self.create_snapshot_for_diagnostic(); | |
| let path = Path { | |
| segments: ThinVec::new(), | |
| span: self.prev_token.span.shrink_to_lo(), | |
| tokens: None, | |
| }; | |
| let struct_expr = snapshot.parse_expr_struct(None, path, false); | |
| let block_tail = self.parse_block_tail(lo, s, AttemptLocalParseRecovery::No); | |
| return Some(match (struct_expr, block_tail) { | |
| (Ok(expr), Err(err)) => { | |
| // We have encountered the following: | |
| // fn foo() -> Foo { | |
| // field: value, | |
| // } | |
| // Suggest: | |
| // fn foo() -> Foo { Path { | |
| // field: value, | |
| // } } | |
| err.cancel(); | |
| self.restore_snapshot(snapshot); | |
| let guar = self.dcx().emit_err(StructLiteralBodyWithoutPath { | |
| span: expr.span, | |
| sugg: StructLiteralBodyWithoutPathSugg { | |
| before: expr.span.shrink_to_lo(), | |
| after: expr.span.shrink_to_hi(), | |
| }, | |
| }); | |
| Ok(self.mk_block( | |
| thin_vec![self.mk_stmt_err(expr.span, guar)], | |
| s, | |
| lo.to(self.prev_token.span), | |
| )) | |
| } | |
| (Err(err), Ok(tail)) => { | |
| // We have a block tail that contains a somehow valid expr. | |
| err.cancel(); | |
| Ok(tail) | |
| } | |
| (Err(snapshot_err), Err(err)) => { | |
| // We don't know what went wrong, emit the normal error. | |
| snapshot_err.cancel(); | |
| self.consume_block(exp!(OpenBrace), exp!(CloseBrace), ConsumeClosingDelim::Yes); | |
| Err(err) | |
| } | |
| (Ok(_), Ok(tail)) => Ok(tail), | |
| }); | |
| } | |
| None | |
| } | |
| pub(super) fn recover_closure_body( | |
| &mut self, | |
| mut err: Diag<'a>, | |
| before: token::Token, | |
| prev: token::Token, | |
| token: token::Token, | |
| lo: Span, | |
| decl_hi: Span, | |
| ) -> PResult<'a, Box<Expr>> { | |
| err.span_label(lo.to(decl_hi), "while parsing the body of this closure"); | |
| let guar = match before.kind { | |
| token::OpenBrace if token.kind != token::OpenBrace => { | |
| // `{ || () }` should have been `|| { () }` | |
| err.multipart_suggestion( | |
| "you might have meant to open the body of the closure, instead of enclosing \ | |
| the closure in a block", | |
| vec![ | |
| (before.span, String::new()), | |
| (prev.span.shrink_to_hi(), " {".to_string()), | |
| ], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| let guar = err.emit(); | |
| self.eat_to_tokens(&[exp!(CloseBrace)]); | |
| guar | |
| } | |
| token::OpenParen if token.kind != token::OpenBrace => { | |
| // We are within a function call or tuple, we can emit the error | |
| // and recover. | |
| self.eat_to_tokens(&[exp!(CloseParen), exp!(Comma)]); | |
| err.multipart_suggestion( | |
| "you might have meant to open the body of the closure", | |
| vec![ | |
| (prev.span.shrink_to_hi(), " {".to_string()), | |
| (self.token.span.shrink_to_lo(), "}".to_string()), | |
| ], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| err.emit() | |
| } | |
| _ if token.kind != token::OpenBrace => { | |
| // We don't have a heuristic to correctly identify where the block | |
| // should be closed. | |
| err.multipart_suggestion( | |
| "you might have meant to open the body of the closure", | |
| vec![(prev.span.shrink_to_hi(), " {".to_string())], | |
| Applicability::HasPlaceholders, | |
| ); | |
| return Err(err); | |
| } | |
| _ => return Err(err), | |
| }; | |
| Ok(self.mk_expr_err(lo.to(self.token.span), guar)) | |
| } | |
| /// Eats and discards tokens until one of `closes` is encountered. Respects token trees, | |
| /// passes through any errors encountered. Used for error recovery. | |
| pub(super) fn eat_to_tokens(&mut self, closes: &[ExpTokenPair]) { | |
| if let Err(err) = self | |
| .parse_seq_to_before_tokens(closes, &[], SeqSep::none(), |p| Ok(p.parse_token_tree())) | |
| { | |
| err.cancel(); | |
| } | |
| } | |
| /// This function checks if there are trailing angle brackets and produces | |
| /// a diagnostic to suggest removing them. | |
| /// | |
| /// ```ignore (diagnostic) | |
| /// let _ = [1, 2, 3].into_iter().collect::<Vec<usize>>>>(); | |
| /// ^^ help: remove extra angle brackets | |
| /// ``` | |
| /// | |
| /// If `true` is returned, then trailing brackets were recovered, tokens were consumed | |
| /// up until one of the tokens in 'end' was encountered, and an error was emitted. | |
| pub(super) fn check_trailing_angle_brackets( | |
| &mut self, | |
| segment: &PathSegment, | |
| end: &[ExpTokenPair], | |
| ) -> Option<ErrorGuaranteed> { | |
| if !self.may_recover() { | |
| return None; | |
| } | |
| // This function is intended to be invoked after parsing a path segment where there are two | |
| // cases: | |
| // | |
| // 1. A specific token is expected after the path segment. | |
| // eg. `x.foo(`, `x.foo::<u32>(` (parenthesis - method call), | |
| // `Foo::`, or `Foo::<Bar>::` (mod sep - continued path). | |
| // 2. No specific token is expected after the path segment. | |
| // eg. `x.foo` (field access) | |
| // | |
| // This function is called after parsing `.foo` and before parsing the token `end` (if | |
| // present). This includes any angle bracket arguments, such as `.foo::<u32>` or | |
| // `Foo::<Bar>`. | |
| // We only care about trailing angle brackets if we previously parsed angle bracket | |
| // arguments. This helps stop us incorrectly suggesting that extra angle brackets be | |
| // removed in this case: | |
| // | |
| // `x.foo >> (3)` (where `x.foo` is a `u32` for example) | |
| // | |
| // This case is particularly tricky as we won't notice it just looking at the tokens - | |
| // it will appear the same (in terms of upcoming tokens) as below (since the `::<u32>` will | |
| // have already been parsed): | |
| // | |
| // `x.foo::<u32>>>(3)` | |
| let parsed_angle_bracket_args = | |
| segment.args.as_ref().is_some_and(|args| args.is_angle_bracketed()); | |
| debug!( | |
| "check_trailing_angle_brackets: parsed_angle_bracket_args={:?}", | |
| parsed_angle_bracket_args, | |
| ); | |
| if !parsed_angle_bracket_args { | |
| return None; | |
| } | |
| // Keep the span at the start so we can highlight the sequence of `>` characters to be | |
| // removed. | |
| let lo = self.token.span; | |
| // We need to look-ahead to see if we have `>` characters without moving the cursor forward | |
| // (since we might have the field access case and the characters we're eating are | |
| // actual operators and not trailing characters - ie `x.foo >> 3`). | |
| let mut position = 0; | |
| // We can encounter `>` or `>>` tokens in any order, so we need to keep track of how | |
| // many of each (so we can correctly pluralize our error messages) and continue to | |
| // advance. | |
| let mut number_of_shr = 0; | |
| let mut number_of_gt = 0; | |
| while self.look_ahead(position, |t| { | |
| trace!("check_trailing_angle_brackets: t={:?}", t); | |
| if *t == token::Shr { | |
| number_of_shr += 1; | |
| true | |
| } else if *t == token::Gt { | |
| number_of_gt += 1; | |
| true | |
| } else { | |
| false | |
| } | |
| }) { | |
| position += 1; | |
| } | |
| // If we didn't find any trailing `>` characters, then we have nothing to error about. | |
| debug!( | |
| "check_trailing_angle_brackets: number_of_gt={:?} number_of_shr={:?}", | |
| number_of_gt, number_of_shr, | |
| ); | |
| if number_of_gt < 1 && number_of_shr < 1 { | |
| return None; | |
| } | |
| // Finally, double check that we have our end token as otherwise this is the | |
| // second case. | |
| if self.look_ahead(position, |t| { | |
| trace!("check_trailing_angle_brackets: t={:?}", t); | |
| end.iter().any(|exp| exp.tok == t.kind) | |
| }) { | |
| // Eat from where we started until the end token so that parsing can continue | |
| // as if we didn't have those extra angle brackets. | |
| self.eat_to_tokens(end); | |
| let span = lo.to(self.prev_token.span); | |
| let num_extra_brackets = number_of_gt + number_of_shr * 2; | |
| return Some(self.dcx().emit_err(UnmatchedAngleBrackets { span, num_extra_brackets })); | |
| } | |
| None | |
| } | |
| /// Check if a method call with an intended turbofish has been written without surrounding | |
| /// angle brackets. | |
| pub(super) fn check_turbofish_missing_angle_brackets(&mut self, segment: &mut PathSegment) { | |
| if !self.may_recover() { | |
| return; | |
| } | |
| if self.token == token::PathSep && segment.args.is_none() { | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| self.bump(); | |
| let lo = self.token.span; | |
| match self.parse_angle_args(None) { | |
| Ok(args) => { | |
| let span = lo.to(self.prev_token.span); | |
| // Detect trailing `>` like in `x.collect::Vec<_>>()`. | |
| let mut trailing_span = self.prev_token.span.shrink_to_hi(); | |
| while self.token == token::Shr || self.token == token::Gt { | |
| trailing_span = trailing_span.to(self.token.span); | |
| self.bump(); | |
| } | |
| if self.token == token::OpenParen { | |
| // Recover from bad turbofish: `foo.collect::Vec<_>()`. | |
| segment.args = Some(AngleBracketedArgs { args, span }.into()); | |
| self.dcx().emit_err(GenericParamsWithoutAngleBrackets { | |
| span, | |
| sugg: GenericParamsWithoutAngleBracketsSugg { | |
| left: span.shrink_to_lo(), | |
| right: trailing_span, | |
| }, | |
| }); | |
| } else { | |
| // This doesn't look like an invalid turbofish, can't recover parse state. | |
| self.restore_snapshot(snapshot); | |
| } | |
| } | |
| Err(err) => { | |
| // We couldn't parse generic parameters, unlikely to be a turbofish. Rely on | |
| // generic parse error instead. | |
| err.cancel(); | |
| self.restore_snapshot(snapshot); | |
| } | |
| } | |
| } | |
| } | |
| /// 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( | |
| &mut self, | |
| mut e: Diag<'a>, | |
| expr: &mut Box<Expr>, | |
| ) -> PResult<'a, ErrorGuaranteed> { | |
| if let ExprKind::Binary(binop, _, _) = &expr.kind | |
| && let ast::BinOpKind::Lt = binop.node | |
| && self.eat(exp!(Comma)) | |
| { | |
| let x = self.parse_seq_to_before_end( | |
| exp!(Gt), | |
| SeqSep::trailing_allowed(exp!(Comma)), | |
| |p| match p.parse_generic_arg(None)? { | |
| Some(arg) => Ok(arg), | |
| // If we didn't eat a generic arg, then we should error. | |
| None => p.unexpected_any(), | |
| }, | |
| ); | |
| match x { | |
| Ok((_, _, Recovered::No)) => { | |
| if self.eat(exp!(Gt)) { | |
| // We made sense of it. Improve the error message. | |
| e.span_suggestion_verbose( | |
| binop.span.shrink_to_lo(), | |
| msg!("use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments"), | |
| "::", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| match self.parse_expr() { | |
| Ok(_) => { | |
| // The subsequent expression is valid. Mark | |
| // `expr` as erroneous and emit `e` now, but | |
| // return `Ok` so parsing can continue. | |
| let guar = e.emit(); | |
| *expr = self.mk_expr_err(expr.span.to(self.prev_token.span), guar); | |
| return Ok(guar); | |
| } | |
| Err(err) => { | |
| err.cancel(); | |
| } | |
| } | |
| } | |
| } | |
| Ok((_, _, Recovered::Yes(_))) => {} | |
| Err(err) => { | |
| err.cancel(); | |
| } | |
| } | |
| } | |
| Err(e) | |
| } | |
| /// Suggest add the missing `let` before the identifier in stmt | |
| /// `a: Ty = 1` -> `let a: Ty = 1` | |
| pub(super) fn suggest_add_missing_let_for_stmt(&mut self, err: &mut Diag<'a>) { | |
| if self.token == token::Colon { | |
| let prev_span = self.prev_token.span.shrink_to_lo(); | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| self.bump(); | |
| match self.parse_ty() { | |
| Ok(_) => { | |
| if self.token == token::Eq { | |
| let sugg = SuggAddMissingLetStmt { span: prev_span }; | |
| sugg.add_to_diag(err); | |
| } | |
| } | |
| Err(e) => { | |
| e.cancel(); | |
| } | |
| } | |
| self.restore_snapshot(snapshot); | |
| } | |
| } | |
| /// Check to see if a pair of chained operators looks like an attempt at chained comparison, | |
| /// e.g. `1 < x <= 3`. If so, suggest either splitting the comparison into two, or | |
| /// parenthesising the leftmost comparison. The return value indicates if recovery happened. | |
| fn attempt_chained_comparison_suggestion( | |
| &mut self, | |
| err: &mut ComparisonOperatorsCannotBeChained, | |
| inner_op: &Expr, | |
| outer_op: &Spanned<AssocOp>, | |
| ) -> bool { | |
| if let ExprKind::Binary(op, l1, r1) = &inner_op.kind { | |
| if let ExprKind::Field(_, ident) = l1.kind | |
| && !ident.is_numeric() | |
| && !matches!(r1.kind, ExprKind::Lit(_)) | |
| { | |
| // The parser has encountered `foo.bar<baz`, the likelihood of the turbofish | |
| // suggestion being the only one to apply is high. | |
| return false; | |
| } | |
| return match (op.node, &outer_op.node) { | |
| // `x == y == z` | |
| (BinOpKind::Eq, AssocOp::Binary(BinOpKind::Eq)) | | |
| // `x < y < z` and friends. | |
| (BinOpKind::Lt, AssocOp::Binary(BinOpKind::Lt | BinOpKind::Le)) | | |
| (BinOpKind::Le, AssocOp::Binary(BinOpKind::Lt | BinOpKind::Le)) | | |
| // `x > y > z` and friends. | |
| (BinOpKind::Gt, AssocOp::Binary(BinOpKind::Gt | BinOpKind::Ge)) | | |
| (BinOpKind::Ge, AssocOp::Binary(BinOpKind::Gt | BinOpKind::Ge)) => { | |
| let expr_to_str = |e: &Expr| { | |
| self.span_to_snippet(e.span) | |
| .unwrap_or_else(|_| pprust::expr_to_string(e)) | |
| }; | |
| err.chaining_sugg = Some(ComparisonOperatorsCannotBeChainedSugg::SplitComparison { | |
| span: inner_op.span.shrink_to_hi(), | |
| middle_term: expr_to_str(r1), | |
| }); | |
| false // Keep the current parse behavior, where the AST is `(x < y) < z`. | |
| } | |
| // `x == y < z` | |
| ( | |
| BinOpKind::Eq, | |
| AssocOp::Binary(BinOpKind::Lt | BinOpKind::Le | BinOpKind::Gt | BinOpKind::Ge) | |
| ) => { | |
| // Consume `z`/outer-op-rhs. | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| match self.parse_expr() { | |
| Ok(r2) => { | |
| // We are sure that outer-op-rhs could be consumed, the suggestion is | |
| // likely correct. | |
| err.chaining_sugg = Some(ComparisonOperatorsCannotBeChainedSugg::Parenthesize { | |
| left: r1.span.shrink_to_lo(), | |
| right: r2.span.shrink_to_hi(), | |
| }); | |
| true | |
| } | |
| Err(expr_err) => { | |
| expr_err.cancel(); | |
| self.restore_snapshot(snapshot); | |
| true | |
| } | |
| } | |
| } | |
| // `x > y == z` | |
| ( | |
| BinOpKind::Lt | BinOpKind::Le | BinOpKind::Gt | BinOpKind::Ge, | |
| AssocOp::Binary(BinOpKind::Eq) | |
| ) => { | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| // At this point it is always valid to enclose the lhs in parentheses, no | |
| // further checks are necessary. | |
| match self.parse_expr() { | |
| Ok(_) => { | |
| err.chaining_sugg = Some(ComparisonOperatorsCannotBeChainedSugg::Parenthesize { | |
| left: l1.span.shrink_to_lo(), | |
| right: r1.span.shrink_to_hi(), | |
| }); | |
| true | |
| } | |
| Err(expr_err) => { | |
| expr_err.cancel(); | |
| self.restore_snapshot(snapshot); | |
| false | |
| } | |
| } | |
| } | |
| _ => false | |
| }; | |
| } | |
| false | |
| } | |
| /// Produces an error if comparison operators are chained (RFC #558). | |
| /// We only need to check the LHS, not the RHS, because all comparison ops have same | |
| /// precedence (see `fn precedence`) and are left-associative (see `fn fixity`). | |
| /// | |
| /// This can also be hit if someone incorrectly writes `foo<bar>()` when they should have used | |
| /// the turbofish (`foo::<bar>()`) syntax. We attempt some heuristic recovery if that is the | |
| /// case. | |
| /// | |
| /// Keep in mind that given that `outer_op.is_comparison()` holds and comparison ops are left | |
| /// associative we can infer that we have: | |
| /// | |
| /// ```text | |
| /// outer_op | |
| /// / \ | |
| /// inner_op r2 | |
| /// / \ | |
| /// l1 r1 | |
| /// ``` | |
| pub(super) fn check_no_chained_comparison( | |
| &mut self, | |
| inner_op: &Expr, | |
| outer_op: &Spanned<AssocOp>, | |
| ) -> PResult<'a, Option<Box<Expr>>> { | |
| debug_assert!( | |
| outer_op.node.is_comparison(), | |
| "check_no_chained_comparison: {:?} is not comparison", | |
| outer_op.node, | |
| ); | |
| let mk_err_expr = | |
| |this: &Self, span, guar| Ok(Some(this.mk_expr(span, ExprKind::Err(guar)))); | |
| match &inner_op.kind { | |
| ExprKind::Binary(op, l1, r1) if op.node.is_comparison() => { | |
| let mut err = ComparisonOperatorsCannotBeChained { | |
| span: vec![op.span, self.prev_token.span], | |
| suggest_turbofish: None, | |
| help_turbofish: false, | |
| chaining_sugg: None, | |
| }; | |
| // Include `<` to provide this recommendation even in a case like | |
| // `Foo<Bar<Baz<Qux, ()>>>` | |
| if op.node == BinOpKind::Lt && outer_op.node == AssocOp::Binary(BinOpKind::Lt) | |
| || outer_op.node == AssocOp::Binary(BinOpKind::Gt) | |
| { | |
| if outer_op.node == AssocOp::Binary(BinOpKind::Lt) { | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| self.bump(); | |
| // So far we have parsed `foo<bar<`, consume the rest of the type args. | |
| let modifiers = [(token::Lt, 1), (token::Gt, -1), (token::Shr, -2)]; | |
| self.consume_tts(1, &modifiers); | |
| if !matches!(self.token.kind, token::OpenParen | token::PathSep) { | |
| // We don't have `foo< bar >(` or `foo< bar >::`, so we rewind the | |
| // parser and bail out. | |
| self.restore_snapshot(snapshot); | |
| } | |
| } | |
| return if self.token == token::PathSep { | |
| // We have some certainty that this was a bad turbofish at this point. | |
| // `foo< bar >::` | |
| if let ExprKind::Binary(o, ..) = inner_op.kind | |
| && o.node == BinOpKind::Lt | |
| { | |
| err.suggest_turbofish = Some(op.span.shrink_to_lo()); | |
| } else { | |
| err.help_turbofish = true; | |
| } | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| self.bump(); // `::` | |
| // Consume the rest of the likely `foo<bar>::new()` or return at `foo<bar>`. | |
| match self.parse_expr() { | |
| Ok(_) => { | |
| // 99% certain that the suggestion is correct, continue parsing. | |
| let guar = self.dcx().emit_err(err); | |
| // FIXME: actually check that the two expressions in the binop are | |
| // paths and resynthesize new fn call expression instead of using | |
| // `ExprKind::Err` placeholder. | |
| mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar) | |
| } | |
| Err(expr_err) => { | |
| expr_err.cancel(); | |
| // Not entirely sure now, but we bubble the error up with the | |
| // suggestion. | |
| self.restore_snapshot(snapshot); | |
| Err(self.dcx().create_err(err)) | |
| } | |
| } | |
| } else if self.token == token::OpenParen { | |
| // We have high certainty that this was a bad turbofish at this point. | |
| // `foo< bar >(` | |
| if let ExprKind::Binary(o, ..) = inner_op.kind | |
| && o.node == BinOpKind::Lt | |
| { | |
| err.suggest_turbofish = Some(op.span.shrink_to_lo()); | |
| } else { | |
| err.help_turbofish = true; | |
| } | |
| // Consume the fn call arguments. | |
| match self.consume_fn_args() { | |
| Err(()) => Err(self.dcx().create_err(err)), | |
| Ok(()) => { | |
| let guar = self.dcx().emit_err(err); | |
| // FIXME: actually check that the two expressions in the binop are | |
| // paths and resynthesize new fn call expression instead of using | |
| // `ExprKind::Err` placeholder. | |
| mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar) | |
| } | |
| } | |
| } else { | |
| if !matches!(l1.kind, ExprKind::Lit(_)) | |
| && !matches!(r1.kind, ExprKind::Lit(_)) | |
| { | |
| // All we know is that this is `foo < bar >` and *nothing* else. Try to | |
| // be helpful, but don't attempt to recover. | |
| err.help_turbofish = true; | |
| } | |
| // If it looks like a genuine attempt to chain operators (as opposed to a | |
| // misformatted turbofish, for instance), suggest a correct form. | |
| let recovered = self | |
| .attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op); | |
| if recovered { | |
| let guar = self.dcx().emit_err(err); | |
| mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar) | |
| } else { | |
| // These cases cause too many knock-down errors, bail out (#61329). | |
| Err(self.dcx().create_err(err)) | |
| } | |
| }; | |
| } | |
| let recovered = | |
| self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op); | |
| let guar = self.dcx().emit_err(err); | |
| if recovered { | |
| return mk_err_expr(self, inner_op.span.to(self.prev_token.span), guar); | |
| } | |
| } | |
| _ => {} | |
| } | |
| Ok(None) | |
| } | |
| fn consume_fn_args(&mut self) -> Result<(), ()> { | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| self.bump(); // `(` | |
| // Consume the fn call arguments. | |
| let modifiers = [(token::OpenParen, 1), (token::CloseParen, -1)]; | |
| self.consume_tts(1, &modifiers); | |
| if self.token == token::Eof { | |
| // Not entirely sure that what we consumed were fn arguments, rollback. | |
| self.restore_snapshot(snapshot); | |
| Err(()) | |
| } else { | |
| // 99% certain that the suggestion is correct, continue parsing. | |
| Ok(()) | |
| } | |
| } | |
| pub(super) fn maybe_report_ambiguous_plus(&mut self, impl_dyn_multi: bool, ty: &Ty) { | |
| if impl_dyn_multi { | |
| self.dcx().emit_err(AmbiguousPlus { | |
| span: ty.span, | |
| suggestion: AddParen { lo: ty.span.shrink_to_lo(), hi: ty.span.shrink_to_hi() }, | |
| }); | |
| } | |
| } | |
| /// Swift lets users write `Ty?` to mean `Option<Ty>`. Parse the construct and recover from it. | |
| pub(super) fn maybe_recover_from_question_mark(&mut self, ty: Box<Ty>) -> Box<Ty> { | |
| if self.token == token::Question { | |
| self.bump(); | |
| let guar = self.dcx().emit_err(QuestionMarkInType { | |
| span: self.prev_token.span, | |
| sugg: QuestionMarkInTypeSugg { | |
| left: ty.span.shrink_to_lo(), | |
| right: self.prev_token.span, | |
| }, | |
| }); | |
| self.mk_ty(ty.span.to(self.prev_token.span), TyKind::Err(guar)) | |
| } else { | |
| ty | |
| } | |
| } | |
| /// Rust has no ternary operator (`cond ? then : else`). Parse it and try | |
| /// to recover from it if `then` and `else` are valid expressions. Returns | |
| /// an err if this appears to be a ternary expression. | |
| /// If we have the span of the condition, we can provide a better error span | |
| /// and code suggestion. | |
| pub(super) fn maybe_recover_from_ternary_operator( | |
| &mut self, | |
| cond: Option<Span>, | |
| ) -> PResult<'a, ()> { | |
| if self.prev_token != token::Question { | |
| return PResult::Ok(()); | |
| } | |
| let question = self.prev_token.span; | |
| let lo = cond.unwrap_or(question).lo(); | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| if match self.parse_expr() { | |
| Ok(_) => true, | |
| Err(err) => { | |
| err.cancel(); | |
| // The colon can sometimes be mistaken for type | |
| // ascription. Catch when this happens and continue. | |
| self.token == token::Colon | |
| } | |
| } { | |
| if self.eat_noexpect(&token::Colon) { | |
| let colon = self.prev_token.span; | |
| match self.parse_expr() { | |
| Ok(expr) => { | |
| let sugg = cond.map(|cond| TernaryOperatorSuggestion { | |
| before_cond: cond.shrink_to_lo(), | |
| question, | |
| colon, | |
| end: expr.span.shrink_to_hi(), | |
| }); | |
| return Err(self.dcx().create_err(TernaryOperator { | |
| span: self.prev_token.span.with_lo(lo), | |
| sugg, | |
| no_sugg: sugg.is_none(), | |
| })); | |
| } | |
| Err(err) => { | |
| err.cancel(); | |
| } | |
| }; | |
| } | |
| } | |
| self.restore_snapshot(snapshot); | |
| Ok(()) | |
| } | |
| pub(super) fn maybe_recover_from_bad_type_plus(&mut self, ty: &Ty) -> PResult<'a, ()> { | |
| // Do not add `+` to expected tokens. | |
| if !self.token.is_like_plus() { | |
| return Ok(()); | |
| } | |
| self.bump(); // `+` | |
| let _bounds = self.parse_generic_bounds()?; | |
| let sub = match &ty.kind { | |
| TyKind::Ref(_lifetime, mut_ty) => { | |
| let lo = mut_ty.ty.span.shrink_to_lo(); | |
| let hi = self.prev_token.span.shrink_to_hi(); | |
| BadTypePlusSub::AddParen { suggestion: AddParen { lo, hi } } | |
| } | |
| TyKind::Ptr(..) | TyKind::FnPtr(..) => { | |
| BadTypePlusSub::ForgotParen { span: ty.span.to(self.prev_token.span) } | |
| } | |
| _ => BadTypePlusSub::ExpectPath { span: ty.span }, | |
| }; | |
| self.dcx().emit_err(BadTypePlus { span: ty.span, sub }); | |
| Ok(()) | |
| } | |
| pub(super) fn recover_from_prefix_increment( | |
| &mut self, | |
| operand_expr: Box<Expr>, | |
| op_span: Span, | |
| start_stmt: bool, | |
| ) -> PResult<'a, Box<Expr>> { | |
| let standalone = if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }; | |
| let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre }; | |
| self.recover_from_inc_dec(operand_expr, kind, op_span) | |
| } | |
| pub(super) fn recover_from_postfix_increment( | |
| &mut self, | |
| operand_expr: Box<Expr>, | |
| op_span: Span, | |
| start_stmt: bool, | |
| ) -> PResult<'a, Box<Expr>> { | |
| let kind = IncDecRecovery { | |
| standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, | |
| op: IncOrDec::Inc, | |
| fixity: UnaryFixity::Post, | |
| }; | |
| self.recover_from_inc_dec(operand_expr, kind, op_span) | |
| } | |
| pub(super) fn recover_from_postfix_decrement( | |
| &mut self, | |
| operand_expr: Box<Expr>, | |
| op_span: Span, | |
| start_stmt: bool, | |
| ) -> PResult<'a, Box<Expr>> { | |
| let kind = IncDecRecovery { | |
| standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, | |
| op: IncOrDec::Dec, | |
| fixity: UnaryFixity::Post, | |
| }; | |
| self.recover_from_inc_dec(operand_expr, kind, op_span) | |
| } | |
| fn recover_from_inc_dec( | |
| &mut self, | |
| base: Box<Expr>, | |
| kind: IncDecRecovery, | |
| op_span: Span, | |
| ) -> PResult<'a, Box<Expr>> { | |
| let mut err = self.dcx().struct_span_err( | |
| op_span, | |
| format!("Rust has no {} {} operator", kind.fixity, kind.op.name()), | |
| ); | |
| err.span_label(op_span, format!("not a valid {} operator", kind.fixity)); | |
| let help_base_case = |mut err: Diag<'_, _>, base| { | |
| err.help(format!("use `{}= 1` instead", kind.op.chr())); | |
| err.emit(); | |
| Ok(base) | |
| }; | |
| // (pre, post) | |
| let spans = match kind.fixity { | |
| UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), | |
| UnaryFixity::Post => (base.span.shrink_to_lo(), op_span), | |
| }; | |
| match kind.standalone { | |
| IsStandalone::Standalone => { | |
| self.inc_dec_standalone_suggest(kind, spans).emit_verbose(&mut err) | |
| } | |
| IsStandalone::Subexpr => { | |
| let Ok(base_src) = self.span_to_snippet(base.span) else { | |
| return help_base_case(err, base); | |
| }; | |
| match kind.fixity { | |
| UnaryFixity::Pre => { | |
| self.prefix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) | |
| } | |
| UnaryFixity::Post => { | |
| // won't suggest since we can not handle the precedences | |
| // for example: `a + b++` has been parsed (a + b)++ and we can not suggest here | |
| if !matches!(base.kind, ExprKind::Binary(_, _, _)) { | |
| self.postfix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Err(err) | |
| } | |
| fn prefix_inc_dec_suggest( | |
| &mut self, | |
| base_src: String, | |
| kind: IncDecRecovery, | |
| (pre_span, post_span): (Span, Span), | |
| ) -> MultiSugg { | |
| MultiSugg { | |
| msg: format!("use `{}= 1` instead", kind.op.chr()), | |
| patches: vec![ | |
| (pre_span, "{ ".to_string()), | |
| (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)), | |
| ], | |
| applicability: Applicability::MachineApplicable, | |
| } | |
| } | |
| fn postfix_inc_dec_suggest( | |
| &mut self, | |
| base_src: String, | |
| kind: IncDecRecovery, | |
| (pre_span, post_span): (Span, Span), | |
| ) -> MultiSugg { | |
| let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; | |
| MultiSugg { | |
| msg: format!("use `{}= 1` instead", kind.op.chr()), | |
| patches: vec![ | |
| (pre_span, format!("{{ let {tmp_var} = ")), | |
| (post_span, format!("; {} {}= 1; {} }}", base_src, kind.op.chr(), tmp_var)), | |
| ], | |
| applicability: Applicability::HasPlaceholders, | |
| } | |
| } | |
| fn inc_dec_standalone_suggest( | |
| &mut self, | |
| kind: IncDecRecovery, | |
| (pre_span, post_span): (Span, Span), | |
| ) -> MultiSugg { | |
| let mut patches = Vec::new(); | |
| if !pre_span.is_empty() { | |
| patches.push((pre_span, String::new())); | |
| } | |
| patches.push((post_span, format!(" {}= 1", kind.op.chr()))); | |
| MultiSugg { | |
| msg: format!("use `{}= 1` instead", kind.op.chr()), | |
| patches, | |
| applicability: Applicability::MachineApplicable, | |
| } | |
| } | |
| /// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`. | |
| /// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem` | |
| /// tail, and combines them into a `<Ty>::AssocItem` expression/pattern/type. | |
| pub(super) fn maybe_recover_from_bad_qpath<T: RecoverQPath>( | |
| &mut self, | |
| base: T, | |
| ) -> PResult<'a, T> { | |
| // Do not add `::` to expected tokens. | |
| if self.may_recover() && self.token == token::PathSep { | |
| return self.recover_from_bad_qpath(base); | |
| } | |
| Ok(base) | |
| } | |
| #[cold] | |
| fn recover_from_bad_qpath<T: RecoverQPath>(&mut self, base: T) -> PResult<'a, T> { | |
| if let Some(ty) = base.to_ty() { | |
| return self.maybe_recover_from_bad_qpath_stage_2(ty.span, ty); | |
| } | |
| Ok(base) | |
| } | |
| /// Given an already parsed `Ty`, parses the `::AssocItem` tail and | |
| /// combines them into a `<Ty>::AssocItem` expression/pattern/type. | |
| pub(super) fn maybe_recover_from_bad_qpath_stage_2<T: RecoverQPath>( | |
| &mut self, | |
| ty_span: Span, | |
| ty: Box<Ty>, | |
| ) -> PResult<'a, T> { | |
| self.expect(exp!(PathSep))?; | |
| let mut path = ast::Path { segments: ThinVec::new(), span: DUMMY_SP, tokens: None }; | |
| self.parse_path_segments(&mut path.segments, T::PATH_STYLE, None)?; | |
| path.span = ty_span.to(self.prev_token.span); | |
| self.dcx().emit_err(BadQPathStage2 { | |
| span: ty_span, | |
| wrap: WrapType { lo: ty_span.shrink_to_lo(), hi: ty_span.shrink_to_hi() }, | |
| }); | |
| let path_span = ty_span.shrink_to_hi(); // Use an empty path since `position == 0`. | |
| Ok(T::recovered(Some(Box::new(QSelf { ty, path_span, position: 0 })), path)) | |
| } | |
| /// This function gets called in places where a semicolon is NOT expected and if there's a | |
| /// semicolon it emits the appropriate error and returns true. | |
| pub fn maybe_consume_incorrect_semicolon(&mut self, previous_item: Option<&Item>) -> bool { | |
| if self.token != TokenKind::Semi { | |
| return false; | |
| } | |
| // Check previous item to add it to the diagnostic, for example to say | |
| // `enum declarations are not followed by a semicolon` | |
| let err = match previous_item { | |
| Some(previous_item) => { | |
| let name = match previous_item.kind { | |
| // Say "braced struct" because tuple-structs and | |
| // braceless-empty-struct declarations do take a semicolon. | |
| ItemKind::Struct(..) => "braced struct", | |
| _ => previous_item.kind.descr(), | |
| }; | |
| IncorrectSemicolon { span: self.token.span, name, show_help: true } | |
| } | |
| None => IncorrectSemicolon { span: self.token.span, name: "", show_help: false }, | |
| }; | |
| self.dcx().emit_err(err); | |
| self.bump(); | |
| true | |
| } | |
| /// Creates a `Diag` for an unexpected token `t` and tries to recover if it is a | |
| /// closing delimiter. | |
| pub(super) fn unexpected_try_recover(&mut self, t: &TokenKind) -> PResult<'a, Recovered> { | |
| let token_str = pprust::token_kind_to_string(t); | |
| let this_token_str = super::token_descr(&self.token); | |
| let (prev_sp, sp) = match (&self.token.kind, self.subparser_name) { | |
| // Point at the end of the macro call when reaching end of macro arguments. | |
| (token::Eof, Some(_)) => { | |
| let sp = self.prev_token.span.shrink_to_hi(); | |
| (sp, sp) | |
| } | |
| // We don't want to point at the following span after DUMMY_SP. | |
| // This happens when the parser finds an empty TokenStream. | |
| _ if self.prev_token.span == DUMMY_SP => (self.token.span, self.token.span), | |
| // EOF, don't want to point at the following char, but rather the last token. | |
| (token::Eof, None) => (self.prev_token.span, self.token.span), | |
| _ => (self.prev_token.span.shrink_to_hi(), self.token.span), | |
| }; | |
| let msg = format!( | |
| "expected `{}`, found {}", | |
| token_str, | |
| match (&self.token.kind, self.subparser_name) { | |
| (token::Eof, Some(origin)) => format!("end of {origin}"), | |
| _ => this_token_str, | |
| }, | |
| ); | |
| let mut err = self.dcx().struct_span_err(sp, msg); | |
| let label_exp = format!("expected `{token_str}`"); | |
| let sm = self.psess.source_map(); | |
| if !sm.is_multiline(prev_sp.until(sp)) { | |
| // When the spans are in the same line, it means that the only content | |
| // between them is whitespace, point only at the found token. | |
| err.span_label(sp, label_exp); | |
| } else { | |
| err.span_label(prev_sp, label_exp); | |
| err.span_label(sp, "unexpected token"); | |
| } | |
| Err(err) | |
| } | |
| pub(super) fn expect_semi(&mut self) -> PResult<'a, ()> { | |
| if self.eat(exp!(Semi)) || self.recover_colon_as_semi() { | |
| return Ok(()); | |
| } | |
| self.expect(exp!(Semi)).map(drop) // Error unconditionally | |
| } | |
| pub(super) fn recover_colon_as_semi(&mut self) -> bool { | |
| let line_idx = |span: Span| { | |
| self.psess | |
| .source_map() | |
| .span_to_lines(span) | |
| .ok() | |
| .and_then(|lines| Some(lines.lines.get(0)?.line_index)) | |
| }; | |
| if self.may_recover() | |
| && self.token == token::Colon | |
| && self.look_ahead(1, |next| line_idx(self.token.span) < line_idx(next.span)) | |
| { | |
| self.dcx().emit_err(ColonAsSemi { span: self.token.span }); | |
| self.bump(); | |
| return true; | |
| } | |
| false | |
| } | |
| /// Consumes alternative await syntaxes like `await!(<expr>)`, `await <expr>`, | |
| /// `await? <expr>`, `await(<expr>)`, and `await { <expr> }`. | |
| pub(super) fn recover_incorrect_await_syntax( | |
| &mut self, | |
| await_sp: Span, | |
| ) -> PResult<'a, Box<Expr>> { | |
| let (hi, expr, is_question) = if self.token == token::Bang { | |
| // Handle `await!(<expr>)`. | |
| self.recover_await_macro()? | |
| } else { | |
| self.recover_await_prefix(await_sp)? | |
| }; | |
| let (sp, guar) = self.error_on_incorrect_await(await_sp, hi, &expr, is_question); | |
| let expr = self.mk_expr_err(await_sp.to(sp), guar); | |
| self.maybe_recover_from_bad_qpath(expr) | |
| } | |
| fn recover_await_macro(&mut self) -> PResult<'a, (Span, Box<Expr>, bool)> { | |
| self.expect(exp!(Bang))?; | |
| self.expect(exp!(OpenParen))?; | |
| let expr = self.parse_expr()?; | |
| self.expect(exp!(CloseParen))?; | |
| Ok((self.prev_token.span, expr, false)) | |
| } | |
| fn recover_await_prefix(&mut self, await_sp: Span) -> PResult<'a, (Span, Box<Expr>, bool)> { | |
| let is_question = self.eat(exp!(Question)); // Handle `await? <expr>`. | |
| let expr = if self.token == token::OpenBrace { | |
| // Handle `await { <expr> }`. | |
| // This needs to be handled separately from the next arm to avoid | |
| // interpreting `await { <expr> }?` as `<expr>?.await`. | |
| self.parse_expr_block(None, self.token.span, BlockCheckMode::Default) | |
| } else { | |
| self.parse_expr() | |
| } | |
| .map_err(|mut err| { | |
| err.span_label(await_sp, format!("while parsing this incorrect await expression")); | |
| err | |
| })?; | |
| Ok((expr.span, expr, is_question)) | |
| } | |
| fn error_on_incorrect_await( | |
| &self, | |
| lo: Span, | |
| hi: Span, | |
| expr: &Expr, | |
| is_question: bool, | |
| ) -> (Span, ErrorGuaranteed) { | |
| let span = lo.to(hi); | |
| let guar = self.dcx().emit_err(IncorrectAwait { | |
| span, | |
| suggestion: AwaitSuggestion { | |
| removal: lo.until(expr.span), | |
| dot_await: expr.span.shrink_to_hi(), | |
| question_mark: if is_question { "?" } else { "" }, | |
| }, | |
| }); | |
| (span, guar) | |
| } | |
| /// If encountering `future.await()`, consumes and emits an error. | |
| pub(super) fn recover_from_await_method_call(&mut self) { | |
| if self.token == token::OpenParen && self.look_ahead(1, |t| t == &token::CloseParen) { | |
| // future.await() | |
| let lo = self.token.span; | |
| self.bump(); // ( | |
| let span = lo.to(self.token.span); | |
| self.bump(); // ) | |
| self.dcx().emit_err(IncorrectUseOfAwait { span }); | |
| } | |
| } | |
| /// | |
| /// If encountering `x.use()`, consumes and emits an error. | |
| pub(super) fn recover_from_use(&mut self) { | |
| if self.token == token::OpenParen && self.look_ahead(1, |t| t == &token::CloseParen) { | |
| // var.use() | |
| let lo = self.token.span; | |
| self.bump(); // ( | |
| let span = lo.to(self.token.span); | |
| self.bump(); // ) | |
| self.dcx().emit_err(IncorrectUseOfUse { span }); | |
| } | |
| } | |
| pub(super) fn try_macro_suggestion(&mut self) -> PResult<'a, Box<Expr>> { | |
| let is_try = self.token.is_keyword(kw::Try); | |
| let is_questionmark = self.look_ahead(1, |t| t == &token::Bang); //check for ! | |
| let is_open = self.look_ahead(2, |t| t == &token::OpenParen); //check for ( | |
| if is_try && is_questionmark && is_open { | |
| let lo = self.token.span; | |
| self.bump(); //remove try | |
| self.bump(); //remove ! | |
| let try_span = lo.to(self.token.span); //we take the try!( span | |
| self.bump(); //remove ( | |
| let is_empty = self.token == token::CloseParen; //check if the block is empty | |
| self.consume_block(exp!(OpenParen), exp!(CloseParen), ConsumeClosingDelim::No); //eat the block | |
| let hi = self.token.span; | |
| self.bump(); //remove ) | |
| let mut err = self.dcx().struct_span_err(lo.to(hi), "use of deprecated `try` macro"); | |
| err.note("in the 2018 edition `try` is a reserved keyword, and the `try!()` macro is deprecated"); | |
| let prefix = if is_empty { "" } else { "alternatively, " }; | |
| if !is_empty { | |
| err.multipart_suggestion( | |
| "you can use the `?` operator instead", | |
| vec![(try_span, "".to_owned()), (hi, "?".to_owned())], | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| err.span_suggestion(lo.shrink_to_lo(), format!("{prefix}you can still access the deprecated `try!()` macro using the \"raw identifier\" syntax"), "r#", Applicability::MachineApplicable); | |
| let guar = err.emit(); | |
| Ok(self.mk_expr_err(lo.to(hi), guar)) | |
| } else { | |
| Err(self.expected_expression_found()) // The user isn't trying to invoke the try! macro | |
| } | |
| } | |
| /// When trying to close a generics list and encountering code like | |
| /// ```text | |
| /// impl<S: Into<std::borrow::Cow<'static, str>> From<S> for Canonical {} | |
| /// // ^ missing > here | |
| /// ``` | |
| /// we provide a structured suggestion on the error from `expect_gt`. | |
| pub(super) fn expect_gt_or_maybe_suggest_closing_generics( | |
| &mut self, | |
| params: &[ast::GenericParam], | |
| ) -> PResult<'a, ()> { | |
| let Err(mut err) = self.expect_gt() else { | |
| return Ok(()); | |
| }; | |
| // Attempt to find places where a missing `>` might belong. | |
| if let [.., ast::GenericParam { bounds, .. }] = params | |
| && let Some(poly) = bounds | |
| .iter() | |
| .filter_map(|bound| match bound { | |
| ast::GenericBound::Trait(poly) => Some(poly), | |
| _ => None, | |
| }) | |
| .next_back() | |
| { | |
| err.span_suggestion_verbose( | |
| poly.span.shrink_to_hi(), | |
| "you might have meant to end the type parameters here", | |
| ">", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| Err(err) | |
| } | |
| pub(super) fn recover_seq_parse_error( | |
| &mut self, | |
| open: ExpTokenPair, | |
| close: ExpTokenPair, | |
| lo: Span, | |
| err: Diag<'a>, | |
| ) -> Box<Expr> { | |
| let guar = err.emit(); | |
| // Recover from parse error, callers expect the closing delim to be consumed. | |
| self.consume_block(open, close, ConsumeClosingDelim::Yes); | |
| self.mk_expr(lo.to(self.prev_token.span), ExprKind::Err(guar)) | |
| } | |
| /// Eats tokens until we can be relatively sure we reached the end of the | |
| /// statement. This is something of a best-effort heuristic. | |
| /// | |
| /// We terminate when we find an unmatched `}` (without consuming it). | |
| pub(super) fn recover_stmt(&mut self) { | |
| self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore) | |
| } | |
| /// If `break_on_semi` is `Break`, then we will stop consuming tokens after | |
| /// finding (and consuming) a `;` outside of `{}` or `[]` (note that this is | |
| /// approximate -- it can mean we break too early due to macros, but that | |
| /// should only lead to sub-optimal recovery, not inaccurate parsing). | |
| /// | |
| /// If `break_on_block` is `Break`, then we will stop consuming tokens | |
| /// after finding (and consuming) a brace-delimited block. | |
| pub(super) fn recover_stmt_( | |
| &mut self, | |
| break_on_semi: SemiColonMode, | |
| break_on_block: BlockMode, | |
| ) { | |
| let mut brace_depth = 0; | |
| let mut bracket_depth = 0; | |
| let mut in_block = false; | |
| debug!("recover_stmt_ enter loop (semi={:?}, block={:?})", break_on_semi, break_on_block); | |
| loop { | |
| debug!("recover_stmt_ loop {:?}", self.token); | |
| match self.token.kind { | |
| token::OpenBrace => { | |
| brace_depth += 1; | |
| self.bump(); | |
| if break_on_block == BlockMode::Break && brace_depth == 1 && bracket_depth == 0 | |
| { | |
| in_block = true; | |
| } | |
| } | |
| token::OpenBracket => { | |
| bracket_depth += 1; | |
| self.bump(); | |
| } | |
| token::CloseBrace => { | |
| if brace_depth == 0 { | |
| debug!("recover_stmt_ return - close delim {:?}", self.token); | |
| break; | |
| } | |
| brace_depth -= 1; | |
| self.bump(); | |
| if in_block && bracket_depth == 0 && brace_depth == 0 { | |
| debug!("recover_stmt_ return - block end {:?}", self.token); | |
| break; | |
| } | |
| } | |
| token::CloseBracket => { | |
| bracket_depth -= 1; | |
| if bracket_depth < 0 { | |
| bracket_depth = 0; | |
| } | |
| self.bump(); | |
| } | |
| token::Eof => { | |
| debug!("recover_stmt_ return - Eof"); | |
| break; | |
| } | |
| token::Semi => { | |
| self.bump(); | |
| if break_on_semi == SemiColonMode::Break | |
| && brace_depth == 0 | |
| && bracket_depth == 0 | |
| { | |
| debug!("recover_stmt_ return - Semi"); | |
| break; | |
| } | |
| } | |
| token::Comma | |
| if break_on_semi == SemiColonMode::Comma | |
| && brace_depth == 0 | |
| && bracket_depth == 0 => | |
| { | |
| break; | |
| } | |
| _ => self.bump(), | |
| } | |
| } | |
| } | |
| pub(super) fn check_for_for_in_in_typo(&mut self, in_span: Span) { | |
| if self.eat_keyword(exp!(In)) { | |
| // a common typo: `for _ in in bar {}` | |
| self.dcx().emit_err(InInTypo { | |
| span: self.prev_token.span, | |
| sugg_span: in_span.until(self.prev_token.span), | |
| }); | |
| } | |
| } | |
| pub(super) fn eat_incorrect_doc_comment_for_param_type(&mut self) { | |
| if let token::DocComment(..) = self.token.kind { | |
| self.dcx().emit_err(DocCommentOnParamType { span: self.token.span }); | |
| self.bump(); | |
| } else if self.token == token::Pound && self.look_ahead(1, |t| *t == token::OpenBracket) { | |
| let lo = self.token.span; | |
| // Skip every token until next possible arg. | |
| while self.token != token::CloseBracket { | |
| self.bump(); | |
| } | |
| let sp = lo.to(self.token.span); | |
| self.bump(); | |
| self.dcx().emit_err(AttributeOnParamType { span: sp }); | |
| } | |
| } | |
| pub(super) fn parameter_without_type( | |
| &mut self, | |
| err: &mut Diag<'_>, | |
| pat: Box<ast::Pat>, | |
| require_name: bool, | |
| first_param: bool, | |
| fn_parse_mode: &crate::parser::item::FnParseMode, | |
| ) -> Option<Ident> { | |
| // If we find a pattern followed by an identifier, it could be an (incorrect) | |
| // C-style parameter declaration. | |
| if self.check_ident() | |
| && self.look_ahead(1, |t| *t == token::Comma || *t == token::CloseParen) | |
| { | |
| // `fn foo(String s) {}` | |
| let ident = self.parse_ident_common(true).unwrap(); | |
| let span = pat.span.with_hi(ident.span.hi()); | |
| err.span_suggestion( | |
| span, | |
| "declare the type after the parameter binding", | |
| "<identifier>: <type>", | |
| Applicability::HasPlaceholders, | |
| ); | |
| return Some(ident); | |
| } else if require_name | |
| && (self.token == token::Comma | |
| || self.token == token::Lt | |
| || self.token == token::CloseParen) | |
| { | |
| let maybe_emit_anon_params_note = |this: &mut Self, err: &mut Diag<'_>| { | |
| let ed = this.token.span.with_neighbor(this.prev_token.span).edition(); | |
| if matches!(fn_parse_mode.context, crate::parser::item::FnContext::Trait) | |
| && (fn_parse_mode.req_name)(ed, IsDotDotDot::No) | |
| { | |
| err.note("anonymous parameters are removed in the 2018 edition (see RFC 1685)"); | |
| } | |
| }; | |
| let (ident, self_sugg, param_sugg, type_sugg, self_span, param_span, type_span) = | |
| match pat.kind { | |
| PatKind::Ident(_, ident, _) => ( | |
| ident, | |
| "self: ", | |
| ": TypeName".to_string(), | |
| "_: ", | |
| pat.span.shrink_to_lo(), | |
| pat.span.shrink_to_hi(), | |
| pat.span.shrink_to_lo(), | |
| ), | |
| PatKind::Ref(ref inner_pat, _, _) | |
| // Fix suggestions for multi-reference `self` parameters (e.g. `&&&self`) | |
| // cc: https://github.com/rust-lang/rust/pull/146305 | |
| if let PatKind::Ref(_, _, _) = &inner_pat.kind | |
| && let PatKind::Path(_, path) = &pat.peel_refs().kind | |
| && let [a, ..] = path.segments.as_slice() | |
| && a.ident.name == kw::SelfLower => | |
| { | |
| let mut inner = inner_pat; | |
| let mut span_vec = vec![pat.span]; | |
| while let PatKind::Ref(ref inner_type, _, _) = inner.kind { | |
| inner = inner_type; | |
| span_vec.push(inner.span.shrink_to_lo()); | |
| } | |
| let span = match span_vec.len() { | |
| // Should be unreachable: match guard ensures at least 2 references | |
| 0 | 1 => unreachable!(), | |
| 2 => span_vec[0].until(inner_pat.span.shrink_to_lo()), | |
| _ => span_vec[0].until(span_vec[span_vec.len() - 2].shrink_to_lo()), | |
| }; | |
| err.span_suggestion_verbose( | |
| span, | |
| "`self` should be `self`, `&self` or `&mut self`, consider removing extra references", | |
| "".to_string(), | |
| Applicability::MachineApplicable, | |
| ); | |
| return None; | |
| } | |
| // Also catches `fn foo(&a)`. | |
| PatKind::Ref(ref inner_pat, pinned, mutab) | |
| if let PatKind::Ident(_, ident, _) = inner_pat.clone().kind => | |
| { | |
| let mutab = pinned.prefix_str(mutab); | |
| ( | |
| ident, | |
| "self: ", | |
| format!("{ident}: &{mutab}TypeName"), | |
| "_: ", | |
| pat.span.shrink_to_lo(), | |
| pat.span, | |
| pat.span.shrink_to_lo(), | |
| ) | |
| } | |
| _ => { | |
| // Otherwise, try to get a type and emit a suggestion. | |
| if let Some(_) = pat.to_ty() { | |
| err.span_suggestion_verbose( | |
| pat.span.shrink_to_lo(), | |
| "explicitly ignore the parameter name", | |
| "_: ".to_string(), | |
| Applicability::MachineApplicable, | |
| ); | |
| maybe_emit_anon_params_note(self, err); | |
| } | |
| return None; | |
| } | |
| }; | |
| // `fn foo(a, b) {}`, `fn foo(a<x>, b<y>) {}` or `fn foo(usize, usize) {}` | |
| if first_param | |
| // Only when the fn is a method, we emit this suggestion. | |
| && matches!( | |
| fn_parse_mode.context, | |
| FnContext::Trait | FnContext::Impl | |
| ) | |
| { | |
| err.span_suggestion_verbose( | |
| self_span, | |
| "if this is a `self` type, give it a parameter name", | |
| self_sugg, | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| // Avoid suggesting that `fn foo(HashMap<u32>)` is fixed with a change to | |
| // `fn foo(HashMap: TypeName<u32>)`. | |
| if self.token != token::Lt { | |
| err.span_suggestion_verbose( | |
| param_span, | |
| "if this is a parameter name, give it a type", | |
| param_sugg, | |
| Applicability::HasPlaceholders, | |
| ); | |
| } | |
| err.span_suggestion_verbose( | |
| type_span, | |
| "if this is a type, explicitly ignore the parameter name", | |
| type_sugg, | |
| Applicability::MachineApplicable, | |
| ); | |
| maybe_emit_anon_params_note(self, err); | |
| // Don't attempt to recover by using the `X` in `X<Y>` as the parameter name. | |
| return if self.token == token::Lt { None } else { Some(ident) }; | |
| } | |
| None | |
| } | |
| #[cold] | |
| pub(super) fn recover_arg_parse(&mut self) -> PResult<'a, (Box<ast::Pat>, Box<ast::Ty>)> { | |
| let pat = self.parse_pat_no_top_alt(Some(Expected::ArgumentName), None)?; | |
| self.expect(exp!(Colon))?; | |
| let ty = self.parse_ty()?; | |
| self.dcx().emit_err(PatternMethodParamWithoutBody { span: pat.span }); | |
| // Pretend the pattern is `_`, to avoid duplicate errors from AST validation. | |
| let pat = Box::new(Pat { | |
| kind: PatKind::Wild, | |
| span: pat.span, | |
| id: ast::DUMMY_NODE_ID, | |
| tokens: None, | |
| }); | |
| Ok((pat, ty)) | |
| } | |
| pub(super) fn recover_bad_self_param(&mut self, mut param: Param) -> PResult<'a, Param> { | |
| let span = param.pat.span; | |
| let guar = self.dcx().emit_err(SelfParamNotFirst { span }); | |
| param.ty.kind = TyKind::Err(guar); | |
| Ok(param) | |
| } | |
| pub(super) fn consume_block( | |
| &mut self, | |
| open: ExpTokenPair, | |
| close: ExpTokenPair, | |
| consume_close: ConsumeClosingDelim, | |
| ) { | |
| let mut brace_depth = 0; | |
| loop { | |
| if self.eat(open) { | |
| brace_depth += 1; | |
| } else if self.check(close) { | |
| if brace_depth == 0 { | |
| if let ConsumeClosingDelim::Yes = consume_close { | |
| // Some of the callers of this method expect to be able to parse the | |
| // closing delimiter themselves, so we leave it alone. Otherwise we advance | |
| // the parser. | |
| self.bump(); | |
| } | |
| return; | |
| } else { | |
| self.bump(); | |
| brace_depth -= 1; | |
| continue; | |
| } | |
| } else if self.token == token::Eof { | |
| return; | |
| } else { | |
| self.bump(); | |
| } | |
| } | |
| } | |
| pub(super) fn expected_expression_found(&self) -> Diag<'a> { | |
| let (span, msg) = match (&self.token.kind, self.subparser_name) { | |
| (&token::Eof, Some(origin)) => { | |
| let sp = self.prev_token.span.shrink_to_hi(); | |
| (sp, format!("expected expression, found end of {origin}")) | |
| } | |
| _ => ( | |
| self.token.span, | |
| format!("expected expression, found {}", super::token_descr(&self.token)), | |
| ), | |
| }; | |
| let mut err = self.dcx().struct_span_err(span, msg); | |
| let sp = self.psess.source_map().start_point(self.token.span); | |
| if let Some(sp) = self.psess.ambiguous_block_expr_parse.borrow().get(&sp) { | |
| err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); | |
| } | |
| err.span_label(span, "expected expression"); | |
| err | |
| } | |
| fn consume_tts( | |
| &mut self, | |
| mut acc: i64, // `i64` because malformed code can have more closing delims than opening. | |
| // Not using `FxHashMap` due to `token::TokenKind: !Eq + !Hash`. | |
| modifier: &[(token::TokenKind, i64)], | |
| ) { | |
| while acc > 0 { | |
| if let Some((_, val)) = modifier.iter().find(|(t, _)| self.token == *t) { | |
| acc += *val; | |
| } | |
| if self.token == token::Eof { | |
| break; | |
| } | |
| self.bump(); | |
| } | |
| } | |
| /// Replace duplicated recovered parameters with `_` pattern to avoid unnecessary errors. | |
| /// | |
| /// This is necessary because at this point we don't know whether we parsed a function with | |
| /// anonymous parameters or a function with names but no types. In order to minimize | |
| /// unnecessary errors, we assume the parameters are in the shape of `fn foo(a, b, c)` where | |
| /// the parameters are *names* (so we don't emit errors about not being able to find `b` in | |
| /// the local scope), but if we find the same name multiple times, like in `fn foo(i8, i8)`, | |
| /// we deduplicate them to not complain about duplicated parameter names. | |
| pub(super) fn deduplicate_recovered_params_names(&self, fn_inputs: &mut ThinVec<Param>) { | |
| let mut seen_inputs = FxHashSet::default(); | |
| for input in fn_inputs.iter_mut() { | |
| let opt_ident = if let (PatKind::Ident(_, ident, _), TyKind::Err(_)) = | |
| (&input.pat.kind, &input.ty.kind) | |
| { | |
| Some(*ident) | |
| } else { | |
| None | |
| }; | |
| if let Some(ident) = opt_ident { | |
| if seen_inputs.contains(&ident) { | |
| input.pat.kind = PatKind::Wild; | |
| } | |
| seen_inputs.insert(ident); | |
| } | |
| } | |
| } | |
| /// Handle encountering a symbol in a generic argument list that is not a `,` or `>`. In this | |
| /// case, we emit an error and try to suggest enclosing a const argument in braces if it looks | |
| /// like the user has forgotten them. | |
| pub(super) fn handle_ambiguous_unbraced_const_arg( | |
| &mut self, | |
| args: &mut ThinVec<AngleBracketedArg>, | |
| ) -> PResult<'a, bool> { | |
| // If we haven't encountered a closing `>`, then the argument is malformed. | |
| // It's likely that the user has written a const expression without enclosing it | |
| // in braces, so we try to recover here. | |
| let arg = args.pop().unwrap(); | |
| // FIXME: for some reason using `unexpected` or `expected_one_of_not_found` has | |
| // adverse side-effects to subsequent errors and seems to advance the parser. | |
| // We are causing this error here exclusively in case that a `const` expression | |
| // could be recovered from the current parser state, even if followed by more | |
| // arguments after a comma. | |
| let mut err = self.dcx().struct_span_err( | |
| self.token.span, | |
| format!("expected one of `,` or `>`, found {}", super::token_descr(&self.token)), | |
| ); | |
| err.span_label(self.token.span, "expected one of `,` or `>`"); | |
| match self.recover_const_arg(arg.span(), err) { | |
| Ok(arg) => { | |
| args.push(AngleBracketedArg::Arg(arg)); | |
| if self.eat(exp!(Comma)) { | |
| return Ok(true); // Continue | |
| } | |
| } | |
| Err(err) => { | |
| args.push(arg); | |
| // We will emit a more generic error later. | |
| err.delay_as_bug(); | |
| } | |
| } | |
| Ok(false) // Don't continue. | |
| } | |
| fn recover_const_param_decl(&mut self, ty_generics: Option<&Generics>) -> Option<GenericArg> { | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| let param = match self.parse_const_param(AttrVec::new()) { | |
| Ok(param) => param, | |
| Err(err) => { | |
| err.cancel(); | |
| self.restore_snapshot(snapshot); | |
| return None; | |
| } | |
| }; | |
| let ident = param.ident.to_string(); | |
| let sugg = match (ty_generics, self.psess.source_map().span_to_snippet(param.span())) { | |
| (Some(Generics { params, span: impl_generics, .. }), Ok(snippet)) => { | |
| Some(match ¶ms[..] { | |
| [] => UnexpectedConstParamDeclarationSugg::AddParam { | |
| impl_generics: *impl_generics, | |
| incorrect_decl: param.span(), | |
| snippet, | |
| ident, | |
| }, | |
| [.., generic] => UnexpectedConstParamDeclarationSugg::AppendParam { | |
| impl_generics_end: generic.span().shrink_to_hi(), | |
| incorrect_decl: param.span(), | |
| snippet, | |
| ident, | |
| }, | |
| }) | |
| } | |
| _ => None, | |
| }; | |
| let guar = | |
| self.dcx().emit_err(UnexpectedConstParamDeclaration { span: param.span(), sugg }); | |
| let value = self.mk_expr_err(param.span(), guar); | |
| Some(GenericArg::Const(AnonConst { | |
| id: ast::DUMMY_NODE_ID, | |
| value, | |
| mgca_disambiguation: MgcaDisambiguation::Direct, | |
| })) | |
| } | |
| pub(super) fn recover_const_param_declaration( | |
| &mut self, | |
| ty_generics: Option<&Generics>, | |
| ) -> PResult<'a, Option<GenericArg>> { | |
| // We have to check for a few different cases. | |
| if let Some(arg) = self.recover_const_param_decl(ty_generics) { | |
| return Ok(Some(arg)); | |
| } | |
| // We haven't consumed `const` yet. | |
| let start = self.token.span; | |
| self.bump(); // `const` | |
| // Detect and recover from the old, pre-RFC2000 syntax for const generics. | |
| let mut err = UnexpectedConstInGenericParam { span: start, to_remove: None }; | |
| if self.check_const_arg() { | |
| err.to_remove = Some(start.until(self.token.span)); | |
| self.dcx().emit_err(err); | |
| Ok(Some(GenericArg::Const(self.parse_const_arg()?))) | |
| } else { | |
| let after_kw_const = self.token.span; | |
| self.recover_const_arg(after_kw_const, self.dcx().create_err(err)).map(Some) | |
| } | |
| } | |
| /// Try to recover from possible generic const argument without `{` and `}`. | |
| /// | |
| /// When encountering code like `foo::< bar + 3 >` or `foo::< bar - baz >` we suggest | |
| /// `foo::<{ bar + 3 }>` and `foo::<{ bar - baz }>`, respectively. We only provide a suggestion | |
| /// if we think that the resulting expression would be well formed. | |
| pub(super) fn recover_const_arg( | |
| &mut self, | |
| start: Span, | |
| mut err: Diag<'a>, | |
| ) -> PResult<'a, GenericArg> { | |
| let is_op_or_dot = AssocOp::from_token(&self.token) | |
| .and_then(|op| { | |
| if let AssocOp::Binary( | |
| BinOpKind::Gt | |
| | BinOpKind::Lt | |
| | BinOpKind::Shr | |
| | BinOpKind::Ge | |
| ) | |
| // Don't recover from `foo::<bar = baz>`, because this could be an attempt to | |
| // assign a value to a defaulted generic parameter. | |
| | AssocOp::Assign | |
| | AssocOp::AssignOp(_) = op | |
| { | |
| None | |
| } else { | |
| Some(op) | |
| } | |
| }) | |
| .is_some() | |
| || self.token == TokenKind::Dot; | |
| // This will be true when a trait object type `Foo +` or a path which was a `const fn` with | |
| // type params has been parsed. | |
| let was_op = matches!(self.prev_token.kind, token::Plus | token::Shr | token::Gt); | |
| if !is_op_or_dot && !was_op { | |
| // We perform these checks and early return to avoid taking a snapshot unnecessarily. | |
| return Err(err); | |
| } | |
| let snapshot = self.create_snapshot_for_diagnostic(); | |
| if is_op_or_dot { | |
| self.bump(); | |
| } | |
| match (|| { | |
| let attrs = self.parse_outer_attributes()?; | |
| self.parse_expr_res(Restrictions::CONST_EXPR, attrs) | |
| })() { | |
| Ok((expr, _)) => { | |
| // Find a mistake like `MyTrait<Assoc == S::Assoc>`. | |
| if snapshot.token == token::EqEq { | |
| err.span_suggestion( | |
| snapshot.token.span, | |
| "if you meant to use an associated type binding, replace `==` with `=`", | |
| "=", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| let guar = err.emit(); | |
| let value = self.mk_expr_err(start.to(expr.span), guar); | |
| return Ok(GenericArg::Const(AnonConst { | |
| id: ast::DUMMY_NODE_ID, | |
| value, | |
| mgca_disambiguation: MgcaDisambiguation::Direct, | |
| })); | |
| } else if snapshot.token == token::Colon | |
| && expr.span.lo() == snapshot.token.span.hi() | |
| && matches!(expr.kind, ExprKind::Path(..)) | |
| { | |
| // Find a mistake like "foo::var:A". | |
| err.span_suggestion( | |
| snapshot.token.span, | |
| "write a path separator here", | |
| "::", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| let guar = err.emit(); | |
| return Ok(GenericArg::Type( | |
| self.mk_ty(start.to(expr.span), TyKind::Err(guar)), | |
| )); | |
| } else if self.token == token::Comma || self.token.kind.should_end_const_arg() { | |
| // Avoid the following output by checking that we consumed a full const arg: | |
| // help: expressions must be enclosed in braces to be used as const generic | |
| // arguments | |
| // | | |
| // LL | let sr: Vec<{ (u32, _, _) = vec![] }; | |
| // | ^ ^ | |
| return Ok(self.dummy_const_arg_needs_braces(err, start.to(expr.span))); | |
| } | |
| } | |
| Err(err) => { | |
| err.cancel(); | |
| } | |
| } | |
| self.restore_snapshot(snapshot); | |
| Err(err) | |
| } | |
| /// Try to recover from an unbraced const argument whose first token [could begin a type][ty]. | |
| /// | |
| /// [ty]: token::Token::can_begin_type | |
| pub(crate) fn recover_unbraced_const_arg_that_can_begin_ty( | |
| &mut self, | |
| mut snapshot: SnapshotParser<'a>, | |
| ) -> Option<Box<ast::Expr>> { | |
| match (|| { | |
| let attrs = self.parse_outer_attributes()?; | |
| snapshot.parse_expr_res(Restrictions::CONST_EXPR, attrs) | |
| })() { | |
| // Since we don't know the exact reason why we failed to parse the type or the | |
| // expression, employ a simple heuristic to weed out some pathological cases. | |
| Ok((expr, _)) if let token::Comma | token::Gt = snapshot.token.kind => { | |
| self.restore_snapshot(snapshot); | |
| Some(expr) | |
| } | |
| Ok(_) => None, | |
| Err(err) => { | |
| err.cancel(); | |
| None | |
| } | |
| } | |
| } | |
| /// Creates a dummy const argument, and reports that the expression must be enclosed in braces | |
| pub(super) fn dummy_const_arg_needs_braces(&self, mut err: Diag<'a>, span: Span) -> GenericArg { | |
| err.multipart_suggestion( | |
| "expressions must be enclosed in braces to be used as const generic \ | |
| arguments", | |
| vec![(span.shrink_to_lo(), "{ ".to_string()), (span.shrink_to_hi(), " }".to_string())], | |
| Applicability::MaybeIncorrect, | |
| ); | |
| let guar = err.emit(); | |
| let value = self.mk_expr_err(span, guar); | |
| GenericArg::Const(AnonConst { | |
| id: ast::DUMMY_NODE_ID, | |
| value, | |
| mgca_disambiguation: MgcaDisambiguation::Direct, | |
| }) | |
| } | |
| /// Some special error handling for the "top-level" patterns in a match arm, | |
| /// `for` loop, `let`, &c. (in contrast to subpatterns within such). | |
| #[cold] | |
| pub(crate) fn recover_colon_colon_in_pat_typo( | |
| &mut self, | |
| mut first_pat: Pat, | |
| expected: Option<Expected>, | |
| ) -> Pat { | |
| if token::Colon != self.token.kind { | |
| return first_pat; | |
| } | |
| // The pattern looks like it might be a path with a `::` -> `:` typo: | |
| // `match foo { bar:baz => {} }` | |
| let colon_span = self.token.span; | |
| // We only emit "unexpected `:`" error here if we can successfully parse the | |
| // whole pattern correctly in that case. | |
| let mut snapshot_pat = self.create_snapshot_for_diagnostic(); | |
| let mut snapshot_type = self.create_snapshot_for_diagnostic(); | |
| // Create error for "unexpected `:`". | |
| match self.expected_one_of_not_found(&[], &[]) { | |
| Err(mut err) => { | |
| // Skip the `:`. | |
| snapshot_pat.bump(); | |
| snapshot_type.bump(); | |
| match snapshot_pat.parse_pat_no_top_alt(expected, None) { | |
| Err(inner_err) => { | |
| inner_err.cancel(); | |
| } | |
| Ok(mut pat) => { | |
| // We've parsed the rest of the pattern. | |
| let new_span = first_pat.span.to(pat.span); | |
| let mut show_sugg = false; | |
| // Try to construct a recovered pattern. | |
| match &mut pat.kind { | |
| PatKind::Struct(qself @ None, path, ..) | |
| | PatKind::TupleStruct(qself @ None, path, _) | |
| | PatKind::Path(qself @ None, path) => match &first_pat.kind { | |
| PatKind::Ident(_, ident, _) => { | |
| path.segments.insert(0, PathSegment::from_ident(*ident)); | |
| path.span = new_span; | |
| show_sugg = true; | |
| first_pat = pat; | |
| } | |
| PatKind::Path(old_qself, old_path) => { | |
| path.segments = old_path | |
| .segments | |
| .iter() | |
| .cloned() | |
| .chain(take(&mut path.segments)) | |
| .collect(); | |
| path.span = new_span; | |
| *qself = old_qself.clone(); | |
| first_pat = pat; | |
| show_sugg = true; | |
| } | |
| _ => {} | |
| }, | |
| PatKind::Ident(BindingMode::NONE, ident, None) => { | |
| match &first_pat.kind { | |
| PatKind::Ident(_, old_ident, _) => { | |
| let path = PatKind::Path( | |
| None, | |
| Path { | |
| span: new_span, | |
| segments: thin_vec![ | |
| PathSegment::from_ident(*old_ident), | |
| PathSegment::from_ident(*ident), | |
| ], | |
| tokens: None, | |
| }, | |
| ); | |
| first_pat = self.mk_pat(new_span, path); | |
| show_sugg = true; | |
| } | |
| PatKind::Path(old_qself, old_path) => { | |
| let mut segments = old_path.segments.clone(); | |
| segments.push(PathSegment::from_ident(*ident)); | |
| let path = PatKind::Path( | |
| old_qself.clone(), | |
| Path { span: new_span, segments, tokens: None }, | |
| ); | |
| first_pat = self.mk_pat(new_span, path); | |
| show_sugg = true; | |
| } | |
| _ => {} | |
| } | |
| } | |
| _ => {} | |
| } | |
| if show_sugg { | |
| err.span_suggestion_verbose( | |
| colon_span.until(self.look_ahead(1, |t| t.span)), | |
| "maybe write a path separator here", | |
| "::", | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } else { | |
| first_pat = self.mk_pat( | |
| new_span, | |
| PatKind::Err( | |
| self.dcx() | |
| .span_delayed_bug(colon_span, "recovered bad path pattern"), | |
| ), | |
| ); | |
| } | |
| self.restore_snapshot(snapshot_pat); | |
| } | |
| } | |
| match snapshot_type.parse_ty() { | |
| Err(inner_err) => { | |
| inner_err.cancel(); | |
| } | |
| Ok(ty) => { | |
| err.span_label(ty.span, "specifying the type of a pattern isn't supported"); | |
| self.restore_snapshot(snapshot_type); | |
| let new_span = first_pat.span.to(ty.span); | |
| first_pat = | |
| self.mk_pat( | |
| new_span, | |
| PatKind::Err(self.dcx().span_delayed_bug( | |
| colon_span, | |
| "recovered bad pattern with type", | |
| )), | |
| ); | |
| } | |
| } | |
| err.emit(); | |
| } | |
| _ => { | |
| // Carry on as if we had not done anything. This should be unreachable. | |
| } | |
| }; | |
| first_pat | |
| } | |
| /// If `loop_header` is `Some` and an unexpected block label is encountered, | |
| /// it is suggested to be moved just before `loop_header`, else it is suggested to be removed. | |
| pub(crate) fn maybe_recover_unexpected_block_label( | |
| &mut self, | |
| loop_header: Option<Span>, | |
| ) -> bool { | |
| // Check for `'a : {` | |
| if !(self.check_lifetime() | |
| && self.look_ahead(1, |t| *t == token::Colon) | |
| && self.look_ahead(2, |t| *t == token::OpenBrace)) | |
| { | |
| return false; | |
| } | |
| let label = self.eat_label().expect("just checked if a label exists"); | |
| self.bump(); // eat `:` | |
| let span = label.ident.span.to(self.prev_token.span); | |
| let mut diag = self | |
| .dcx() | |
| .struct_span_err(span, "block label not supported here") | |
| .with_span_label(span, "not supported here"); | |
| if let Some(loop_header) = loop_header { | |
| diag.multipart_suggestion( | |
| "if you meant to label the loop, move this label before the loop", | |
| vec![ | |
| (label.ident.span.until(self.token.span), String::from("")), | |
| (loop_header.shrink_to_lo(), format!("{}: ", label.ident)), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| } else { | |
| diag.tool_only_span_suggestion( | |
| label.ident.span.until(self.token.span), | |
| "remove this block label", | |
| "", | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| diag.emit(); | |
| true | |
| } | |
| /// Some special error handling for the "top-level" patterns in a match arm, | |
| /// `for` loop, `let`, &c. (in contrast to subpatterns within such). | |
| pub(crate) fn maybe_recover_unexpected_comma( | |
| &mut self, | |
| lo: Span, | |
| rt: CommaRecoveryMode, | |
| ) -> PResult<'a, ()> { | |
| if self.token != token::Comma { | |
| return Ok(()); | |
| } | |
| self.recover_unexpected_comma(lo, rt) | |
| } | |
| #[cold] | |
| fn recover_unexpected_comma(&mut self, lo: Span, rt: CommaRecoveryMode) -> PResult<'a, ()> { | |
| // An unexpected comma after a top-level pattern is a clue that the | |
| // user (perhaps more accustomed to some other language) forgot the | |
| // parentheses in what should have been a tuple pattern; return a | |
| // suggestion-enhanced error here rather than choking on the comma later. | |
| let comma_span = self.token.span; | |
| self.bump(); | |
| if let Err(err) = self.skip_pat_list() { | |
| // We didn't expect this to work anyway; we just wanted to advance to the | |
| // end of the comma-sequence so we know the span to suggest parenthesizing. | |
| err.cancel(); | |
| } | |
| let seq_span = lo.to(self.prev_token.span); | |
| let mut err = self.dcx().struct_span_err(comma_span, "unexpected `,` in pattern"); | |
| err.multipart_suggestion( | |
| format!( | |
| "try adding parentheses to match on a tuple{}", | |
| if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." }, | |
| ), | |
| vec![ | |
| (seq_span.shrink_to_lo(), "(".to_string()), | |
| (seq_span.shrink_to_hi(), ")".to_string()), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| if let CommaRecoveryMode::EitherTupleOrPipe = rt { | |
| err.span_suggestion( | |
| comma_span, | |
| "...or a vertical bar to match on alternatives", | |
| " |", | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| Err(err) | |
| } | |
| pub(crate) fn maybe_recover_bounds_doubled_colon(&mut self, ty: &Ty) -> PResult<'a, ()> { | |
| let TyKind::Path(qself, path) = &ty.kind else { return Ok(()) }; | |
| let qself_position = qself.as_ref().map(|qself| qself.position); | |
| for (i, segments) in path.segments.windows(2).enumerate() { | |
| if qself_position.is_some_and(|pos| i < pos) { | |
| continue; | |
| } | |
| if let [a, b] = segments { | |
| let (a_span, b_span) = (a.span(), b.span()); | |
| let between_span = a_span.shrink_to_hi().to(b_span.shrink_to_lo()); | |
| if self.span_to_snippet(between_span).as_deref() == Ok(":: ") { | |
| return Err(self.dcx().create_err(DoubleColonInBound { | |
| span: path.span.shrink_to_hi(), | |
| between: between_span, | |
| })); | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// Check for exclusive ranges written as `..<` | |
| pub(crate) fn maybe_err_dotdotlt_syntax(&self, maybe_lt: Token, mut err: Diag<'a>) -> Diag<'a> { | |
| if maybe_lt == token::Lt | |
| && (self.expected_token_types.contains(TokenType::Gt) | |
| || matches!(self.token.kind, token::Literal(..))) | |
| { | |
| err.span_suggestion( | |
| maybe_lt.span, | |
| "remove the `<` to write an exclusive range", | |
| "", | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| err | |
| } | |
| /// This checks if this is a conflict marker, depending of the parameter passed. | |
| /// | |
| /// * `<<<<<<<` | |
| /// * `|||||||` | |
| /// * `=======` | |
| /// * `>>>>>>>` | |
| /// | |
| pub(super) fn is_vcs_conflict_marker( | |
| &mut self, | |
| long_kind: &TokenKind, | |
| short_kind: &TokenKind, | |
| ) -> bool { | |
| if long_kind == short_kind { | |
| // For conflict marker chars like `%` and `\`. | |
| (0..7).all(|i| self.look_ahead(i, |tok| tok == long_kind)) | |
| } else { | |
| // For conflict marker chars like `<` and `|`. | |
| (0..3).all(|i| self.look_ahead(i, |tok| tok == long_kind)) | |
| && self.look_ahead(3, |tok| tok == short_kind || tok == long_kind) | |
| } | |
| } | |
| fn conflict_marker( | |
| &mut self, | |
| long_kind: &TokenKind, | |
| short_kind: &TokenKind, | |
| expected: Option<usize>, | |
| ) -> Option<(Span, usize)> { | |
| if self.is_vcs_conflict_marker(long_kind, short_kind) { | |
| let lo = self.token.span; | |
| if self.psess.source_map().span_to_margin(lo) != Some(0) { | |
| return None; | |
| } | |
| let mut len = 0; | |
| while self.token.kind == *long_kind || self.token.kind == *short_kind { | |
| if self.token.kind.break_two_token_op(1).is_some() { | |
| len += 2; | |
| } else { | |
| len += 1; | |
| } | |
| self.bump(); | |
| if expected == Some(len) { | |
| break; | |
| } | |
| } | |
| if expected.is_some() && expected != Some(len) { | |
| return None; | |
| } | |
| return Some((lo.to(self.prev_token.span), len)); | |
| } | |
| None | |
| } | |
| pub(super) fn recover_vcs_conflict_marker(&mut self) { | |
| // <<<<<<< | |
| let Some((start, len)) = self.conflict_marker(&TokenKind::Shl, &TokenKind::Lt, None) else { | |
| return; | |
| }; | |
| let mut spans = Vec::with_capacity(2); | |
| spans.push(start); | |
| // ||||||| | |
| let mut middlediff3 = None; | |
| // ======= | |
| let mut middle = None; | |
| // >>>>>>> | |
| let mut end = None; | |
| loop { | |
| if self.token == TokenKind::Eof { | |
| break; | |
| } | |
| if let Some((span, _)) = | |
| self.conflict_marker(&TokenKind::OrOr, &TokenKind::Or, Some(len)) | |
| { | |
| middlediff3 = Some(span); | |
| } | |
| if let Some((span, _)) = | |
| self.conflict_marker(&TokenKind::EqEq, &TokenKind::Eq, Some(len)) | |
| { | |
| middle = Some(span); | |
| } | |
| if let Some((span, _)) = | |
| self.conflict_marker(&TokenKind::Shr, &TokenKind::Gt, Some(len)) | |
| { | |
| spans.push(span); | |
| end = Some(span); | |
| break; | |
| } | |
| self.bump(); | |
| } | |
| let mut err = self.dcx().struct_span_fatal(spans, "encountered diff marker"); | |
| let middle_marker = match middlediff3 { | |
| // We're using diff3 | |
| Some(middlediff3) => { | |
| err.span_label( | |
| middlediff3, | |
| "between this marker and `=======` is the base code (what the two refs \ | |
| diverged from)", | |
| ); | |
| "|||||||" | |
| } | |
| None => "=======", | |
| }; | |
| err.span_label( | |
| start, | |
| format!( | |
| "between this marker and `{middle_marker}` is the code that you are merging into", | |
| ), | |
| ); | |
| if let Some(middle) = middle { | |
| err.span_label(middle, "between this marker and `>>>>>>>` is the incoming code"); | |
| } | |
| if let Some(end) = end { | |
| err.span_label(end, "this marker concludes the conflict region"); | |
| } | |
| err.note( | |
| "conflict markers indicate that a merge was started but could not be completed due \ | |
| to merge conflicts\n\ | |
| to resolve a conflict, keep only the code you want and then delete the lines \ | |
| containing conflict markers", | |
| ); | |
| err.help( | |
| "if you are in a merge, the top section is the code you already had checked out and \ | |
| the bottom section is the new code\n\ | |
| if you are in a rebase, the top section is the code being rebased onto and the bottom \ | |
| section is the code you had checked out which is being rebased", | |
| ); | |
| err.note( | |
| "for an explanation on these markers from the `git` documentation, visit \ | |
| <https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_checking_out_conflicts>", | |
| ); | |
| err.emit(); | |
| } | |
| /// Parse and throw away a parenthesized comma separated | |
| /// sequence of patterns until `)` is reached. | |
| fn skip_pat_list(&mut self) -> PResult<'a, ()> { | |
| while !self.check(exp!(CloseParen)) { | |
| self.parse_pat_no_top_alt(None, None)?; | |
| if !self.eat(exp!(Comma)) { | |
| return Ok(()); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| } |