diff options
Diffstat (limited to 'compiler/rustc_parse/src')
| -rw-r--r-- | compiler/rustc_parse/src/errors.rs | 89 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/lexer/mod.rs | 3 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/diagnostics.rs | 10 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/expr.rs | 70 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/item.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/mod.rs | 21 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/pat.rs | 354 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/path.rs | 25 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/stmt.rs | 10 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/ty.rs | 3 |
10 files changed, 473 insertions, 118 deletions
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index da1103a4fe5..e9fe2e6c1dd 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -1,3 +1,5 @@ +// ignore-tidy-filelength + use std::borrow::Cow; use rustc_ast::token::Token; @@ -1569,7 +1571,7 @@ pub(crate) struct ExpectedFnPathFoundFnKeyword { } #[derive(Diagnostic)] -#[diag(parse_path_single_colon)] +#[diag(parse_path_double_colon)] pub(crate) struct PathSingleColon { #[primary_span] pub span: Span, @@ -1582,6 +1584,14 @@ pub(crate) struct PathSingleColon { } #[derive(Diagnostic)] +#[diag(parse_path_double_colon)] +pub(crate) struct PathTripleColon { + #[primary_span] + #[suggestion(applicability = "maybe-incorrect", code = "", style = "verbose")] + pub span: Span, +} + +#[derive(Diagnostic)] #[diag(parse_colon_as_semi)] pub(crate) struct ColonAsSemi { #[primary_span] @@ -2592,13 +2602,86 @@ pub(crate) struct ExpectedCommaAfterPatternField { #[derive(Diagnostic)] #[diag(parse_unexpected_expr_in_pat)] pub(crate) struct UnexpectedExpressionInPattern { + /// The unexpected expr's span. #[primary_span] #[label] pub span: Span, /// Was a `RangePatternBound` expected? pub is_bound: bool, - /// Was the unexpected expression a `MethodCallExpression`? - pub is_method_call: bool, + /// The unexpected expr's precedence (used in match arm guard suggestions). + pub expr_precedence: i8, +} + +#[derive(Subdiagnostic)] +pub(crate) enum UnexpectedExpressionInPatternSugg { + #[multipart_suggestion( + parse_unexpected_expr_in_pat_create_guard_sugg, + applicability = "maybe-incorrect" + )] + CreateGuard { + /// Where to put the suggested identifier. + #[suggestion_part(code = "{ident}")] + ident_span: Span, + /// Where to put the match arm. + #[suggestion_part(code = " if {ident} == {expr}")] + pat_hi: Span, + /// The suggested identifier. + ident: String, + /// The unexpected expression. + expr: String, + }, + + #[multipart_suggestion( + parse_unexpected_expr_in_pat_update_guard_sugg, + applicability = "maybe-incorrect" + )] + UpdateGuard { + /// Where to put the suggested identifier. + #[suggestion_part(code = "{ident}")] + ident_span: Span, + /// The beginning of the match arm guard's expression (insert a `(` if `Some`). + #[suggestion_part(code = "(")] + guard_lo: Option<Span>, + /// The end of the match arm guard's expression. + #[suggestion_part(code = "{guard_hi_paren} && {ident} == {expr}")] + guard_hi: Span, + /// Either `")"` or `""`. + guard_hi_paren: &'static str, + /// The suggested identifier. + ident: String, + /// The unexpected expression. + expr: String, + }, + + #[multipart_suggestion( + parse_unexpected_expr_in_pat_const_sugg, + applicability = "has-placeholders" + )] + Const { + /// Where to put the extracted constant declaration. + #[suggestion_part(code = "{indentation}const {ident}: /* Type */ = {expr};\n")] + stmt_lo: Span, + /// Where to put the suggested identifier. + #[suggestion_part(code = "{ident}")] + ident_span: Span, + /// The suggested identifier. + ident: String, + /// The unexpected expression. + expr: String, + /// The statement's block's indentation. + indentation: String, + }, + + #[multipart_suggestion( + parse_unexpected_expr_in_pat_inline_const_sugg, + applicability = "maybe-incorrect" + )] + InlineConst { + #[suggestion_part(code = "const {{ ")] + start_span: Span, + #[suggestion_part(code = " }}")] + end_span: Span, + }, } #[derive(Diagnostic)] diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index b7232ff21ca..898c4779b08 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -299,6 +299,9 @@ impl<'psess, 'src> StringReader<'psess, 'src> { lifetime_name += lifetime_name_without_tick; let sym = Symbol::intern(&lifetime_name); + // Make sure we mark this as a raw identifier. + self.psess.raw_identifier_spans.push(self.mk_sp(start, self.pos)); + token::Lifetime(sym, IdentIsRaw::Yes) } else { // Otherwise, this should be parsed like `'r`. Warn about it though. diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index bee73c58cb7..fd488cf1d31 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -16,7 +16,7 @@ use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{ pluralize, Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, FatalError, PErr, PResult, - Subdiagnostic, + Subdiagnostic, Suggestions, }; use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::edit_distance::find_best_match_for_name; @@ -775,7 +775,7 @@ impl<'a> Parser<'a> { } // Check for misspelled keywords if there are no suggestions added to the diagnostic. - if err.suggestions.as_ref().is_ok_and(|code_suggestions| code_suggestions.is_empty()) { + if matches!(&err.suggestions, Suggestions::Enabled(list) if list.is_empty()) { self.check_for_misspelled_kw(&mut err, &expected); } Err(err) @@ -803,6 +803,9 @@ impl<'a> Parser<'a> { && 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() { @@ -818,6 +821,9 @@ impl<'a> Parser<'a> { // 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(); } } } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 2d6edad2977..f19cba4c1bf 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -41,7 +41,7 @@ use super::{ use crate::{errors, maybe_recover_from_interpolated_ty_qpath}; #[derive(Debug)] -enum DestructuredFloat { +pub(super) enum DestructuredFloat { /// 1e2 Single(Symbol, Span), /// 1. @@ -49,7 +49,7 @@ enum DestructuredFloat { /// 1.2 | 1.2e3 MiddleDot(Symbol, Span, Span, Symbol, Span), /// Invalid - Error, + Error(ErrorGuaranteed), } impl<'a> Parser<'a> { @@ -1008,7 +1008,7 @@ impl<'a> Parser<'a> { self.mk_expr_tuple_field_access(lo, ident1_span, base, sym1, None); self.mk_expr_tuple_field_access(lo, ident2_span, base1, sym2, suffix) } - DestructuredFloat::Error => base, + DestructuredFloat::Error(_) => base, }) } _ => { @@ -1018,7 +1018,7 @@ impl<'a> Parser<'a> { } } - fn error_unexpected_after_dot(&self) { + fn error_unexpected_after_dot(&self) -> ErrorGuaranteed { let actual = pprust::token_to_string(&self.token); let span = self.token.span; let sm = self.psess.source_map(); @@ -1028,18 +1028,20 @@ impl<'a> Parser<'a> { } _ => (span, actual), }; - self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual }); + self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual }) } - // We need an identifier or integer, but the next token is a float. - // Break the float into components to extract the identifier or integer. + /// We need an identifier or integer, but the next token is a float. + /// Break the float into components to extract the identifier or integer. + /// + /// See also [`TokenKind::break_two_token_op`] which does similar splitting of `>>` into `>`. + // // FIXME: With current `TokenCursor` it's hard to break tokens into more than 2 - // parts unless those parts are processed immediately. `TokenCursor` should either - // support pushing "future tokens" (would be also helpful to `break_and_eat`), or - // we should break everything including floats into more basic proc-macro style - // tokens in the lexer (probably preferable). - // See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`. - fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat { + // parts unless those parts are processed immediately. `TokenCursor` should either + // support pushing "future tokens" (would be also helpful to `break_and_eat`), or + // we should break everything including floats into more basic proc-macro style + // tokens in the lexer (probably preferable). + pub(super) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat { #[derive(Debug)] enum FloatComponent { IdentLike(String), @@ -1078,34 +1080,30 @@ impl<'a> Parser<'a> { DestructuredFloat::Single(Symbol::intern(i), span) } // 1. - [IdentLike(i), Punct('.')] => { - let (ident_span, dot_span) = if can_take_span_apart() { - let (span, ident_len) = (span.data(), BytePos::from_usize(i.len())); - let ident_span = span.with_hi(span.lo + ident_len); - let dot_span = span.with_lo(span.lo + ident_len); - (ident_span, dot_span) + [IdentLike(left), Punct('.')] => { + let (left_span, dot_span) = if can_take_span_apart() { + let left_span = span.with_hi(span.lo() + BytePos::from_usize(left.len())); + let dot_span = span.with_lo(left_span.hi()); + (left_span, dot_span) } else { (span, span) }; - let symbol = Symbol::intern(i); - DestructuredFloat::TrailingDot(symbol, ident_span, dot_span) + let left = Symbol::intern(left); + DestructuredFloat::TrailingDot(left, left_span, dot_span) } // 1.2 | 1.2e3 - [IdentLike(i1), Punct('.'), IdentLike(i2)] => { - let (ident1_span, dot_span, ident2_span) = if can_take_span_apart() { - let (span, ident1_len) = (span.data(), BytePos::from_usize(i1.len())); - let ident1_span = span.with_hi(span.lo + ident1_len); - let dot_span = span - .with_lo(span.lo + ident1_len) - .with_hi(span.lo + ident1_len + BytePos(1)); - let ident2_span = self.token.span.with_lo(span.lo + ident1_len + BytePos(1)); - (ident1_span, dot_span, ident2_span) + [IdentLike(left), Punct('.'), IdentLike(right)] => { + let (left_span, dot_span, right_span) = if can_take_span_apart() { + let left_span = span.with_hi(span.lo() + BytePos::from_usize(left.len())); + let dot_span = span.with_lo(left_span.hi()).with_hi(left_span.hi() + BytePos(1)); + let right_span = span.with_lo(dot_span.hi()); + (left_span, dot_span, right_span) } else { (span, span, span) }; - let symbol1 = Symbol::intern(i1); - let symbol2 = Symbol::intern(i2); - DestructuredFloat::MiddleDot(symbol1, ident1_span, dot_span, symbol2, ident2_span) + let left = Symbol::intern(left); + let right = Symbol::intern(right); + DestructuredFloat::MiddleDot(left, left_span, dot_span, right, right_span) } // 1e+ | 1e- (recovered) [IdentLike(_), Punct('+' | '-')] | @@ -1116,8 +1114,8 @@ impl<'a> Parser<'a> { // 1.2e+3 | 1.2e-3 [IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-'), IdentLike(_)] => { // See the FIXME about `TokenCursor` above. - self.error_unexpected_after_dot(); - DestructuredFloat::Error + let guar = self.error_unexpected_after_dot(); + DestructuredFloat::Error(guar) } _ => panic!("unexpected components in a float token: {components:?}"), } @@ -1183,7 +1181,7 @@ impl<'a> Parser<'a> { fields.insert(start_idx, Ident::new(symbol2, span2)); fields.insert(start_idx, Ident::new(symbol1, span1)); } - DestructuredFloat::Error => { + DestructuredFloat::Error(_) => { trailing_dot = None; fields.insert(start_idx, Ident::new(symbol, self.prev_token.span)); } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 104678e081c..afd9871a635 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -707,7 +707,7 @@ impl<'a> Parser<'a> { }) }; - let (ident, item_kind) = if self.eat(&token::PathSep) { + let (ident, item_kind) = if self.eat_path_sep() { let suffixes = if self.eat(&token::BinOp(token::Star)) { None } else { @@ -1054,7 +1054,7 @@ impl<'a> Parser<'a> { { // `use *;` or `use ::*;` or `use {...};` or `use ::{...};` let mod_sep_ctxt = self.token.span.ctxt(); - if self.eat(&token::PathSep) { + if self.eat_path_sep() { prefix .segments .push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt))); @@ -1065,7 +1065,7 @@ impl<'a> Parser<'a> { // `use path::*;` or `use path::{...};` or `use path;` or `use path as bar;` prefix = self.parse_path(PathStyle::Mod)?; - if self.eat(&token::PathSep) { + if self.eat_path_sep() { self.parse_use_tree_glob_or_nested()? } else { // Recover from using a colon as path separator. diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 9d9265d5318..3b58b2337f3 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -1562,12 +1562,25 @@ impl<'a> Parser<'a> { }) } + /// Checks for `::` or, potentially, `:::` and then look ahead after it. + fn check_path_sep_and_look_ahead(&mut self, looker: impl Fn(&Token) -> bool) -> bool { + if self.check(&token::PathSep) { + if self.may_recover() && self.look_ahead(1, |t| t.kind == token::Colon) { + debug_assert!(!self.look_ahead(1, &looker), "Looker must not match on colon"); + self.look_ahead(2, looker) + } else { + self.look_ahead(1, looker) + } + } else { + false + } + } + /// `::{` or `::*` fn is_import_coupler(&mut self) -> bool { - self.check(&token::PathSep) - && self.look_ahead(1, |t| { - *t == token::OpenDelim(Delimiter::Brace) || *t == token::BinOp(token::Star) - }) + self.check_path_sep_and_look_ahead(|t| { + matches!(t.kind, token::OpenDelim(Delimiter::Brace) | token::BinOp(token::Star)) + }) } // Debug view of the parser's token stream, up to `{lookahead}` tokens. diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index daced411b8f..647df25c82e 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1,12 +1,14 @@ -use rustc_ast::mut_visit::{walk_pat, MutVisitor}; +use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::ptr::P; use rustc_ast::token::{self, BinOpToken, Delimiter, IdentIsRaw, Token}; +use rustc_ast::visit::{self, Visitor}; use rustc_ast::{ - self as ast, AttrVec, BindingMode, ByRef, Expr, ExprKind, MacCall, Mutability, Pat, PatField, - PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, + self as ast, Arm, AttrVec, BinOpKind, BindingMode, ByRef, Expr, ExprKind, ExprPrecedence, + LocalKind, MacCall, Mutability, Pat, PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, + RangeSyntax, Stmt, StmtKind, }; use rustc_ast_pretty::pprust; -use rustc_errors::{Applicability, Diag, PResult}; +use rustc_errors::{Applicability, Diag, DiagArgValue, PResult, StashKey}; use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::source_map::{respan, Spanned}; use rustc_span::symbol::{kw, sym, Ident}; @@ -21,11 +23,11 @@ use crate::errors::{ InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern, ParenRangeSuggestion, PatternOnWrongSideOfAt, RemoveLet, RepeatedMutInPattern, SwitchRefBoxOrder, TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg, - TrailingVertNotAllowed, UnexpectedExpressionInPattern, UnexpectedLifetimeInPattern, - UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg, + TrailingVertNotAllowed, UnexpectedExpressionInPattern, UnexpectedExpressionInPatternSugg, + UnexpectedLifetimeInPattern, UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, WrapInParens, }; -use crate::parser::expr::could_be_unclosed_char_literal; +use crate::parser::expr::{could_be_unclosed_char_literal, DestructuredFloat}; use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; #[derive(PartialEq, Copy, Clone)] @@ -342,7 +344,7 @@ impl<'a> Parser<'a> { } } - /// Ensures that the last parsed pattern (or pattern range bound) is not followed by a method call or an operator. + /// Ensures that the last parsed pattern (or pattern range bound) is not followed by an expression. /// /// `is_end_bound` indicates whether the last parsed thing was the end bound of a range pattern (see [`parse_pat_range_end`](Self::parse_pat_range_end)) /// in order to say "expected a pattern range bound" instead of "expected a pattern"; @@ -350,38 +352,64 @@ impl<'a> Parser<'a> { /// 0..=1 + 2 /// ^^^^^ /// ``` - /// Only the end bound is spanned, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter. + /// Only the end bound is spanned in this case, and this function has no idea if there was a `..=` before `pat_span`, hence the parameter. + /// + /// This function returns `Some` if a trailing expression was recovered, and said expression's span. #[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"] fn maybe_recover_trailing_expr( &mut self, pat_span: Span, is_end_bound: bool, - ) -> Option<ErrorGuaranteed> { + ) -> Option<(ErrorGuaranteed, Span)> { if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() { // Don't recover anything after an `_` or if recovery is disabled. return None; } - // Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`. - let has_trailing_method = self.check_noexpect(&token::Dot) + // Returns `true` iff `token` is an unsuffixed integer. + let is_one_tuple_index = |_: &Self, token: &Token| -> bool { + use token::{Lit, LitKind}; + + matches!( + token.kind, + token::Literal(Lit { kind: LitKind::Integer, symbol: _, suffix: None }) + ) + }; + + // Returns `true` iff `token` is an unsuffixed `x.y` float. + let is_two_tuple_indexes = |this: &Self, token: &Token| -> bool { + use token::{Lit, LitKind}; + + if let token::Literal(Lit { kind: LitKind::Float, symbol, suffix: None }) = token.kind + && let DestructuredFloat::MiddleDot(..) = this.break_up_float(symbol, token.span) + { + true + } else { + false + } + }; + + // Check for `.hello` or `.0`. + let has_dot_expr = self.check_noexpect(&token::Dot) // `.` && self.look_ahead(1, |tok| { - tok.ident() - .and_then(|(ident, _)| ident.name.as_str().chars().next()) - .is_some_and(char::is_lowercase) - }) - && self.look_ahead(2, |t| *t == token::OpenDelim(Delimiter::Parenthesis)); + tok.is_ident() // `hello` + || is_one_tuple_index(&self, &tok) // `0` + || is_two_tuple_indexes(&self, &tok) // `0.0` + }); // Check for operators. // `|` is excluded as it is used in pattern alternatives and lambdas, // `?` is included for error propagation, // `[` is included for indexing operations, - // `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`) + // `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`), + // `as` is included for type casts let has_trailing_operator = matches!(self.token.kind, token::BinOp(op) if op != BinOpToken::Or) || self.token == token::Question || (self.token == token::OpenDelim(Delimiter::Bracket) - && self.look_ahead(1, |t| *t != token::CloseDelim(Delimiter::Bracket))); + && self.look_ahead(1, |t| *t != token::CloseDelim(Delimiter::Bracket))) // excludes `[]` + || self.token.is_keyword(kw::As); - if !has_trailing_method && !has_trailing_operator { + if !has_dot_expr && !has_trailing_operator { // Nothing to recover here. return None; } @@ -391,44 +419,248 @@ impl<'a> Parser<'a> { snapshot.restrictions.insert(Restrictions::IS_PAT); // Parse `?`, `.f`, `(arg0, arg1, ...)` or `[expr]` until they've all been eaten. - if let Ok(expr) = snapshot + let Ok(expr) = snapshot .parse_expr_dot_or_call_with( AttrVec::new(), self.mk_expr(pat_span, ExprKind::Dummy), // equivalent to transforming the parsed pattern into an `Expr` pat_span, ) .map_err(|err| err.cancel()) - { - let non_assoc_span = expr.span; + else { + // We got a trailing method/operator, but that wasn't an expression. + return None; + }; - // Parse an associative expression such as `+ expr`, `% expr`, ... - // Assignments, ranges and `|` are disabled by [`Restrictions::IS_PAT`]. - if let Ok((expr, _)) = - snapshot.parse_expr_assoc_rest_with(0, false, expr).map_err(|err| err.cancel()) - { - // We got a valid expression. - self.restore_snapshot(snapshot); - self.restrictions.remove(Restrictions::IS_PAT); + // Parse an associative expression such as `+ expr`, `% expr`, ... + // Assignments, ranges and `|` are disabled by [`Restrictions::IS_PAT`]. + let Ok((expr, _)) = + snapshot.parse_expr_assoc_rest_with(0, false, expr).map_err(|err| err.cancel()) + else { + // We got a trailing method/operator, but that wasn't an expression. + return None; + }; - let is_bound = is_end_bound - // is_start_bound: either `..` or `)..` - || self.token.is_range_separator() - || self.token == token::CloseDelim(Delimiter::Parenthesis) - && self.look_ahead(1, Token::is_range_separator); + // We got a valid expression. + self.restore_snapshot(snapshot); + self.restrictions.remove(Restrictions::IS_PAT); - // Check that `parse_expr_assoc_with` didn't eat a rhs. - let is_method_call = has_trailing_method && non_assoc_span == expr.span; + let is_bound = is_end_bound + // is_start_bound: either `..` or `)..` + || self.token.is_range_separator() + || self.token == token::CloseDelim(Delimiter::Parenthesis) + && self.look_ahead(1, Token::is_range_separator); - return Some(self.dcx().emit_err(UnexpectedExpressionInPattern { - span: expr.span, + let span = expr.span; + + Some(( + self.dcx() + .create_err(UnexpectedExpressionInPattern { + span, is_bound, - is_method_call, - })); + expr_precedence: expr.precedence().order(), + }) + .stash(span, StashKey::ExprInPat) + .unwrap(), + span, + )) + } + + /// Called by [`Parser::parse_stmt_without_recovery`], used to add statement-aware subdiagnostics to the errors stashed + /// by [`Parser::maybe_recover_trailing_expr`]. + pub(super) fn maybe_augment_stashed_expr_in_pats_with_suggestions(&mut self, stmt: &Stmt) { + if self.dcx().has_errors().is_none() { + // No need to walk the statement if there's no stashed errors. + return; + } + + struct PatVisitor<'a> { + /// `self` + parser: &'a Parser<'a>, + /// The freshly-parsed statement. + stmt: &'a Stmt, + /// The current match arm (for arm guard suggestions). + arm: Option<&'a Arm>, + /// The current struct field (for variable name suggestions). + field: Option<&'a PatField>, + } + + impl<'a> PatVisitor<'a> { + /// Looks for stashed [`StashKey::ExprInPat`] errors in `stash_span`, and emit them with suggestions. + /// `stash_span` is contained in `expr_span`, the latter being larger in borrow patterns; + /// ```txt + /// &mut x.y + /// -----^^^ `stash_span` + /// | + /// `expr_span` + /// ``` + /// `is_range_bound` is used to exclude arm guard suggestions in range pattern bounds. + fn maybe_add_suggestions_then_emit( + &self, + stash_span: Span, + expr_span: Span, + is_range_bound: bool, + ) { + self.parser.dcx().try_steal_modify_and_emit_err( + stash_span, + StashKey::ExprInPat, + |err| { + // Includes pre-pats (e.g. `&mut <err>`) in the diagnostic. + err.span.replace(stash_span, expr_span); + + let sm = self.parser.psess.source_map(); + let stmt = self.stmt; + let line_lo = sm.span_extend_to_line(stmt.span).shrink_to_lo(); + let indentation = sm.indentation_before(stmt.span).unwrap_or_default(); + let Ok(expr) = self.parser.span_to_snippet(expr_span) else { + // FIXME: some suggestions don't actually need the snippet; see PR #123877's unresolved conversations. + return; + }; + + if let StmtKind::Let(local) = &stmt.kind { + match &local.kind { + LocalKind::Decl | LocalKind::Init(_) => { + // It's kinda hard to guess what the user intended, so don't make suggestions. + return; + } + + LocalKind::InitElse(_, _) => {} + } + } + + // help: use an arm guard `if val == expr` + // FIXME(guard_patterns): suggest this regardless of a match arm. + if let Some(arm) = &self.arm + && !is_range_bound + { + let (ident, ident_span) = match self.field { + Some(field) => { + (field.ident.to_string(), field.ident.span.to(expr_span)) + } + None => ("val".to_owned(), expr_span), + }; + + // Are parentheses required around `expr`? + // HACK: a neater way would be preferable. + let expr = match &err.args["expr_precedence"] { + DiagArgValue::Number(expr_precedence) => { + if *expr_precedence + <= ExprPrecedence::Binary(BinOpKind::Eq).order() as i32 + { + format!("({expr})") + } else { + format!("{expr}") + } + } + _ => unreachable!(), + }; + + match &arm.guard { + None => { + err.subdiagnostic( + UnexpectedExpressionInPatternSugg::CreateGuard { + ident_span, + pat_hi: arm.pat.span.shrink_to_hi(), + ident, + expr, + }, + ); + } + Some(guard) => { + // Are parentheses required around the old guard? + let wrap_guard = guard.precedence().order() + <= ExprPrecedence::Binary(BinOpKind::And).order(); + + err.subdiagnostic( + UnexpectedExpressionInPatternSugg::UpdateGuard { + ident_span, + guard_lo: if wrap_guard { + Some(guard.span.shrink_to_lo()) + } else { + None + }, + guard_hi: guard.span.shrink_to_hi(), + guard_hi_paren: if wrap_guard { ")" } else { "" }, + ident, + expr, + }, + ); + } + } + } + + // help: extract the expr into a `const VAL: _ = expr` + let ident = match self.field { + Some(field) => field.ident.as_str().to_uppercase(), + None => "VAL".to_owned(), + }; + err.subdiagnostic(UnexpectedExpressionInPatternSugg::Const { + stmt_lo: line_lo, + ident_span: expr_span, + expr, + ident, + indentation, + }); + + // help: wrap the expr in a `const { expr }` + // FIXME(inline_const_pat): once stabilized, remove this check and remove the `(requires #[feature(inline_const_pat)])` note from the message + if self.parser.psess.unstable_features.is_nightly_build() { + err.subdiagnostic(UnexpectedExpressionInPatternSugg::InlineConst { + start_span: expr_span.shrink_to_lo(), + end_span: expr_span.shrink_to_hi(), + }); + } + }, + ); } } - // We got a trailing method/operator, but we couldn't parse an expression. - None + impl<'a> Visitor<'a> for PatVisitor<'a> { + fn visit_arm(&mut self, a: &'a Arm) -> Self::Result { + self.arm = Some(a); + visit::walk_arm(self, a); + self.arm = None; + } + + fn visit_pat_field(&mut self, fp: &'a PatField) -> Self::Result { + self.field = Some(fp); + visit::walk_pat_field(self, fp); + self.field = None; + } + + fn visit_pat(&mut self, p: &'a Pat) -> Self::Result { + match &p.kind { + // Base expression + PatKind::Err(_) | PatKind::Lit(_) => { + self.maybe_add_suggestions_then_emit(p.span, p.span, false) + } + + // Sub-patterns + // FIXME: this doesn't work with recursive subpats (`&mut &mut <err>`) + PatKind::Box(subpat) | PatKind::Ref(subpat, _) + if matches!(subpat.kind, PatKind::Err(_) | PatKind::Lit(_)) => + { + self.maybe_add_suggestions_then_emit(subpat.span, p.span, false) + } + + // Sub-expressions + PatKind::Range(start, end, _) => { + if let Some(start) = start { + self.maybe_add_suggestions_then_emit(start.span, start.span, true); + } + + if let Some(end) = end { + self.maybe_add_suggestions_then_emit(end.span, end.span, true); + } + } + + // Walk continuation + _ => visit::walk_pat(self, p), + } + } + } + + // Starts the visit. + PatVisitor { parser: self, stmt, arm: None, field: None }.visit_stmt(stmt); } /// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are @@ -544,7 +776,7 @@ impl<'a> Parser<'a> { self.parse_pat_tuple_struct(qself, path)? } else { match self.maybe_recover_trailing_expr(span, false) { - Some(guar) => PatKind::Err(guar), + Some((guar, _)) => PatKind::Err(guar), None => PatKind::Path(qself, path), } } @@ -577,10 +809,10 @@ impl<'a> Parser<'a> { // Try to parse everything else as literal with optional minus match self.parse_literal_maybe_minus() { Ok(begin) => { - let begin = match self.maybe_recover_trailing_expr(begin.span, false) { - Some(guar) => self.mk_expr_err(begin.span, guar), - None => begin, - }; + let begin = self + .maybe_recover_trailing_expr(begin.span, false) + .map(|(guar, sp)| self.mk_expr_err(sp, guar)) + .unwrap_or(begin); match self.parse_range_end() { Some(form) => self.parse_pat_range_begin_with(begin, form)?, @@ -721,7 +953,8 @@ impl<'a> Parser<'a> { // For backward compatibility, `(..)` is a tuple pattern as well. let paren_pattern = fields.len() == 1 && !(matches!(trailing_comma, Trailing::Yes) || fields[0].is_rest()); - if paren_pattern { + + let pat = if paren_pattern { let pat = fields.into_iter().next().unwrap(); let close_paren = self.prev_token.span; @@ -739,7 +972,7 @@ impl<'a> Parser<'a> { }, }); - self.parse_pat_range_begin_with(begin.clone(), form) + self.parse_pat_range_begin_with(begin.clone(), form)? } // recover ranges with parentheses around the `(start)..` PatKind::Err(guar) @@ -754,15 +987,20 @@ impl<'a> Parser<'a> { }, }); - self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form) + self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)? } // (pat) with optional parentheses - _ => Ok(PatKind::Paren(pat)), + _ => PatKind::Paren(pat), } } else { - Ok(PatKind::Tuple(fields)) - } + PatKind::Tuple(fields) + }; + + Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) { + None => pat, + Some((guar, _)) => PatKind::Err(guar), + }) } /// Parse a mutable binding with the `mut` token already eaten. @@ -816,7 +1054,7 @@ impl<'a> Parser<'a> { self.0 = true; *m = Mutability::Mut; } - walk_pat(self, pat); + mut_visit::walk_pat(self, pat); } } @@ -1015,7 +1253,7 @@ impl<'a> Parser<'a> { } Ok(match recovered { - Some(guar) => self.mk_expr_err(bound.span, guar), + Some((guar, sp)) => self.mk_expr_err(sp, guar), None => bound, }) } @@ -1084,7 +1322,7 @@ impl<'a> Parser<'a> { // but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`. let pat = if sub.is_none() - && let Some(guar) = self.maybe_recover_trailing_expr(ident.span, false) + && let Some((guar, _)) = self.maybe_recover_trailing_expr(ident.span, false) { PatKind::Err(guar) } else { diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 42039c621d6..961679b1f56 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -16,7 +16,7 @@ use tracing::debug; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{Parser, Restrictions, TokenType}; -use crate::errors::PathSingleColon; +use crate::errors::{PathSingleColon, PathTripleColon}; use crate::parser::{CommaRecoveryMode, RecoverColon, RecoverComma}; use crate::{errors, maybe_whole}; @@ -210,7 +210,7 @@ impl<'a> Parser<'a> { let lo = self.token.span; let mut segments = ThinVec::new(); let mod_sep_ctxt = self.token.span.ctxt(); - if self.eat(&token::PathSep) { + if self.eat_path_sep() { segments.push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt))); } self.parse_path_segments(&mut segments, style, ty_generics)?; @@ -246,7 +246,7 @@ impl<'a> Parser<'a> { } segments.push(segment); - if self.is_import_coupler() || !self.eat(&token::PathSep) { + if self.is_import_coupler() || !self.eat_path_sep() { if style == PathStyle::Expr && self.may_recover() && self.token == token::Colon @@ -272,6 +272,18 @@ impl<'a> Parser<'a> { } } + /// Eat `::` or, potentially, `:::`. + #[must_use] + pub(super) fn eat_path_sep(&mut self) -> bool { + let result = self.eat(&token::PathSep); + if result && self.may_recover() { + if self.eat_noexpect(&token::Colon) { + self.dcx().emit_err(PathTripleColon { span: self.prev_token.span }); + } + } + result + } + pub(super) fn parse_path_segment( &mut self, style: PathStyle, @@ -297,9 +309,7 @@ impl<'a> Parser<'a> { Ok( if style == PathStyle::Type && check_args_start(self) - || style != PathStyle::Mod - && self.check(&token::PathSep) - && self.look_ahead(1, |t| is_args_start(t)) + || style != PathStyle::Mod && self.check_path_sep_and_look_ahead(is_args_start) { // We use `style == PathStyle::Expr` to check if this is in a recursion or not. If // it isn't, then we reset the unmatched angle bracket count as we're about to start @@ -310,7 +320,8 @@ impl<'a> Parser<'a> { // Generic arguments are found - `<`, `(`, `::<` or `::(`. // First, eat `::` if it exists. - let _ = self.eat(&token::PathSep); + let _ = self.eat_path_sep(); + let lo = self.token.span; let args = if self.eat_lt() { // `<'a, T, A = U>` diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 26ad39e06cd..92fba89d28a 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -29,6 +29,9 @@ use crate::{errors, maybe_whole}; impl<'a> Parser<'a> { /// Parses a statement. This stops just before trailing semicolons on everything but items. /// e.g., a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed. + /// + /// If `force_collect` is [`ForceCollect::Yes`], forces collection of tokens regardless of + /// whether or not we have attributes. // Public for rustfmt usage. pub(super) fn parse_stmt(&mut self, force_collect: ForceCollect) -> PResult<'a, Option<Stmt>> { Ok(self.parse_stmt_without_recovery(false, force_collect).unwrap_or_else(|e| { @@ -66,7 +69,7 @@ impl<'a> Parser<'a> { }); } - Ok(Some(if self.token.is_keyword(kw::Let) { + let stmt = if self.token.is_keyword(kw::Let) { self.collect_tokens(None, attrs, force_collect, |this, attrs| { this.expect_keyword(kw::Let)?; let local = this.parse_local(attrs)?; @@ -163,7 +166,10 @@ impl<'a> Parser<'a> { } else { self.error_outer_attrs(attrs); return Ok(None); - })) + }; + + self.maybe_augment_stashed_expr_in_pats_with_suggestions(&stmt); + Ok(Some(stmt)) } fn parse_stmt_path_start(&mut self, lo: Span, attrs: AttrWrapper) -> PResult<'a, Stmt> { diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index dd1cc75c7ff..66019ffbc0a 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -607,9 +607,6 @@ impl<'a> Parser<'a> { let decl = self.parse_fn_decl(|_| false, AllowPlus::No, recover_return_sign)?; let whole_span = lo.to(self.prev_token.span); if let ast::Const::Yes(span) = constness { - // If we ever start to allow `const fn()`, then update - // feature gating for `#![feature(const_extern_fn)]` to - // cover it. self.dcx().emit_err(FnPointerCannotBeConst { span: whole_span, qualifier: span }); } if let Some(ast::CoroutineKind::Async { span, .. }) = coroutine_kind { |
