From c23a457e72abe608715ac76f076f47dc42af07a5 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 20:31:44 +0200 Subject: Merging upstream version 1.74.1+dfsg1. Signed-off-by: Daniel Baumann --- compiler/rustc_parse/messages.ftl | 5 +- compiler/rustc_parse/src/errors.rs | 18 +- compiler/rustc_parse/src/lib.rs | 4 +- compiler/rustc_parse/src/parser/attr_wrapper.rs | 2 +- compiler/rustc_parse/src/parser/diagnostics.rs | 7 +- compiler/rustc_parse/src/parser/expr.rs | 381 ++++++++++++++++-------- compiler/rustc_parse/src/parser/item.rs | 36 +-- compiler/rustc_parse/src/parser/mod.rs | 1 + compiler/rustc_parse/src/parser/ty.rs | 84 +++++- 9 files changed, 371 insertions(+), 167 deletions(-) (limited to 'compiler/rustc_parse') diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 34cc0998c..05b6c4062 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -196,6 +196,9 @@ parse_expected_else_block = expected `{"{"}`, found {$first_tok} .suggestion = add an `if` if this is the condition of a chained `else if` statement parse_expected_expression_found_let = expected expression, found `let` statement + .note = only supported directly in conditions of `if` and `while` expressions + .not_supported_or = `||` operators are not supported in let chain expressions + .not_supported_parentheses = `let`s wrapped in parentheses are not supported in a context with let chains parse_expected_fn_path_found_fn_keyword = expected identifier, found keyword `fn` .suggestion = use `Fn` to refer to the trait @@ -506,7 +509,7 @@ parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of parse_maybe_recover_from_bad_qpath_stage_2 = missing angle brackets in associated item path - .suggestion = try: `{$ty}` + .suggestion = types that don't start with an identifier need to be surrounded with angle brackets in qualified paths parse_maybe_recover_from_bad_type_plus = expected a path on the left-hand side of `+`, not `{$ty}` diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index e0b1e3678..7c75e440a 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -10,7 +10,7 @@ use rustc_span::symbol::Ident; use rustc_span::{Span, Symbol}; use crate::fluent_generated as fluent; -use crate::parser::TokenDescription; +use crate::parser::{ForbiddenLetReason, TokenDescription}; #[derive(Diagnostic)] #[diag(parse_maybe_report_ambiguous_plus)] @@ -59,9 +59,18 @@ pub(crate) enum BadTypePlusSub { #[diag(parse_maybe_recover_from_bad_qpath_stage_2)] pub(crate) struct BadQPathStage2 { #[primary_span] - #[suggestion(code = "", applicability = "maybe-incorrect")] pub span: Span, - pub ty: String, + #[subdiagnostic] + pub wrap: WrapType, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")] +pub(crate) struct WrapType { + #[suggestion_part(code = "<")] + pub lo: Span, + #[suggestion_part(code = ">")] + pub hi: Span, } #[derive(Diagnostic)] @@ -392,9 +401,12 @@ pub(crate) struct IfExpressionMissingCondition { #[derive(Diagnostic)] #[diag(parse_expected_expression_found_let)] +#[note] pub(crate) struct ExpectedExpressionFoundLet { #[primary_span] pub span: Span, + #[subdiagnostic] + pub reason: ForbiddenLetReason, } #[derive(Diagnostic)] diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 892be36aa..c012a8663 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -8,7 +8,7 @@ #![feature(never_type)] #![feature(rustc_attrs)] #![recursion_limit = "256"] -#![cfg_attr(not(bootstrap), allow(internal_features))] +#![allow(internal_features)] #[macro_use] extern crate tracing; @@ -132,7 +132,7 @@ fn maybe_source_file_to_parser( sess: &ParseSess, source_file: Lrc, ) -> Result, Vec> { - let end_pos = source_file.end_pos; + let end_pos = source_file.end_position(); let stream = maybe_file_to_stream(sess, source_file, None)?; let mut parser = stream_to_parser(sess, stream, None); if parser.token == token::Eof { diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs index 5d6c574ba..c4e8d9006 100644 --- a/compiler/rustc_parse/src/parser/attr_wrapper.rs +++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs @@ -106,7 +106,7 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl { let mut cursor_snapshot = self.cursor_snapshot.clone(); let tokens = std::iter::once((FlatToken::Token(self.start_token.0.clone()), self.start_token.1)) - .chain((0..self.num_calls).map(|_| { + .chain(std::iter::repeat_with(|| { let token = cursor_snapshot.next(); (FlatToken::Token(token.0), token.1) })) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 6c8ef3406..06b1b1523 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -16,7 +16,7 @@ use crate::errors::{ StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens, StructLiteralNeedingParensSugg, SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma, TernaryOperator, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, - UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, + UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType, }; use crate::fluent_generated as fluent; @@ -1589,10 +1589,9 @@ impl<'a> Parser<'a> { self.parse_path_segments(&mut path.segments, T::PATH_STYLE, None)?; path.span = ty_span.to(self.prev_token.span); - let ty_str = self.span_to_snippet(ty_span).unwrap_or_else(|_| pprust::ty_to_string(&ty)); self.sess.emit_err(BadQPathStage2 { - span: path.span, - ty: format!("<{}>::{}", ty_str, pprust::path_to_string(&path)), + 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`. diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 9ae3ef617..f4cee3a66 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -8,6 +8,7 @@ use super::{ use crate::errors; use crate::maybe_recover_from_interpolated_ty_qpath; +use ast::mut_visit::{noop_visit_expr, MutVisitor}; use ast::{Path, PathSegment}; use core::mem; use rustc_ast::ptr::P; @@ -27,6 +28,7 @@ use rustc_errors::{ AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult, StashKey, }; +use rustc_macros::Subdiagnostic; use rustc_session::errors::{report_lit_error, ExprParenthesesNeeded}; use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP; use rustc_session::lint::BuiltinLintDiagnostics; @@ -122,8 +124,8 @@ impl<'a> Parser<'a> { self.parse_expr().map(|value| AnonConst { id: DUMMY_NODE_ID, value }) } - fn parse_expr_catch_underscore(&mut self) -> PResult<'a, P> { - match self.parse_expr() { + fn parse_expr_catch_underscore(&mut self, restrictions: Restrictions) -> PResult<'a, P> { + match self.parse_expr_res(restrictions, None) { Ok(expr) => Ok(expr), Err(mut err) => match self.token.ident() { Some((Ident { name: kw::Underscore, .. }, false)) @@ -141,7 +143,8 @@ impl<'a> Parser<'a> { /// Parses a sequence of expressions delimited by parentheses. fn parse_expr_paren_seq(&mut self) -> PResult<'a, ThinVec>> { - self.parse_paren_comma_seq(|p| p.parse_expr_catch_underscore()).map(|(r, _)| r) + self.parse_paren_comma_seq(|p| p.parse_expr_catch_underscore(Restrictions::empty())) + .map(|(r, _)| r) } /// Parses an expression, subject to the given restrictions. @@ -1345,110 +1348,113 @@ impl<'a> Parser<'a> { // Outer attributes are already parsed and will be // added to the return value after the fact. - // Note: when adding new syntax here, don't forget to adjust `TokenKind::can_begin_expr()`. - let lo = self.token.span; - if let token::Literal(_) = self.token.kind { - // This match arm is a special-case of the `_` match arm below and - // could be removed without changing functionality, but it's faster - // to have it here, especially for programs with large constants. - self.parse_expr_lit() - } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { - self.parse_expr_tuple_parens() - } else if self.check(&token::OpenDelim(Delimiter::Brace)) { - self.parse_expr_block(None, lo, BlockCheckMode::Default) - } else if self.check(&token::BinOp(token::Or)) || self.check(&token::OrOr) { - self.parse_expr_closure().map_err(|mut err| { - // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }` - // then suggest parens around the lhs. - if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&lo) { - err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + let restrictions = self.restrictions; + self.with_res(restrictions - Restrictions::ALLOW_LET, |this| { + // Note: when adding new syntax here, don't forget to adjust `TokenKind::can_begin_expr()`. + let lo = this.token.span; + if let token::Literal(_) = this.token.kind { + // This match arm is a special-case of the `_` match arm below and + // could be removed without changing functionality, but it's faster + // to have it here, especially for programs with large constants. + this.parse_expr_lit() + } else if this.check(&token::OpenDelim(Delimiter::Parenthesis)) { + this.parse_expr_tuple_parens(restrictions) + } else if this.check(&token::OpenDelim(Delimiter::Brace)) { + this.parse_expr_block(None, lo, BlockCheckMode::Default) + } else if this.check(&token::BinOp(token::Or)) || this.check(&token::OrOr) { + this.parse_expr_closure().map_err(|mut err| { + // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }` + // then suggest parens around the lhs. + if let Some(sp) = this.sess.ambiguous_block_expr_parse.borrow().get(&lo) { + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + } + err + }) + } else if this.check(&token::OpenDelim(Delimiter::Bracket)) { + this.parse_expr_array_or_repeat(Delimiter::Bracket) + } else if this.is_builtin() { + this.parse_expr_builtin() + } else if this.check_path() { + this.parse_expr_path_start() + } else if this.check_keyword(kw::Move) + || this.check_keyword(kw::Static) + || this.check_const_closure() + { + this.parse_expr_closure() + } else if this.eat_keyword(kw::If) { + this.parse_expr_if() + } else if this.check_keyword(kw::For) { + if this.choose_generics_over_qpath(1) { + this.parse_expr_closure() + } else { + assert!(this.eat_keyword(kw::For)); + this.parse_expr_for(None, this.prev_token.span) } - err - }) - } else if self.check(&token::OpenDelim(Delimiter::Bracket)) { - self.parse_expr_array_or_repeat(Delimiter::Bracket) - } else if self.is_builtin() { - self.parse_expr_builtin() - } else if self.check_path() { - self.parse_expr_path_start() - } else if self.check_keyword(kw::Move) - || self.check_keyword(kw::Static) - || self.check_const_closure() - { - self.parse_expr_closure() - } else if self.eat_keyword(kw::If) { - self.parse_expr_if() - } else if self.check_keyword(kw::For) { - if self.choose_generics_over_qpath(1) { - self.parse_expr_closure() - } else { - assert!(self.eat_keyword(kw::For)); - self.parse_expr_for(None, self.prev_token.span) - } - } else if self.eat_keyword(kw::While) { - self.parse_expr_while(None, self.prev_token.span) - } else if let Some(label) = self.eat_label() { - self.parse_expr_labeled(label, true) - } else if self.eat_keyword(kw::Loop) { - let sp = self.prev_token.span; - self.parse_expr_loop(None, self.prev_token.span).map_err(|mut err| { - err.span_label(sp, "while parsing this `loop` expression"); - err - }) - } else if self.eat_keyword(kw::Match) { - let match_sp = self.prev_token.span; - self.parse_expr_match().map_err(|mut err| { - err.span_label(match_sp, "while parsing this `match` expression"); - err - }) - } else if self.eat_keyword(kw::Unsafe) { - let sp = self.prev_token.span; - self.parse_expr_block(None, lo, BlockCheckMode::Unsafe(ast::UserProvided)).map_err( - |mut err| { - err.span_label(sp, "while parsing this `unsafe` expression"); + } else if this.eat_keyword(kw::While) { + this.parse_expr_while(None, this.prev_token.span) + } else if let Some(label) = this.eat_label() { + this.parse_expr_labeled(label, true) + } else if this.eat_keyword(kw::Loop) { + let sp = this.prev_token.span; + this.parse_expr_loop(None, this.prev_token.span).map_err(|mut err| { + err.span_label(sp, "while parsing this `loop` expression"); err - }, - ) - } else if self.check_inline_const(0) { - self.parse_const_block(lo.to(self.token.span), false) - } else if self.may_recover() && self.is_do_catch_block() { - self.recover_do_catch() - } else if self.is_try_block() { - self.expect_keyword(kw::Try)?; - self.parse_try_block(lo) - } else if self.eat_keyword(kw::Return) { - self.parse_expr_return() - } else if self.eat_keyword(kw::Continue) { - self.parse_expr_continue(lo) - } else if self.eat_keyword(kw::Break) { - self.parse_expr_break() - } else if self.eat_keyword(kw::Yield) { - self.parse_expr_yield() - } else if self.is_do_yeet() { - self.parse_expr_yeet() - } else if self.eat_keyword(kw::Become) { - self.parse_expr_become() - } else if self.check_keyword(kw::Let) { - self.parse_expr_let() - } else if self.eat_keyword(kw::Underscore) { - Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore)) - } else if self.token.uninterpolated_span().at_least_rust_2018() { - // `Span:.at_least_rust_2018()` is somewhat expensive; don't get it repeatedly. - if self.check_keyword(kw::Async) { - if self.is_async_block() { - // Check for `async {` and `async move {`. - self.parse_async_block() + }) + } else if this.eat_keyword(kw::Match) { + let match_sp = this.prev_token.span; + this.parse_expr_match().map_err(|mut err| { + err.span_label(match_sp, "while parsing this `match` expression"); + err + }) + } else if this.eat_keyword(kw::Unsafe) { + let sp = this.prev_token.span; + this.parse_expr_block(None, lo, BlockCheckMode::Unsafe(ast::UserProvided)).map_err( + |mut err| { + err.span_label(sp, "while parsing this `unsafe` expression"); + err + }, + ) + } else if this.check_inline_const(0) { + this.parse_const_block(lo.to(this.token.span), false) + } else if this.may_recover() && this.is_do_catch_block() { + this.recover_do_catch() + } else if this.is_try_block() { + this.expect_keyword(kw::Try)?; + this.parse_try_block(lo) + } else if this.eat_keyword(kw::Return) { + this.parse_expr_return() + } else if this.eat_keyword(kw::Continue) { + this.parse_expr_continue(lo) + } else if this.eat_keyword(kw::Break) { + this.parse_expr_break() + } else if this.eat_keyword(kw::Yield) { + this.parse_expr_yield() + } else if this.is_do_yeet() { + this.parse_expr_yeet() + } else if this.eat_keyword(kw::Become) { + this.parse_expr_become() + } else if this.check_keyword(kw::Let) { + this.parse_expr_let(restrictions) + } else if this.eat_keyword(kw::Underscore) { + Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore)) + } else if this.token.uninterpolated_span().at_least_rust_2018() { + // `Span:.at_least_rust_2018()` is somewhat expensive; don't get it repeatedly. + if this.check_keyword(kw::Async) { + if this.is_async_block() { + // Check for `async {` and `async move {`. + this.parse_async_block() + } else { + this.parse_expr_closure() + } + } else if this.eat_keyword(kw::Await) { + this.recover_incorrect_await_syntax(lo, this.prev_token.span) } else { - self.parse_expr_closure() + this.parse_expr_lit() } - } else if self.eat_keyword(kw::Await) { - self.recover_incorrect_await_syntax(lo, self.prev_token.span) } else { - self.parse_expr_lit() + this.parse_expr_lit() } - } else { - self.parse_expr_lit() - } + }) } fn parse_expr_lit(&mut self) -> PResult<'a, P> { @@ -1462,13 +1468,13 @@ impl<'a> Parser<'a> { } } - fn parse_expr_tuple_parens(&mut self) -> PResult<'a, P> { + fn parse_expr_tuple_parens(&mut self, restrictions: Restrictions) -> PResult<'a, P> { let lo = self.token.span; self.expect(&token::OpenDelim(Delimiter::Parenthesis))?; let (es, trailing_comma) = match self.parse_seq_to_end( &token::CloseDelim(Delimiter::Parenthesis), SeqSep::trailing_allowed(token::Comma), - |p| p.parse_expr_catch_underscore(), + |p| p.parse_expr_catch_underscore(restrictions.intersection(Restrictions::ALLOW_LET)), ) { Ok(x) => x, Err(err) => { @@ -2231,7 +2237,8 @@ impl<'a> Parser<'a> { let decl_hi = self.prev_token.span; let mut body = match fn_decl.output { FnRetTy::Default(_) => { - let restrictions = self.restrictions - Restrictions::STMT_EXPR; + let restrictions = + self.restrictions - Restrictions::STMT_EXPR - Restrictions::ALLOW_LET; self.parse_expr_res(restrictions, None)? } _ => { @@ -2436,10 +2443,12 @@ impl<'a> Parser<'a> { /// Parses the condition of a `if` or `while` expression. fn parse_expr_cond(&mut self) -> PResult<'a, P> { - let cond = + let mut cond = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, None)?; - if let ExprKind::Let(..) = cond.kind { + CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond); + + if let ExprKind::Let(_, _, _, None) = cond.kind { // Remove the last feature gating of a `let` expression since it's stable. self.sess.gated_spans.ungate_last(sym::let_chains, cond.span); } @@ -2448,18 +2457,15 @@ impl<'a> Parser<'a> { } /// Parses a `let $pat = $expr` pseudo-expression. - fn parse_expr_let(&mut self) -> PResult<'a, P> { - // This is a *approximate* heuristic that detects if `let` chains are - // being parsed in the right position. It's approximate because it - // doesn't deny all invalid `let` expressions, just completely wrong usages. - let not_in_chain = !matches!( - self.prev_token.kind, - TokenKind::AndAnd | TokenKind::Ident(kw::If, _) | TokenKind::Ident(kw::While, _) - ); - if !self.restrictions.contains(Restrictions::ALLOW_LET) || not_in_chain { - self.sess.emit_err(errors::ExpectedExpressionFoundLet { span: self.token.span }); - } - + fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, P> { + let is_recovered = if !restrictions.contains(Restrictions::ALLOW_LET) { + Some(self.sess.emit_err(errors::ExpectedExpressionFoundLet { + span: self.token.span, + reason: ForbiddenLetReason::OtherForbidden, + })) + } else { + None + }; self.bump(); // Eat `let` token let lo = self.prev_token.span; let pat = self.parse_pat_allow_top_alt( @@ -2477,12 +2483,9 @@ impl<'a> Parser<'a> { } else { self.expect(&token::Eq)?; } - let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| { - this.parse_expr_assoc_with(1 + prec_let_scrutinee_needs_par(), None.into()) - })?; + let expr = self.parse_expr_assoc_with(1 + prec_let_scrutinee_needs_par(), None.into())?; let span = lo.to(expr.span); - self.sess.gated_spans.gate(sym::let_chains, span); - Ok(self.mk_expr(span, ExprKind::Let(pat, expr, span))) + Ok(self.mk_expr(span, ExprKind::Let(pat, expr, span, is_recovered))) } /// Parses an `else { ... }` expression (`else` token already eaten). @@ -2831,7 +2834,10 @@ impl<'a> Parser<'a> { )?; let guard = if this.eat_keyword(kw::If) { let if_span = this.prev_token.span; - let cond = this.parse_expr_res(Restrictions::ALLOW_LET, None)?; + let mut cond = this.parse_expr_res(Restrictions::ALLOW_LET, None)?; + + CondChecker { parser: this, forbid_let_reason: None }.visit_expr(&mut cond); + let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond); if has_let_expr { if does_not_have_bin_op { @@ -3416,3 +3422,130 @@ impl<'a> Parser<'a> { }) } } + +/// Used to forbid `let` expressions in certain syntactic locations. +#[derive(Clone, Copy, Subdiagnostic)] +pub(crate) enum ForbiddenLetReason { + /// `let` is not valid and the source environment is not important + OtherForbidden, + /// A let chain with the `||` operator + #[note(parse_not_supported_or)] + NotSupportedOr(#[primary_span] Span), + /// A let chain with invalid parentheses + /// + /// For example, `let 1 = 1 && (expr && expr)` is allowed + /// but `(let 1 = 1 && (let 1 = 1 && (let 1 = 1))) && let a = 1` is not + #[note(parse_not_supported_parentheses)] + NotSupportedParentheses(#[primary_span] Span), +} + +/// Visitor to check for invalid/unstable use of `ExprKind::Let` that can't +/// easily be caught in parsing. For example: +/// +/// ```rust,ignore (example) +/// // Only know that the let isn't allowed once the `||` token is reached +/// if let Some(x) = y || true {} +/// // Only know that the let isn't allowed once the second `=` token is reached. +/// if let Some(x) = y && z = 1 {} +/// ``` +struct CondChecker<'a> { + parser: &'a Parser<'a>, + forbid_let_reason: Option, +} + +impl MutVisitor for CondChecker<'_> { + fn visit_expr(&mut self, e: &mut P) { + use ForbiddenLetReason::*; + + let span = e.span; + match e.kind { + ExprKind::Let(_, _, _, ref mut is_recovered @ None) => { + if let Some(reason) = self.forbid_let_reason { + *is_recovered = Some( + self.parser + .sess + .emit_err(errors::ExpectedExpressionFoundLet { span, reason }), + ); + } else { + self.parser.sess.gated_spans.gate(sym::let_chains, span); + } + } + ExprKind::Binary(Spanned { node: BinOpKind::And, .. }, _, _) => { + noop_visit_expr(e, self); + } + ExprKind::Binary(Spanned { node: BinOpKind::Or, span: or_span }, _, _) + if let None | Some(NotSupportedOr(_)) = self.forbid_let_reason => + { + let forbid_let_reason = self.forbid_let_reason; + self.forbid_let_reason = Some(NotSupportedOr(or_span)); + noop_visit_expr(e, self); + self.forbid_let_reason = forbid_let_reason; + } + ExprKind::Paren(ref inner) + if let None | Some(NotSupportedParentheses(_)) = self.forbid_let_reason => + { + let forbid_let_reason = self.forbid_let_reason; + self.forbid_let_reason = Some(NotSupportedParentheses(inner.span)); + noop_visit_expr(e, self); + self.forbid_let_reason = forbid_let_reason; + } + ExprKind::Unary(_, _) + | ExprKind::Await(_, _) + | ExprKind::Assign(_, _, _) + | ExprKind::AssignOp(_, _, _) + | ExprKind::Range(_, _, _) + | ExprKind::Try(_) + | ExprKind::AddrOf(_, _, _) + | ExprKind::Binary(_, _, _) + | ExprKind::Field(_, _) + | ExprKind::Index(_, _, _) + | ExprKind::Call(_, _) + | ExprKind::MethodCall(_) + | ExprKind::Tup(_) + | ExprKind::Paren(_) => { + let forbid_let_reason = self.forbid_let_reason; + self.forbid_let_reason = Some(OtherForbidden); + noop_visit_expr(e, self); + self.forbid_let_reason = forbid_let_reason; + } + ExprKind::Cast(ref mut op, _) + | ExprKind::Type(ref mut op, _) => { + let forbid_let_reason = self.forbid_let_reason; + self.forbid_let_reason = Some(OtherForbidden); + self.visit_expr(op); + self.forbid_let_reason = forbid_let_reason; + } + ExprKind::Let(_, _, _, Some(_)) + | ExprKind::Array(_) + | ExprKind::ConstBlock(_) + | ExprKind::Lit(_) + | ExprKind::If(_, _, _) + | ExprKind::While(_, _, _) + | ExprKind::ForLoop(_, _, _, _) + | ExprKind::Loop(_, _, _) + | ExprKind::Match(_, _) + | ExprKind::Closure(_) + | ExprKind::Block(_, _) + | ExprKind::Async(_, _) + | ExprKind::TryBlock(_) + | ExprKind::Underscore + | ExprKind::Path(_, _) + | ExprKind::Break(_, _) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::InlineAsm(_) + | ExprKind::OffsetOf(_, _) + | ExprKind::MacCall(_) + | ExprKind::Struct(_) + | ExprKind::Repeat(_, _) + | ExprKind::Yield(_) + | ExprKind::Yeet(_) + | ExprKind::Become(_) + | ExprKind::IncludedBytes(_) + | ExprKind::FormatArgs(_) + | ExprKind::Err => { + // These would forbid any let expressions they contain already. + } + } + } +} diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 24c65d061..aad4edaba 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -73,12 +73,16 @@ impl<'a> Parser<'a> { if !self.maybe_consume_incorrect_semicolon(&items) { let msg = format!("expected item, found {token_str}"); let mut err = self.struct_span_err(self.token.span, msg); - let label = if self.is_kw_followed_by_ident(kw::Let) { - "consider using `const` or `static` instead of `let` for global variables" + let span = self.token.span; + if self.is_kw_followed_by_ident(kw::Let) { + err.span_label( + span, + "consider using `const` or `static` instead of `let` for global variables", + ); } else { - "expected item" + err.span_label(span, "expected item") + .note("for a full list of items that can appear in modules, see "); }; - err.span_label(self.token.span, label); return Err(err); } } @@ -1594,7 +1598,7 @@ impl<'a> Parser<'a> { Ok((class_name, ItemKind::Union(vdata, generics))) } - fn parse_record_struct_body( + pub(crate) fn parse_record_struct_body( &mut self, adt_ty: &str, ident_span: Span, @@ -1851,25 +1855,15 @@ impl<'a> Parser<'a> { attrs: AttrVec, ) -> PResult<'a, FieldDef> { let name = self.parse_field_ident(adt_ty, lo)?; - // Parse the macro invocation and recover if self.token.kind == token::Not { if let Err(mut err) = self.unexpected::() { - err.subdiagnostic(MacroExpandsToAdtField { adt_ty }).emit(); - self.bump(); - self.parse_delim_args()?; - return Ok(FieldDef { - span: DUMMY_SP, - ident: None, - vis, - id: DUMMY_NODE_ID, - ty: self.mk_ty(DUMMY_SP, TyKind::Err), - attrs, - is_placeholder: false, - }); + // Encounter the macro invocation + err.subdiagnostic(MacroExpandsToAdtField { adt_ty }); + return Err(err); } } self.expect_field_ty_separator()?; - let ty = self.parse_ty()?; + let ty = self.parse_ty_for_field_def()?; if self.token.kind == token::Colon && self.look_ahead(1, |tok| tok.kind != token::Colon) { self.sess.emit_err(errors::SingleColonStructType { span: self.token.span }); } @@ -1894,7 +1888,9 @@ impl<'a> Parser<'a> { /// for better diagnostics and suggestions. fn parse_field_ident(&mut self, adt_ty: &str, lo: Span) -> PResult<'a, Ident> { let (ident, is_raw) = self.ident_or_err(true)?; - if !is_raw && ident.is_reserved() { + if ident.name == kw::Underscore { + self.sess.gated_spans.gate(sym::unnamed_fields, lo); + } else if !is_raw && ident.is_reserved() { let snapshot = self.create_snapshot_for_diagnostic(); let err = if self.check_fn_front_matter(false, Case::Sensitive) { let inherited_vis = Visibility { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 77c59bb38..e84d8f5b3 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -13,6 +13,7 @@ mod ty; use crate::lexer::UnmatchedDelim; pub use attr_wrapper::AttrWrapper; pub use diagnostics::AttemptLocalParseRecovery; +pub(crate) use expr::ForbiddenLetReason; pub(crate) use item::FnParseMode; pub use pat::{CommaRecoveryMode, RecoverColon, RecoverComma}; pub use path::PathStyle; diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 2d888efb1..a25b0f1f8 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -136,6 +136,17 @@ impl<'a> Parser<'a> { ) } + /// Parse a type suitable for a field defintion. + /// The difference from `parse_ty` is that this version + /// allows anonymous structs and unions. + pub fn parse_ty_for_field_def(&mut self) -> PResult<'a, P> { + if self.can_begin_anon_struct_or_union() { + self.parse_anon_struct_or_union() + } else { + self.parse_ty() + } + } + /// Parse a type suitable for a function or function pointer parameter. /// The difference from `parse_ty` is that this version allows `...` /// (`CVarArgs`) at the top level of the type. @@ -336,6 +347,36 @@ impl<'a> Parser<'a> { if allow_qpath_recovery { self.maybe_recover_from_bad_qpath(ty) } else { Ok(ty) } } + /// Parse an anonymous struct or union (only for field definitions): + /// ```ignore (feature-not-ready) + /// #[repr(C)] + /// struct Foo { + /// _: struct { // anonymous struct + /// x: u32, + /// y: f64, + /// } + /// _: union { // anonymous union + /// z: u32, + /// w: f64, + /// } + /// } + /// ``` + fn parse_anon_struct_or_union(&mut self) -> PResult<'a, P> { + assert!(self.token.is_keyword(kw::Union) || self.token.is_keyword(kw::Struct)); + let is_union = self.token.is_keyword(kw::Union); + + let lo = self.token.span; + self.bump(); + + let (fields, _recovered) = + self.parse_record_struct_body(if is_union { "union" } else { "struct" }, lo, false)?; + let span = lo.to(self.prev_token.span); + self.sess.gated_spans.gate(sym::unnamed_fields, span); + // These can be rejected during AST validation in `deny_anon_struct_or_union`. + let kind = if is_union { TyKind::AnonUnion(fields) } else { TyKind::AnonStruct(fields) }; + Ok(self.mk_ty(span, kind)) + } + /// Parses either: /// - `(TYPE)`, a parenthesized type. /// - `(TYPE,)`, a tuple with a single field of type TYPE. @@ -696,6 +737,11 @@ impl<'a> Parser<'a> { Ok(bounds) } + pub(super) fn can_begin_anon_struct_or_union(&mut self) -> bool { + (self.token.is_keyword(kw::Struct) || self.token.is_keyword(kw::Union)) + && self.look_ahead(1, |t| t == &token::OpenDelim(Delimiter::Brace)) + } + /// Can the current token begin a bound? fn can_begin_bound(&mut self) -> bool { // This needs to be synchronized with `TokenKind::can_begin_bound`. @@ -845,18 +891,32 @@ impl<'a> Parser<'a> { // that we do not use the try operator when parsing the type because // if it fails then we get a parser error which we don't want (we're trying // to recover from errors, not make more). - let path = if self.may_recover() - && matches!(ty.kind, TyKind::Ptr(..) | TyKind::Ref(..)) - && let TyKind::Path(_, path) = &ty.peel_refs().kind { - // Just get the indirection part of the type. - let span = ty.span.until(path.span); - - err.span_suggestion_verbose( - span, - "consider removing the indirection", - "", - Applicability::MaybeIncorrect, - ); + let path = if self.may_recover() { + let (span, message, sugg, path, applicability) = match &ty.kind { + TyKind::Ptr(..) | TyKind::Ref(..) if let TyKind::Path(_, path) = &ty.peel_refs().kind => { + ( + ty.span.until(path.span), + "consider removing the indirection", + "", + path, + Applicability::MaybeIncorrect + ) + } + TyKind::ImplTrait(_, bounds) + if let [GenericBound::Trait(tr, ..), ..] = bounds.as_slice() => + { + ( + ty.span.until(tr.span), + "use the trait bounds directly", + "", + &tr.trait_ref.path, + Applicability::MachineApplicable + ) + } + _ => return Err(err) + }; + + err.span_suggestion_verbose(span, message, sugg, applicability); path.clone() } else { -- cgit v1.2.3