diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_parse/src/parser/attr.rs | 176 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/attr_wrapper.rs | 109 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/diagnostics.rs | 628 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/expr.rs | 1282 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/generics.rs | 45 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/item.rs | 353 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/mod.rs | 196 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/nonterminal.rs | 24 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/pat.rs | 83 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/path.rs | 22 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/stmt.rs | 211 | ||||
-rw-r--r-- | compiler/rustc_parse/src/parser/ty.rs | 50 |
12 files changed, 1559 insertions, 1620 deletions
diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index acdbddf40..9e4565694 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -1,29 +1,26 @@ +use crate::errors::{InvalidMetaItem, SuffixedLiteralInAttribute}; + use super::{AttrWrapper, Capturing, FnParseMode, ForceCollect, Parser, PathStyle}; use rustc_ast as ast; use rustc_ast::attr; use rustc_ast::token::{self, Delimiter, Nonterminal}; -use rustc_ast_pretty::pprust; -use rustc_errors::{error_code, Diagnostic, PResult}; +use rustc_errors::{error_code, fluent, Diagnostic, IntoDiagnostic, PResult}; use rustc_span::{sym, BytePos, Span}; use std::convert::TryInto; -use tracing::debug; - // Public for rustfmt usage #[derive(Debug)] -pub enum InnerAttrPolicy<'a> { +pub enum InnerAttrPolicy { Permitted, - Forbidden { reason: &'a str, saw_doc_comment: bool, prev_outer_attr_sp: Option<Span> }, + Forbidden(Option<InnerAttrForbiddenReason>), } -const DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG: &str = "an inner attribute is not \ - permitted in this context"; - -pub(super) const DEFAULT_INNER_ATTR_FORBIDDEN: InnerAttrPolicy<'_> = InnerAttrPolicy::Forbidden { - reason: DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG, - saw_doc_comment: false, - prev_outer_attr_sp: None, -}; +#[derive(Clone, Copy, Debug)] +pub enum InnerAttrForbiddenReason { + InCodeBlock, + AfterOuterDocComment { prev_doc_comment_span: Span }, + AfterOuterAttribute { prev_outer_attr_sp: Span }, +} enum OuterAttributeType { DocComment, @@ -34,7 +31,7 @@ enum OuterAttributeType { impl<'a> Parser<'a> { /// Parses attributes that appear before an item. pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, AttrWrapper> { - let mut outer_attrs: Vec<ast::Attribute> = Vec::new(); + let mut outer_attrs = ast::AttrVec::new(); let mut just_parsed_doc_comment = false; let start_pos = self.token_cursor.num_next_calls; loop { @@ -42,17 +39,15 @@ impl<'a> Parser<'a> { let prev_outer_attr_sp = outer_attrs.last().map(|attr| attr.span); let inner_error_reason = if just_parsed_doc_comment { - "an inner attribute is not permitted following an outer doc comment" - } else if prev_outer_attr_sp.is_some() { - "an inner attribute is not permitted following an outer attribute" + Some(InnerAttrForbiddenReason::AfterOuterDocComment { + prev_doc_comment_span: prev_outer_attr_sp.unwrap(), + }) + } else if let Some(prev_outer_attr_sp) = prev_outer_attr_sp { + Some(InnerAttrForbiddenReason::AfterOuterAttribute { prev_outer_attr_sp }) } else { - DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG - }; - let inner_parse_policy = InnerAttrPolicy::Forbidden { - reason: inner_error_reason, - saw_doc_comment: just_parsed_doc_comment, - prev_outer_attr_sp, + None }; + let inner_parse_policy = InnerAttrPolicy::Forbidden(inner_error_reason); just_parsed_doc_comment = false; Some(self.parse_attribute(inner_parse_policy)?) } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind { @@ -60,7 +55,7 @@ impl<'a> Parser<'a> { let span = self.token.span; let mut err = self.sess.span_diagnostic.struct_span_err_with_code( span, - "expected outer doc comment", + fluent::parser_inner_doc_comment_not_permitted, error_code!(E0753), ); if let Some(replacement_span) = self.annotate_following_item_if_applicable( @@ -71,13 +66,10 @@ impl<'a> Parser<'a> { token::CommentKind::Block => OuterAttributeType::DocBlockComment, }, ) { - err.note( - "inner doc comments like this (starting with `//!` or `/*!`) can \ - only appear before items", - ); + err.note(fluent::note); err.span_suggestion_verbose( replacement_span, - "you might have meant to write a regular comment", + fluent::suggestion, "", rustc_errors::Applicability::MachineApplicable, ); @@ -89,6 +81,7 @@ impl<'a> Parser<'a> { // Always make an outer attribute - this allows us to recover from a misplaced // inner attribute. Some(attr::mk_doc_comment( + &self.sess.attr_id_generator, comment_kind, ast::AttrStyle::Outer, data, @@ -106,7 +99,7 @@ impl<'a> Parser<'a> { break; } } - Ok(AttrWrapper::new(outer_attrs.into(), start_pos)) + Ok(AttrWrapper::new(outer_attrs, start_pos)) } /// Matches `attribute = # ! [ meta_item ]`. @@ -114,7 +107,7 @@ impl<'a> Parser<'a> { // Public for rustfmt usage. pub fn parse_attribute( &mut self, - inner_parse_policy: InnerAttrPolicy<'_>, + inner_parse_policy: InnerAttrPolicy, ) -> PResult<'a, ast::Attribute> { debug!( "parse_attribute: inner_parse_policy={:?} self.token={:?}", @@ -123,29 +116,22 @@ impl<'a> Parser<'a> { let lo = self.token.span; // Attributes can't have attributes of their own [Editor's note: not with that attitude] self.collect_tokens_no_attrs(|this| { - if this.eat(&token::Pound) { - let style = if this.eat(&token::Not) { - ast::AttrStyle::Inner - } else { - ast::AttrStyle::Outer - }; + assert!(this.eat(&token::Pound), "parse_attribute called in non-attribute position"); - this.expect(&token::OpenDelim(Delimiter::Bracket))?; - let item = this.parse_attr_item(false)?; - this.expect(&token::CloseDelim(Delimiter::Bracket))?; - let attr_sp = lo.to(this.prev_token.span); + let style = + if this.eat(&token::Not) { ast::AttrStyle::Inner } else { ast::AttrStyle::Outer }; - // Emit error if inner attribute is encountered and forbidden. - if style == ast::AttrStyle::Inner { - this.error_on_forbidden_inner_attr(attr_sp, inner_parse_policy); - } + this.expect(&token::OpenDelim(Delimiter::Bracket))?; + let item = this.parse_attr_item(false)?; + this.expect(&token::CloseDelim(Delimiter::Bracket))?; + let attr_sp = lo.to(this.prev_token.span); - Ok(attr::mk_attr_from_item(item, None, style, attr_sp)) - } else { - let token_str = pprust::token_to_string(&this.token); - let msg = &format!("expected `#`, found `{token_str}`"); - Err(this.struct_span_err(this.token.span, msg)) + // Emit error if inner attribute is encountered and forbidden. + if style == ast::AttrStyle::Inner { + this.error_on_forbidden_inner_attr(attr_sp, inner_parse_policy); } + + Ok(attr::mk_attr_from_item(&self.sess.attr_id_generator, item, None, style, attr_sp)) }) } @@ -185,21 +171,12 @@ impl<'a> Parser<'a> { ForceCollect::No, ) { Ok(Some(item)) => { - let attr_name = match attr_type { - OuterAttributeType::Attribute => "attribute", - _ => "doc comment", - }; - err.span_label( - item.span, - &format!("the inner {} doesn't annotate this {}", attr_name, item.kind.descr()), - ); + // FIXME(#100717) + err.set_arg("item", item.kind.descr()); + err.span_label(item.span, fluent::label_does_not_annotate_this); err.span_suggestion_verbose( replacement_span, - &format!( - "to annotate the {}, change the {} from inner to outer style", - item.kind.descr(), - attr_name - ), + fluent::sugg_change_inner_to_outer, match attr_type { OuterAttributeType::Attribute => "", OuterAttributeType::DocBlockComment => "*", @@ -217,22 +194,33 @@ impl<'a> Parser<'a> { Some(replacement_span) } - pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy<'_>) { - if let InnerAttrPolicy::Forbidden { reason, saw_doc_comment, prev_outer_attr_sp } = policy { - let prev_outer_attr_note = - if saw_doc_comment { "previous doc comment" } else { "previous outer attribute" }; - - let mut diag = self.struct_span_err(attr_sp, reason); - - if let Some(prev_outer_attr_sp) = prev_outer_attr_sp { - diag.span_label(attr_sp, "not permitted following an outer attribute") - .span_label(prev_outer_attr_sp, prev_outer_attr_note); - } + pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy) { + if let InnerAttrPolicy::Forbidden(reason) = policy { + let mut diag = match reason.as_ref().copied() { + Some(InnerAttrForbiddenReason::AfterOuterDocComment { prev_doc_comment_span }) => { + let mut diag = self.struct_span_err( + attr_sp, + fluent::parser_inner_attr_not_permitted_after_outer_doc_comment, + ); + diag.span_label(attr_sp, fluent::label_attr) + .span_label(prev_doc_comment_span, fluent::label_prev_doc_comment); + diag + } + Some(InnerAttrForbiddenReason::AfterOuterAttribute { prev_outer_attr_sp }) => { + let mut diag = self.struct_span_err( + attr_sp, + fluent::parser_inner_attr_not_permitted_after_outer_attr, + ); + diag.span_label(attr_sp, fluent::label_attr) + .span_label(prev_outer_attr_sp, fluent::label_prev_attr); + diag + } + Some(InnerAttrForbiddenReason::InCodeBlock) | None => { + self.struct_span_err(attr_sp, fluent::parser_inner_attr_not_permitted) + } + }; - diag.note( - "inner attributes, like `#![no_std]`, annotate the item enclosing them, and \ - are usually found at the beginning of source files", - ); + diag.note(fluent::parser_inner_attr_explanation); if self .annotate_following_item_if_applicable( &mut diag, @@ -241,7 +229,7 @@ impl<'a> Parser<'a> { ) .is_some() { - diag.note("outer attributes, like `#[test]`, annotate the item following them"); + diag.note(fluent::parser_outer_attr_explanation); }; diag.emit(); } @@ -283,8 +271,8 @@ impl<'a> Parser<'a> { /// terminated by a semicolon. /// /// Matches `inner_attrs*`. - pub(crate) fn parse_inner_attributes(&mut self) -> PResult<'a, Vec<ast::Attribute>> { - let mut attrs: Vec<ast::Attribute> = vec![]; + pub(crate) fn parse_inner_attributes(&mut self) -> PResult<'a, ast::AttrVec> { + let mut attrs = ast::AttrVec::new(); loop { let start_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap(); // Only try to parse if it is an inner attribute (has `!`). @@ -293,7 +281,13 @@ impl<'a> Parser<'a> { } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind { if attr_style == ast::AttrStyle::Inner { self.bump(); - Some(attr::mk_doc_comment(comment_kind, attr_style, data, self.prev_token.span)) + Some(attr::mk_doc_comment( + &self.sess.attr_id_generator, + comment_kind, + attr_style, + data, + self.prev_token.span, + )) } else { None } @@ -303,9 +297,9 @@ impl<'a> Parser<'a> { if let Some(attr) = attr { let end_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap(); // If we are currently capturing tokens, mark the location of this inner attribute. - // If capturing ends up creating a `LazyTokenStream`, we will include + // If capturing ends up creating a `LazyAttrTokenStream`, we will include // this replace range with it, removing the inner attribute from the final - // `AttrAnnotatedTokenStream`. Inner attributes are stored in the parsed AST note. + // `AttrTokenStream`. Inner attributes are stored in the parsed AST note. // During macro expansion, they are selectively inserted back into the // token stream (the first inner attribute is removed each time we invoke the // corresponding macro). @@ -326,12 +320,7 @@ impl<'a> Parser<'a> { debug!("checking if {:?} is unusuffixed", lit); if !lit.kind.is_unsuffixed() { - self.struct_span_err(lit.span, "suffixed literals are not allowed in attributes") - .help( - "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \ - use an unsuffixed version (`1`, `1.0`, etc.)", - ) - .emit(); + self.sess.emit_err(SuffixedLiteralInAttribute { span: lit.span }); } Ok(lit) @@ -424,9 +413,8 @@ impl<'a> Parser<'a> { Err(err) => err.cancel(), } - let found = pprust::token_to_string(&self.token); - let msg = format!("expected unsuffixed literal or identifier, found `{found}`"); - Err(self.struct_span_err(self.token.span, &msg)) + Err(InvalidMetaItem { span: self.token.span, token: self.token.clone() } + .into_diagnostic(&self.sess.span_diagnostic)) } } diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs index 6c750ff42..1b16ecb5e 100644 --- a/compiler/rustc_parse/src/parser/attr_wrapper.rs +++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs @@ -1,7 +1,7 @@ use super::{Capturing, FlatToken, ForceCollect, Parser, ReplaceRange, TokenCursor, TrailingToken}; use rustc_ast::token::{self, Delimiter, Token, TokenKind}; -use rustc_ast::tokenstream::{AttrAnnotatedTokenStream, AttributesData, CreateTokenStream}; -use rustc_ast::tokenstream::{AttrAnnotatedTokenTree, DelimSpan, LazyTokenStream, Spacing}; +use rustc_ast::tokenstream::{AttrTokenStream, AttributesData, ToAttrTokenStream}; +use rustc_ast::tokenstream::{AttrTokenTree, DelimSpan, LazyAttrTokenStream, Spacing}; use rustc_ast::{self as ast}; use rustc_ast::{AttrVec, Attribute, HasAttrs, HasTokens}; use rustc_errors::PResult; @@ -15,11 +15,11 @@ use std::ops::Range; /// for the attribute target. This allows us to perform cfg-expansion on /// a token stream before we invoke a derive proc-macro. /// -/// This wrapper prevents direct access to the underlying `Vec<ast::Attribute>`. +/// This wrapper prevents direct access to the underlying `ast::AttrVec>`. /// Parsing code can only get access to the underlying attributes /// by passing an `AttrWrapper` to `collect_tokens_trailing_tokens`. /// This makes it difficult to accidentally construct an AST node -/// (which stores a `Vec<ast::Attribute>`) without first collecting tokens. +/// (which stores an `ast::AttrVec`) without first collecting tokens. /// /// This struct has its own module, to ensure that the parser code /// cannot directly access the `attrs` field @@ -32,11 +32,6 @@ pub struct AttrWrapper { start_pos: usize, } -// This struct is passed around very frequently, -// so make sure it doesn't accidentally get larger -#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -rustc_data_structures::static_assert_size!(AttrWrapper, 16); - impl AttrWrapper { pub(super) fn new(attrs: AttrVec, start_pos: usize) -> AttrWrapper { AttrWrapper { attrs, start_pos } @@ -49,9 +44,10 @@ impl AttrWrapper { self.attrs } + // Prepend `self.attrs` to `attrs`. // FIXME: require passing an NT to prevent misuse of this method - pub(crate) fn prepend_to_nt_inner(self, attrs: &mut Vec<Attribute>) { - let mut self_attrs: Vec<_> = self.attrs.into(); + pub(crate) fn prepend_to_nt_inner(self, attrs: &mut AttrVec) { + let mut self_attrs = self.attrs; std::mem::swap(attrs, &mut self_attrs); attrs.extend(self_attrs); } @@ -87,7 +83,7 @@ fn has_cfg_or_cfg_attr(attrs: &[Attribute]) -> bool { // This also makes `Parser` very cheap to clone, since // there is no intermediate collection buffer to clone. #[derive(Clone)] -struct LazyTokenStreamImpl { +struct LazyAttrTokenStreamImpl { start_token: (Token, Spacing), cursor_snapshot: TokenCursor, num_calls: usize, @@ -95,11 +91,8 @@ struct LazyTokenStreamImpl { replace_ranges: Box<[ReplaceRange]>, } -#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -rustc_data_structures::static_assert_size!(LazyTokenStreamImpl, 144); - -impl CreateTokenStream for LazyTokenStreamImpl { - fn create_token_stream(&self) -> AttrAnnotatedTokenStream { +impl ToAttrTokenStream for LazyAttrTokenStreamImpl { + fn to_attr_token_stream(&self) -> AttrTokenStream { // The token produced by the final call to `{,inlined_}next` was not // actually consumed by the callback. The combination of chaining the // initial token and using `take` produces the desired result - we @@ -116,7 +109,7 @@ impl CreateTokenStream for LazyTokenStreamImpl { if !self.replace_ranges.is_empty() { let mut tokens: Vec<_> = tokens.collect(); - let mut replace_ranges = self.replace_ranges.clone(); + let mut replace_ranges = self.replace_ranges.to_vec(); replace_ranges.sort_by_key(|(range, _)| range.start); #[cfg(debug_assertions)] @@ -146,7 +139,7 @@ impl CreateTokenStream for LazyTokenStreamImpl { // start position, we ensure that any replace range which encloses // another replace range will capture the *replaced* tokens for the inner // range, not the original tokens. - for (range, new_tokens) in replace_ranges.iter().rev() { + for (range, new_tokens) in replace_ranges.into_iter().rev() { assert!(!range.is_empty(), "Cannot replace an empty range: {:?}", range); // Replace ranges are only allowed to decrease the number of tokens. assert!( @@ -165,7 +158,7 @@ impl CreateTokenStream for LazyTokenStreamImpl { tokens.splice( (range.start as usize)..(range.end as usize), - new_tokens.clone().into_iter().chain(filler), + new_tokens.into_iter().chain(filler), ); } make_token_stream(tokens.into_iter(), self.break_last_token) @@ -178,7 +171,7 @@ impl CreateTokenStream for LazyTokenStreamImpl { impl<'a> Parser<'a> { /// Records all tokens consumed by the provided callback, /// including the current token. These tokens are collected - /// into a `LazyTokenStream`, and returned along with the result + /// into a `LazyAttrTokenStream`, and returned along with the result /// of the callback. /// /// Note: If your callback consumes an opening delimiter @@ -196,7 +189,7 @@ impl<'a> Parser<'a> { &mut self, attrs: AttrWrapper, force_collect: ForceCollect, - f: impl FnOnce(&mut Self, Vec<ast::Attribute>) -> PResult<'a, (R, TrailingToken)>, + f: impl FnOnce(&mut Self, ast::AttrVec) -> PResult<'a, (R, TrailingToken)>, ) -> PResult<'a, R> { // We only bail out when nothing could possibly observe the collected tokens: // 1. We cannot be force collecting tokens (since force-collecting requires tokens @@ -212,7 +205,7 @@ impl<'a> Parser<'a> { // or `#[cfg_attr]` attributes. && !self.capture_cfg { - return Ok(f(self, attrs.attrs.into())?.0); + return Ok(f(self, attrs.attrs)?.0); } let start_token = (self.token.clone(), self.token_spacing); @@ -222,7 +215,7 @@ impl<'a> Parser<'a> { let prev_capturing = std::mem::replace(&mut self.capture_state.capturing, Capturing::Yes); let replace_ranges_start = self.capture_state.replace_ranges.len(); - let ret = f(self, attrs.attrs.into()); + let ret = f(self, attrs.attrs); self.capture_state.capturing = prev_capturing; @@ -280,30 +273,33 @@ impl<'a> Parser<'a> { let cursor_snapshot_next_calls = cursor_snapshot.num_next_calls; let mut end_pos = self.token_cursor.num_next_calls; + let mut captured_trailing = false; + // Capture a trailing token if requested by the callback 'f' match trailing { TrailingToken::None => {} + TrailingToken::Gt => { + assert_eq!(self.token.kind, token::Gt); + } TrailingToken::Semi => { assert_eq!(self.token.kind, token::Semi); end_pos += 1; + captured_trailing = true; } TrailingToken::MaybeComma => { if self.token.kind == token::Comma { end_pos += 1; + captured_trailing = true; } } } // If we 'broke' the last token (e.g. breaking a '>>' token to two '>' tokens), // then extend the range of captured tokens to include it, since the parser - // was not actually bumped past it. When the `LazyTokenStream` gets converted - // into an `AttrAnnotatedTokenStream`, we will create the proper token. + // was not actually bumped past it. When the `LazyAttrTokenStream` gets converted + // into an `AttrTokenStream`, we will create the proper token. if self.token_cursor.break_last_token { - assert_eq!( - trailing, - TrailingToken::None, - "Cannot set `break_last_token` and have trailing token" - ); + assert!(!captured_trailing, "Cannot set break_last_token and have trailing token"); end_pos += 1; } @@ -315,20 +311,20 @@ impl<'a> Parser<'a> { Box::new([]) } else { // Grab any replace ranges that occur *inside* the current AST node. - // We will perform the actual replacement when we convert the `LazyTokenStream` - // to an `AttrAnnotatedTokenStream` + // We will perform the actual replacement when we convert the `LazyAttrTokenStream` + // to an `AttrTokenStream`. let start_calls: u32 = cursor_snapshot_next_calls.try_into().unwrap(); self.capture_state.replace_ranges[replace_ranges_start..replace_ranges_end] .iter() .cloned() - .chain(inner_attr_replace_ranges.clone().into_iter()) + .chain(inner_attr_replace_ranges.iter().cloned()) .map(|(range, tokens)| { ((range.start - start_calls)..(range.end - start_calls), tokens) }) .collect() }; - let tokens = LazyTokenStream::new(LazyTokenStreamImpl { + let tokens = LazyAttrTokenStream::new(LazyAttrTokenStreamImpl { start_token, num_calls, cursor_snapshot, @@ -352,9 +348,9 @@ impl<'a> Parser<'a> { // on the captured token stream. if self.capture_cfg && matches!(self.capture_state.capturing, Capturing::Yes) - && has_cfg_or_cfg_attr(&final_attrs) + && has_cfg_or_cfg_attr(final_attrs) { - let attr_data = AttributesData { attrs: final_attrs.to_vec().into(), tokens }; + let attr_data = AttributesData { attrs: final_attrs.iter().cloned().collect(), tokens }; // Replace the entire AST node that we just parsed, including attributes, // with a `FlatToken::AttrTarget`. If this AST node is inside an item @@ -391,12 +387,12 @@ impl<'a> Parser<'a> { fn make_token_stream( mut iter: impl Iterator<Item = (FlatToken, Spacing)>, break_last_token: bool, -) -> AttrAnnotatedTokenStream { +) -> AttrTokenStream { #[derive(Debug)] struct FrameData { // This is `None` for the first frame, `Some` for all others. open_delim_sp: Option<(Delimiter, Span)>, - inner: Vec<(AttrAnnotatedTokenTree, Spacing)>, + inner: Vec<AttrTokenTree>, } let mut stack = vec![FrameData { open_delim_sp: None, inner: vec![] }]; let mut token_and_spacing = iter.next(); @@ -417,48 +413,57 @@ fn make_token_stream( open_delim, span ); let dspan = DelimSpan::from_pair(open_sp, span); - let stream = AttrAnnotatedTokenStream::new(frame_data.inner); - let delimited = AttrAnnotatedTokenTree::Delimited(dspan, delim, stream); + let stream = AttrTokenStream::new(frame_data.inner); + let delimited = AttrTokenTree::Delimited(dspan, delim, stream); stack .last_mut() .unwrap_or_else(|| { panic!("Bottom token frame is missing for token: {:?}", token) }) .inner - .push((delimited, Spacing::Alone)); + .push(delimited); } FlatToken::Token(token) => stack .last_mut() .expect("Bottom token frame is missing!") .inner - .push((AttrAnnotatedTokenTree::Token(token), spacing)), + .push(AttrTokenTree::Token(token, spacing)), FlatToken::AttrTarget(data) => stack .last_mut() .expect("Bottom token frame is missing!") .inner - .push((AttrAnnotatedTokenTree::Attributes(data), spacing)), + .push(AttrTokenTree::Attributes(data)), FlatToken::Empty => {} } token_and_spacing = iter.next(); } let mut final_buf = stack.pop().expect("Missing final buf!"); if break_last_token { - let (last_token, spacing) = final_buf.inner.pop().unwrap(); - if let AttrAnnotatedTokenTree::Token(last_token) = last_token { + let last_token = final_buf.inner.pop().unwrap(); + if let AttrTokenTree::Token(last_token, spacing) = last_token { let unglued_first = last_token.kind.break_two_token_op().unwrap().0; // An 'unglued' token is always two ASCII characters let mut first_span = last_token.span.shrink_to_lo(); first_span = first_span.with_hi(first_span.lo() + rustc_span::BytePos(1)); - final_buf.inner.push(( - AttrAnnotatedTokenTree::Token(Token::new(unglued_first, first_span)), - spacing, - )); + final_buf + .inner + .push(AttrTokenTree::Token(Token::new(unglued_first, first_span), spacing)); } else { panic!("Unexpected last token {:?}", last_token) } } - assert!(stack.is_empty(), "Stack should be empty: final_buf={:?} stack={:?}", final_buf, stack); - AttrAnnotatedTokenStream::new(final_buf.inner) + AttrTokenStream::new(final_buf.inner) +} + +// Some types are used a lot. Make sure they don't unintentionally get bigger. +#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +mod size_asserts { + use super::*; + use rustc_data_structures::static_assert_size; + // tidy-alphabetical-start + static_assert_size!(AttrWrapper, 16); + static_assert_size!(LazyAttrTokenStreamImpl, 144); + // tidy-alphabetical-end } diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index a2155ac1d..309717350 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -3,6 +3,19 @@ use super::{ BlockMode, CommaRecoveryMode, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenExpectType, TokenType, }; +use crate::errors::{ + AmbiguousPlus, AttributeOnParamType, BadQPathStage2, BadTypePlus, BadTypePlusSub, + ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg, + ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, DocCommentOnParamType, + DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, + GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg, InInTypo, + IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, ParenthesesInForHead, + ParenthesesInForHeadSugg, PatternMethodParamWithoutBody, QuestionMarkInType, + QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath, + StructLiteralBodyWithoutPathSugg, SuggEscapeToUseAsIdentifier, SuggRemoveComma, + UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, + UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, +}; use crate::lexer::UnmatchedBrace; use rustc_ast as ast; @@ -10,35 +23,31 @@ use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Lit, LitKind, TokenKind}; use rustc_ast::util::parser::AssocOp; use rustc_ast::{ - AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingMode, Block, - BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind, Mutability, Param, Pat, - PatKind, Path, PathSegment, QSelf, Ty, TyKind, + AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingAnnotation, Block, + BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind, Param, Pat, PatKind, + Path, PathSegment, QSelf, Ty, TyKind, }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{ fluent, Applicability, DiagnosticBuilder, DiagnosticMessage, Handler, MultiSpan, PResult, }; -use rustc_errors::{pluralize, struct_span_err, Diagnostic, EmissionGuarantee, ErrorGuaranteed}; -use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic}; +use rustc_errors::{pluralize, Diagnostic, ErrorGuaranteed, IntoDiagnostic}; +use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::source_map::Spanned; -use rustc_span::symbol::{kw, Ident}; +use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{Span, SpanSnippetError, DUMMY_SP}; use std::ops::{Deref, DerefMut}; use std::mem::take; use crate::parser; -use tracing::{debug, trace}; - -const TURBOFISH_SUGGESTION_STR: &str = - "use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments"; /// Creates a placeholder argument. pub(super) fn dummy_arg(ident: Ident) -> Param { let pat = P(Pat { id: ast::DUMMY_NODE_ID, - kind: PatKind::Ident(BindingMode::ByValue(Mutability::Not), ident, None), + kind: PatKind::Ident(BindingAnnotation::NONE, ident, None), span: ident.span, tokens: None, }); @@ -53,34 +62,6 @@ pub(super) fn dummy_arg(ident: Ident) -> Param { } } -pub enum Error { - UselessDocComment, -} - -impl Error { - fn span_err( - self, - sp: impl Into<MultiSpan>, - handler: &Handler, - ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { - match self { - Error::UselessDocComment => { - let mut err = struct_span_err!( - handler, - sp, - E0585, - "found a documentation comment that doesn't document anything", - ); - err.help( - "doc comments must come before what they document, maybe a comment was \ - intended with `//`?", - ); - err - } - } - } -} - pub(super) trait RecoverQPath: Sized + 'static { const PATH_STYLE: PathStyle = PathStyle::Expr; fn to_ty(&self) -> Option<P<Ty>>; @@ -228,13 +209,13 @@ struct MultiSugg { } impl MultiSugg { - fn emit<G: EmissionGuarantee>(self, err: &mut DiagnosticBuilder<'_, G>) { + fn emit(self, err: &mut Diagnostic) { err.multipart_suggestion(&self.msg, self.patches, self.applicability); } /// Overrides individual messages and applicabilities. - fn emit_many<G: EmissionGuarantee>( - err: &mut DiagnosticBuilder<'_, G>, + fn emit_many( + err: &mut Diagnostic, msg: &str, applicability: Applicability, suggestions: impl Iterator<Item = Self>, @@ -243,97 +224,6 @@ impl MultiSugg { } } -#[derive(SessionDiagnostic)] -#[error(parser::maybe_report_ambiguous_plus)] -struct AmbiguousPlus { - pub sum_ty: String, - #[primary_span] - #[suggestion(code = "({sum_ty})")] - pub span: Span, -} - -#[derive(SessionDiagnostic)] -#[error(parser::maybe_recover_from_bad_type_plus, code = "E0178")] -struct BadTypePlus { - pub ty: String, - #[primary_span] - pub span: Span, - #[subdiagnostic] - pub sub: BadTypePlusSub, -} - -#[derive(SessionSubdiagnostic)] -pub enum BadTypePlusSub { - #[suggestion( - parser::add_paren, - code = "{sum_with_parens}", - applicability = "machine-applicable" - )] - AddParen { - sum_with_parens: String, - #[primary_span] - span: Span, - }, - #[label(parser::forgot_paren)] - ForgotParen { - #[primary_span] - span: Span, - }, - #[label(parser::expect_path)] - ExpectPath { - #[primary_span] - span: Span, - }, -} - -#[derive(SessionDiagnostic)] -#[error(parser::maybe_recover_from_bad_qpath_stage_2)] -struct BadQPathStage2 { - #[primary_span] - #[suggestion(applicability = "maybe-incorrect")] - span: Span, - ty: String, -} - -#[derive(SessionDiagnostic)] -#[error(parser::incorrect_semicolon)] -struct IncorrectSemicolon<'a> { - #[primary_span] - #[suggestion_short(applicability = "machine-applicable")] - span: Span, - #[help] - opt_help: Option<()>, - name: &'a str, -} - -#[derive(SessionDiagnostic)] -#[error(parser::incorrect_use_of_await)] -struct IncorrectUseOfAwait { - #[primary_span] - #[suggestion(parser::parentheses_suggestion, applicability = "machine-applicable")] - span: Span, -} - -#[derive(SessionDiagnostic)] -#[error(parser::incorrect_use_of_await)] -struct IncorrectAwait { - #[primary_span] - span: Span, - #[suggestion(parser::postfix_suggestion, code = "{expr}.await{question_mark}")] - sugg_span: (Span, Applicability), - expr: String, - question_mark: &'static str, -} - -#[derive(SessionDiagnostic)] -#[error(parser::in_in_typo)] -struct InInTypo { - #[primary_span] - span: Span, - #[suggestion(applicability = "machine-applicable")] - sugg_span: Span, -} - // SnapshotParser is used to create a snapshot of the parser // without causing duplicate errors being emitted when the `Parser` // is dropped. @@ -358,15 +248,6 @@ impl<'a> DerefMut for SnapshotParser<'a> { impl<'a> Parser<'a> { #[rustc_lint_diagnostics] - pub(super) fn span_err<S: Into<MultiSpan>>( - &self, - sp: S, - err: Error, - ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { - err.span_err(sp, self.diagnostic()) - } - - #[rustc_lint_diagnostics] pub fn struct_span_err<S: Into<MultiSpan>>( &self, sp: S, @@ -387,7 +268,7 @@ impl<'a> Parser<'a> { /// This is to avoid losing unclosed delims errors `create_snapshot_for_diagnostic` clears. pub(super) fn restore_snapshot(&mut self, snapshot: SnapshotParser<'a>) { *self = snapshot.parser; - self.unclosed_delims.extend(snapshot.unclosed_delims.clone()); + self.unclosed_delims.extend(snapshot.unclosed_delims); } pub fn unclosed_delims(&self) -> &[UnmatchedBrace] { @@ -411,10 +292,6 @@ impl<'a> Parser<'a> { } pub(super) fn expected_ident_found(&self) -> DiagnosticBuilder<'a, ErrorGuaranteed> { - let mut err = self.struct_span_err( - self.token.span, - &format!("expected identifier, found {}", super::token_descr(&self.token)), - ); let valid_follow = &[ TokenKind::Eq, TokenKind::Colon, @@ -426,34 +303,35 @@ impl<'a> Parser<'a> { TokenKind::CloseDelim(Delimiter::Brace), TokenKind::CloseDelim(Delimiter::Parenthesis), ]; - match self.token.ident() { + let suggest_raw = match self.token.ident() { Some((ident, false)) if ident.is_raw_guess() && self.look_ahead(1, |t| valid_follow.contains(&t.kind)) => { - err.span_suggestion_verbose( - ident.span.shrink_to_lo(), - &format!("escape `{}` to use it as an identifier", ident.name), - "r#", - Applicability::MaybeIncorrect, - ); + Some(SuggEscapeToUseAsIdentifier { + span: ident.span.shrink_to_lo(), + // `Symbol::to_string()` is different from `Symbol::into_diagnostic_arg()`, + // which uses `Symbol::to_ident_string()` and "helpfully" adds an implicit `r#` + ident_name: ident.name.to_string(), + }) } - _ => {} - } - if let Some(token_descr) = super::token_descr_opt(&self.token) { - err.span_label(self.token.span, format!("expected identifier, found {}", token_descr)); - } else { - err.span_label(self.token.span, "expected identifier"); + _ => None, + }; + + let suggest_remove_comma = if self.token == token::Comma && self.look_ahead(1, |t| t.is_ident()) { - err.span_suggestion( - self.token.span, - "remove this comma", - "", - Applicability::MachineApplicable, - ); - } - } - err + Some(SuggRemoveComma { span: self.token.span }) + } else { + None + }; + + let err = ExpectedIdentifier { + span: self.token.span, + token: self.token.clone(), + suggest_raw, + suggest_remove_comma, + }; + err.into_diagnostic(&self.sess.span_diagnostic) } pub(super) fn expected_one_of_not_found( @@ -518,8 +396,8 @@ impl<'a> Parser<'a> { expected.dedup(); let sm = self.sess.source_map(); - let msg = format!("expected `;`, found {}", super::token_descr(&self.token)); - let appl = Applicability::MachineApplicable; + + // Special-case "expected `;`" errors if expected.contains(&TokenType::Token(token::Semi)) { if self.token.span == DUMMY_SP || self.prev_token.span == DUMMY_SP { // Likely inside a macro, can't provide meaningful suggestions. @@ -547,18 +425,22 @@ impl<'a> Parser<'a> { // // let x = 32: // let y = 42; + self.sess.emit_err(ExpectedSemi { + span: self.token.span, + token: self.token.clone(), + unexpected_token_label: None, + sugg: ExpectedSemiSugg::ChangeToSemi(self.token.span), + }); self.bump(); - let sp = self.prev_token.span; - self.struct_span_err(sp, &msg) - .span_suggestion_short(sp, "change this to `;`", ";", appl) - .emit(); return Ok(true); } else if self.look_ahead(0, |t| { t == &token::CloseDelim(Delimiter::Brace) - || (t.can_begin_expr() && t != &token::Semi && t != &token::Pound) + || ((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()) + self.prev_token.span.shrink_to_hi().until(self.token.span.shrink_to_lo()), ) && t == &token::Pound) }) && !expected.contains(&TokenType::Token(token::Comma)) { @@ -567,15 +449,25 @@ impl<'a> Parser<'a> { // // let x = 32 // let y = 42; - let sp = self.prev_token.span.shrink_to_hi(); - self.struct_span_err(sp, &msg) - .span_label(self.token.span, "unexpected token") - .span_suggestion_short(sp, "add `;` here", ";", appl) - .emit(); + let span = self.prev_token.span.shrink_to_hi(); + self.sess.emit_err(ExpectedSemi { + span, + token: self.token.clone(), + unexpected_token_label: Some(self.token.span), + sugg: ExpectedSemiSugg::AddSemi(span), + }); return Ok(true); } } + if self.token.kind == TokenKind::EqEq + && self.prev_token.is_ident() + && expected.iter().any(|tok| matches!(tok, TokenType::Token(TokenKind::Eq))) + { + // Likely typo: `=` → `==` in let expr or enum item + return Err(self.sess.create_err(UseEqInstead { span: self.token.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 { @@ -590,7 +482,7 @@ impl<'a> Parser<'a> { ) } else if expected.is_empty() { ( - format!("unexpected token: {}", actual), + format!("unexpected token: {actual}"), (self.prev_token.span, "unexpected token after this".to_string()), ) } else { @@ -600,19 +492,33 @@ impl<'a> Parser<'a> { ) }; self.last_unexpected_token_span = Some(self.token.span); + // FIXME: translation requires list formatting (for `expect`) let mut err = self.struct_span_err(self.token.span, &msg_exp); if let TokenKind::Ident(symbol, _) = &self.prev_token.kind { - if symbol.as_str() == "public" { + if ["def", "fun", "func", "function"].contains(&symbol.as_str()) { err.span_suggestion_short( self.prev_token.span, - "write `pub` instead of `public` to make the item public", - "pub", - appl, + &format!("write `fn` instead of `{symbol}` to declare a function"), + "fn", + Applicability::MachineApplicable, ); } } + // `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.kind == TokenKind::OpenDelim(Delimiter::Parenthesis)) + { + err.span_suggestion_short( + self.prev_token.span, + "write `pub` instead of `public` to make the item public", + "pub", + Applicability::MachineApplicable, + ); + } + // Add suggestion for a missing closing angle bracket if '>' is included in expected_tokens // there are unclosed angle brackets if self.unmatched_angle_bracket_count > 0 @@ -734,7 +640,7 @@ impl<'a> Parser<'a> { let mut snapshot = self.create_snapshot_for_diagnostic(); let path = Path { segments: vec![], span: self.prev_token.span.shrink_to_lo(), tokens: None }; - let struct_expr = snapshot.parse_struct_expr(None, path, AttrVec::new(), false); + let struct_expr = snapshot.parse_struct_expr(None, path, false); let block_tail = self.parse_block_tail(lo, s, AttemptLocalParseRecovery::No); return Some(match (struct_expr, block_tail) { (Ok(expr), Err(mut err)) => { @@ -747,19 +653,13 @@ impl<'a> Parser<'a> { // field: value, // } } err.delay_as_bug(); - self.struct_span_err( - expr.span, - fluent::parser::struct_literal_body_without_path, - ) - .multipart_suggestion( - fluent::parser::suggestion, - vec![ - (expr.span.shrink_to_lo(), "{ SomeStruct ".to_string()), - (expr.span.shrink_to_hi(), " }".to_string()), - ], - Applicability::MaybeIncorrect, - ) - .emit(); + self.sess.emit_err(StructLiteralBodyWithoutPath { + span: expr.span, + sugg: StructLiteralBodyWithoutPathSugg { + before: expr.span.shrink_to_lo(), + after: expr.span.shrink_to_hi(), + }, + }); self.restore_snapshot(snapshot); let mut tail = self.mk_block( vec![self.mk_stmt_err(expr.span)], @@ -953,18 +853,8 @@ impl<'a> Parser<'a> { self.eat_to_tokens(end); let span = lo.until(self.token.span); - let total_num_of_gt = number_of_gt + number_of_shr * 2; - self.struct_span_err( - span, - &format!("unmatched angle bracket{}", pluralize!(total_num_of_gt)), - ) - .span_suggestion( - span, - &format!("remove extra angle bracket{}", pluralize!(total_num_of_gt)), - "", - Applicability::MachineApplicable, - ) - .emit(); + let num_extra_brackets = number_of_gt + number_of_shr * 2; + self.sess.emit_err(UnmatchedAngleBrackets { span, num_extra_brackets }); return true; } false @@ -993,19 +883,13 @@ impl<'a> Parser<'a> { let args = AngleBracketedArgs { args, span }.into(); segment.args = args; - self.struct_span_err( + self.sess.emit_err(GenericParamsWithoutAngleBrackets { span, - "generic parameters without surrounding angle brackets", - ) - .multipart_suggestion( - "surround the type parameters with angle brackets", - vec![ - (span.shrink_to_lo(), "<".to_string()), - (trailing_span, ">".to_string()), - ], - Applicability::MachineApplicable, - ) - .emit(); + 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); @@ -1042,7 +926,7 @@ impl<'a> Parser<'a> { if self.eat(&token::Gt) { e.span_suggestion_verbose( binop.span.shrink_to_lo(), - TURBOFISH_SUGGESTION_STR, + fluent::parser_sugg_turbofish_syntax, "::", Applicability::MaybeIncorrect, ) @@ -1074,7 +958,7 @@ impl<'a> Parser<'a> { /// parenthesising the leftmost comparison. fn attempt_chained_comparison_suggestion( &mut self, - err: &mut Diagnostic, + err: &mut ComparisonOperatorsCannotBeChained, inner_op: &Expr, outer_op: &Spanned<AssocOp>, ) -> bool /* advanced the cursor */ { @@ -1087,16 +971,6 @@ impl<'a> Parser<'a> { // suggestion being the only one to apply is high. return false; } - let mut enclose = |left: Span, right: Span| { - err.multipart_suggestion( - "parenthesize the comparison", - vec![ - (left.shrink_to_lo(), "(".to_string()), - (right.shrink_to_hi(), ")".to_string()), - ], - Applicability::MaybeIncorrect, - ); - }; return match (op.node, &outer_op.node) { // `x == y == z` (BinOpKind::Eq, AssocOp::Equal) | @@ -1110,12 +984,10 @@ impl<'a> Parser<'a> { self.span_to_snippet(e.span) .unwrap_or_else(|_| pprust::expr_to_string(&e)) }; - err.span_suggestion_verbose( - inner_op.span.shrink_to_hi(), - "split the comparison into two", - format!(" && {}", expr_to_str(&r1)), - Applicability::MaybeIncorrect, - ); + 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` @@ -1126,7 +998,10 @@ impl<'a> Parser<'a> { Ok(r2) => { // We are sure that outer-op-rhs could be consumed, the suggestion is // likely correct. - enclose(r1.span, r2.span); + err.chaining_sugg = Some(ComparisonOperatorsCannotBeChainedSugg::Parenthesize { + left: r1.span.shrink_to_lo(), + right: r2.span.shrink_to_hi(), + }); true } Err(expr_err) => { @@ -1143,7 +1018,10 @@ impl<'a> Parser<'a> { // further checks are necessary. match self.parse_expr() { Ok(_) => { - enclose(l1.span, r1.span); + err.chaining_sugg = Some(ComparisonOperatorsCannotBeChainedSugg::Parenthesize { + left: l1.span.shrink_to_lo(), + right: r1.span.shrink_to_hi(), + }); true } Err(expr_err) => { @@ -1188,23 +1066,15 @@ impl<'a> Parser<'a> { outer_op.node, ); - let mk_err_expr = - |this: &Self, span| Ok(Some(this.mk_expr(span, ExprKind::Err, AttrVec::new()))); + let mk_err_expr = |this: &Self, span| Ok(Some(this.mk_expr(span, ExprKind::Err))); match inner_op.kind { ExprKind::Binary(op, ref l1, ref r1) if op.node.is_comparison() => { - let mut err = self.struct_span_err( - vec![op.span, self.prev_token.span], - "comparison operators cannot be chained", - ); - - let suggest = |err: &mut Diagnostic| { - err.span_suggestion_verbose( - op.span.shrink_to_lo(), - TURBOFISH_SUGGESTION_STR, - "::", - Applicability::MaybeIncorrect, - ); + let mut err = ComparisonOperatorsCannotBeChained { + span: vec![op.span, self.prev_token.span], + suggest_turbofish: None, + help_turbofish: None, + chaining_sugg: None, }; // Include `<` to provide this recommendation even in a case like @@ -1231,7 +1101,7 @@ impl<'a> Parser<'a> { return if token::ModSep == self.token.kind { // We have some certainty that this was a bad turbofish at this point. // `foo< bar >::` - suggest(&mut err); + err.suggest_turbofish = Some(op.span.shrink_to_lo()); let snapshot = self.create_snapshot_for_diagnostic(); self.bump(); // `::` @@ -1240,7 +1110,7 @@ impl<'a> Parser<'a> { match self.parse_expr() { Ok(_) => { // 99% certain that the suggestion is correct, continue parsing. - err.emit(); + self.sess.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. @@ -1251,18 +1121,18 @@ impl<'a> Parser<'a> { // Not entirely sure now, but we bubble the error up with the // suggestion. self.restore_snapshot(snapshot); - Err(err) + Err(err.into_diagnostic(&self.sess.span_diagnostic)) } } } else if token::OpenDelim(Delimiter::Parenthesis) == self.token.kind { // We have high certainty that this was a bad turbofish at this point. // `foo< bar >(` - suggest(&mut err); + err.suggest_turbofish = Some(op.span.shrink_to_lo()); // Consume the fn call arguments. match self.consume_fn_args() { - Err(()) => Err(err), + Err(()) => Err(err.into_diagnostic(&self.sess.span_diagnostic)), Ok(()) => { - err.emit(); + self.sess.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. @@ -1275,25 +1145,24 @@ impl<'a> Parser<'a> { { // 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_SUGGESTION_STR); - err.help("or use `(...)` if you meant to specify fn arguments"); + err.help_turbofish = Some(()); } // If it looks like a genuine attempt to chain operators (as opposed to a // misformatted turbofish, for instance), suggest a correct form. if self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op) { - err.emit(); + self.sess.emit_err(err); mk_err_expr(self, inner_op.span.to(self.prev_token.span)) } else { // These cases cause too many knock-down errors, bail out (#61329). - Err(err) + Err(err.into_diagnostic(&self.sess.span_diagnostic)) } }; } let recover = self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op); - err.emit(); + self.sess.emit_err(err); if recover { return mk_err_expr(self, inner_op.span.to(self.prev_token.span)); } @@ -1334,17 +1203,13 @@ impl<'a> Parser<'a> { pub(super) fn maybe_recover_from_question_mark(&mut self, ty: P<Ty>) -> P<Ty> { if self.token == token::Question { self.bump(); - self.struct_span_err(self.prev_token.span, "invalid `?` in type") - .span_label(self.prev_token.span, "`?` is only allowed on expressions, not types") - .multipart_suggestion( - "if you meant to express that the type might not contain a value, use the `Option` wrapper type", - vec![ - (ty.span.shrink_to_lo(), "Option<".to_string()), - (self.prev_token.span, ">".to_string()), - ], - Applicability::MachineApplicable, - ) - .emit(); + self.sess.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) } else { ty @@ -1497,7 +1362,7 @@ impl<'a> Parser<'a> { MultiSugg { msg: format!("use `{}= 1` instead", kind.op.chr()), patches: vec![ - (pre_span, format!("{{ let {} = ", tmp_var)), + (pre_span, format!("{{ let {tmp_var} = ")), (post_span, format!("; {} {}= 1; {} }}", base_src, kind.op.chr(), tmp_var)), ], applicability: Applicability::HasPlaceholders, @@ -1509,9 +1374,17 @@ impl<'a> Parser<'a> { 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: vec![(pre_span, String::new()), (post_span, format!(" {}= 1", kind.op.chr()))], + patches, applicability: Applicability::MachineApplicable, } } @@ -1596,7 +1469,7 @@ impl<'a> Parser<'a> { 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.sess.source_map().next_point(self.prev_token.span); + let sp = self.prev_token.span.shrink_to_hi(); (sp, sp) } // We don't want to point at the following span after DUMMY_SP. @@ -1647,7 +1520,6 @@ impl<'a> Parser<'a> { &mut self, lo: Span, await_sp: Span, - attrs: AttrVec, ) -> PResult<'a, P<Expr>> { let (hi, expr, is_question) = if self.token == token::Not { // Handle `await!(<expr>)`. @@ -1662,7 +1534,7 @@ impl<'a> Parser<'a> { ExprKind::Try(_) => ExprKind::Err, _ => ExprKind::Await(expr), }; - let expr = self.mk_expr(lo.to(sp), kind, attrs); + let expr = self.mk_expr(lo.to(sp), kind); self.maybe_recover_from_bad_qpath(expr) } @@ -1680,7 +1552,7 @@ impl<'a> Parser<'a> { // Handle `await { <expr> }`. // This needs to be handled separately from the next arm to avoid // interpreting `await { <expr> }?` as `<expr>?.await`. - self.parse_block_expr(None, self.token.span, BlockCheckMode::Default, AttrVec::new()) + self.parse_block_expr(None, self.token.span, BlockCheckMode::Default) } else { self.parse_expr() } @@ -1769,19 +1641,16 @@ impl<'a> Parser<'a> { (token::CloseDelim(Delimiter::Parenthesis), Some(begin_par_sp)) => { self.bump(); - self.struct_span_err( - MultiSpan::from_spans(vec![begin_par_sp, self.prev_token.span]), - "unexpected parentheses surrounding `for` loop head", - ) - .multipart_suggestion( - "remove parentheses in `for` loop", - vec![(begin_par_sp, String::new()), (self.prev_token.span, String::new())], + self.sess.emit_err(ParenthesesInForHead { + span: vec![begin_par_sp, self.prev_token.span], // With e.g. `for (x) in y)` this would replace `(x) in y)` // with `x) in y)` which is syntactically invalid. // However, this is prevented before we get here. - Applicability::MachineApplicable, - ) - .emit(); + sugg: ParenthesesInForHeadSugg { + left: begin_par_sp, + right: self.prev_token.span, + }, + }); // Unwrap `(pat)` into `pat` to avoid the `unused_parens` lint. pat.and_then(|pat| match pat.kind { @@ -1823,7 +1692,7 @@ impl<'a> Parser<'a> { err.emit(); // Recover from parse error, callers expect the closing delim to be consumed. self.consume_block(delim, ConsumeClosingDelim::Yes); - self.mk_expr(lo.to(self.prev_token.span), ExprKind::Err, AttrVec::new()) + self.mk_expr(lo.to(self.prev_token.span), ExprKind::Err) } } } @@ -2000,12 +1869,7 @@ impl<'a> Parser<'a> { pub(super) fn eat_incorrect_doc_comment_for_param_type(&mut self) { if let token::DocComment(..) = self.token.kind { - self.struct_span_err( - self.token.span, - "documentation comments cannot be applied to a function parameter's type", - ) - .span_label(self.token.span, "doc comments are not allowed here") - .emit(); + self.sess.emit_err(DocCommentOnParamType { span: self.token.span }); self.bump(); } else if self.token == token::Pound && self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Bracket)) @@ -2017,9 +1881,7 @@ impl<'a> Parser<'a> { } let sp = lo.to(self.token.span); self.bump(); - self.struct_span_err(sp, "attributes cannot be applied to a function parameter's type") - .span_label(sp, "attributes are not allowed here") - .emit(); + self.sess.emit_err(AttributeOnParamType { span: sp }); } } @@ -2140,19 +2002,7 @@ impl<'a> Parser<'a> { self.expect(&token::Colon)?; let ty = self.parse_ty()?; - struct_span_err!( - self.diagnostic(), - pat.span, - E0642, - "patterns aren't allowed in methods without bodies", - ) - .span_suggestion_short( - pat.span, - "give this argument a name or use an underscore to ignore it", - "_", - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(PatternMethodParamWithoutBody { span: pat.span }); // Pretend the pattern is `_`, to avoid duplicate errors from AST validation. let pat = @@ -2161,11 +2011,9 @@ impl<'a> Parser<'a> { } pub(super) fn recover_bad_self_param(&mut self, mut param: Param) -> PResult<'a, Param> { - let sp = param.pat.span; + let span = param.pat.span; param.ty.kind = TyKind::Err; - self.struct_span_err(sp, "unexpected `self` parameter in function") - .span_label(sp, "must be the first parameter of an associated function") - .emit(); + self.sess.emit_err(SelfParamNotFirst { span }); Ok(param) } @@ -2199,7 +2047,7 @@ impl<'a> Parser<'a> { pub(super) fn expected_expression_found(&self) -> DiagnosticBuilder<'a, ErrorGuaranteed> { let (span, msg) = match (&self.token.kind, self.subparser_name) { (&token::Eof, Some(origin)) => { - let sp = self.sess.source_map().next_point(self.prev_token.span); + let sp = self.prev_token.span.shrink_to_hi(); (sp, format!("expected expression, found end of {origin}")) } _ => ( @@ -2210,7 +2058,7 @@ impl<'a> Parser<'a> { let mut err = self.struct_span_err(span, &msg); let sp = self.sess.source_map().start_point(self.token.span); if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&sp) { - self.sess.expr_parentheses_needed(&mut err, *sp); + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); } err.span_label(span, "expected expression"); err @@ -2314,27 +2162,20 @@ impl<'a> Parser<'a> { err })?; if !self.expr_is_valid_const_arg(&expr) { - self.struct_span_err( - expr.span, - "expressions must be enclosed in braces to be used as const generic \ - arguments", - ) - .multipart_suggestion( - "enclose the `const` expression in braces", - vec![ - (expr.span.shrink_to_lo(), "{ ".to_string()), - (expr.span.shrink_to_hi(), " }".to_string()), - ], - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(ConstGenericWithoutBraces { + span: expr.span, + sugg: ConstGenericWithoutBracesSugg { + left: expr.span.shrink_to_lo(), + right: expr.span.shrink_to_hi(), + }, + }); } Ok(expr) } 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(vec![]) { + let param = match self.parse_const_param(AttrVec::new()) { Ok(param) => param, Err(err) => { err.cancel(); @@ -2342,24 +2183,30 @@ impl<'a> Parser<'a> { return None; } }; - let mut err = - self.struct_span_err(param.span(), "unexpected `const` parameter declaration"); - err.span_label(param.span(), "expected a `const` expression, not a parameter declaration"); - if let (Some(generics), Ok(snippet)) = - (ty_generics, self.sess.source_map().span_to_snippet(param.span())) - { - let (span, sugg) = match &generics.params[..] { - [] => (generics.span, format!("<{snippet}>")), - [.., generic] => (generic.span().shrink_to_hi(), format!(", {snippet}")), - }; - err.multipart_suggestion( - "`const` parameters must be declared for the `impl`", - vec![(span, sugg), (param.span(), param.ident.to_string())], - Applicability::MachineApplicable, - ); - } + + let ident = param.ident.to_string(); + let sugg = match (ty_generics, self.sess.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, + }; + self.sess.emit_err(UnexpectedConstParamDeclaration { span: param.span(), sugg }); + let value = self.mk_expr_err(param.span()); - err.emit(); Some(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })) } @@ -2377,20 +2224,15 @@ impl<'a> Parser<'a> { self.bump(); // `const` // Detect and recover from the old, pre-RFC2000 syntax for const generics. - let mut err = self - .struct_span_err(start, "expected lifetime, type, or constant, found keyword `const`"); + let mut err = UnexpectedConstInGenericParam { span: start, to_remove: None }; if self.check_const_arg() { - err.span_suggestion_verbose( - start.until(self.token.span), - "the `const` keyword is only needed in the definition of the type", - "", - Applicability::MaybeIncorrect, - ); - err.emit(); + err.to_remove = Some(start.until(self.token.span)); + self.sess.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, err).map(Some) + self.recover_const_arg(after_kw_const, err.into_diagnostic(&self.sess.span_diagnostic)) + .map(Some) } } @@ -2398,7 +2240,7 @@ impl<'a> Parser<'a> { /// /// 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 that the resulting expression would be well formed. + /// if we think that the resulting expression would be well formed. pub fn recover_const_arg( &mut self, start: Span, @@ -2496,24 +2338,6 @@ impl<'a> Parser<'a> { GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }) } - /// Get the diagnostics for the cases where `move async` is found. - /// - /// `move_async_span` starts at the 'm' of the move keyword and ends with the 'c' of the async keyword - pub(super) fn incorrect_move_async_order_found( - &self, - move_async_span: Span, - ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { - let mut err = - self.struct_span_err(move_async_span, "the order of `move` and `async` is incorrect"); - err.span_suggestion_verbose( - move_async_span, - "try switching the order", - "async move", - Applicability::MaybeIncorrect, - ); - err - } - /// 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_colon_colon_in_pat_typo( @@ -2577,7 +2401,7 @@ impl<'a> Parser<'a> { } _ => {} }, - PatKind::Ident(BindingMode::ByValue(Mutability::Not), ident, None) => { + PatKind::Ident(BindingAnnotation::NONE, ident, None) => { match &first_pat.kind { PatKind::Ident(_, old_ident, _) => { let path = PatKind::Path( @@ -2632,11 +2456,15 @@ impl<'a> Parser<'a> { } pub(crate) fn maybe_recover_unexpected_block_label(&mut self) -> bool { - let Some(label) = self.eat_label().filter(|_| { - self.eat(&token::Colon) && self.token.kind == token::OpenDelim(Delimiter::Brace) - }) else { + // Check for `'a : {` + if !(self.check_lifetime() + && self.look_ahead(1, |tok| tok.kind == token::Colon) + && self.look_ahead(2, |tok| tok.kind == token::OpenDelim(Delimiter::Brace))) + { 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 err = self.struct_span_err(span, "block label not supported here"); err.span_label(span, "not supported here"); @@ -2709,17 +2537,11 @@ impl<'a> Parser<'a> { 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_ref().map(|a| &a[..]) == Ok(":: ") { - let mut err = self.struct_span_err( - path.span.shrink_to_hi(), - "expected `:` followed by trait or lifetime", - ); - err.span_suggestion( - between_span, - "use single colon", - ": ", - Applicability::MachineApplicable, - ); - return Err(err); + return Err(DoubleColonInBound { + span: path.span.shrink_to_hi(), + between: between_span, + } + .into_diagnostic(&self.sess.span_diagnostic)); } } } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 0719a0ef0..a781748ef 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -5,6 +5,28 @@ use super::{ AttrWrapper, BlockMode, ClosureSpans, ForceCollect, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenExpectType, TokenType, TrailingToken, }; +use crate::errors::{ + ArrayBracketsInsteadOfSpaces, ArrayBracketsInsteadOfSpacesSugg, AsyncMoveOrderIncorrect, + BinaryFloatLiteralNotSupported, BracesForStructLiteral, CatchAfterTry, CommaAfterBaseStruct, + ComparisonInterpretedAsGeneric, ComparisonOrShiftInterpretedAsGenericSugg, + DoCatchSyntaxRemoved, DotDotDot, EqFieldInit, ExpectedElseBlock, ExpectedExpressionFoundLet, + FieldExpressionWithGeneric, FloatLiteralRequiresIntegerPart, FoundExprWouldBeStmt, + HexadecimalFloatLiteralNotSupported, IfExpressionMissingCondition, + IfExpressionMissingThenBlock, IfExpressionMissingThenBlockSub, IntLiteralTooLarge, + InvalidBlockMacroSegment, InvalidComparisonOperator, InvalidComparisonOperatorSub, + InvalidFloatLiteralSuffix, InvalidFloatLiteralWidth, InvalidIntLiteralWidth, + InvalidInterpolatedExpression, InvalidLiteralSuffix, InvalidLiteralSuffixOnTupleIndex, + InvalidLogicalOperator, InvalidLogicalOperatorSub, InvalidNumLiteralBasePrefix, + InvalidNumLiteralSuffix, LabeledLoopInBreak, LeadingPlusNotSupported, LeftArrowOperator, + LifetimeInBorrowExpression, MacroInvocationWithQualifiedPath, MalformedLoopLabel, + MatchArmBodyWithoutBraces, MatchArmBodyWithoutBracesSugg, MissingCommaAfterMatchArm, + MissingInInForLoop, MissingInInForLoopSub, MissingSemicolonBeforeArray, NoFieldsForFnCall, + NotAsNegationOperator, NotAsNegationOperatorSub, OctalFloatLiteralNotSupported, + OuterAttributeNotAllowedOnIfElse, ParenthesesWithStructFields, + RequireColonAfterLabeledExpression, ShiftInterpretedAsGeneric, StructLiteralNotAllowedHere, + StructLiteralNotAllowedHereSugg, TildeAsUnaryOperator, UnexpectedTokenAfterLabel, + UnexpectedTokenAfterLabelSugg, WrapExpressionInParentheses, +}; use crate::maybe_recover_from_interpolated_ty_qpath; use core::mem; @@ -20,8 +42,11 @@ use rustc_ast::{AnonConst, BinOp, BinOpKind, FnDecl, FnRetTy, MacCall, Param, Ty use rustc_ast::{Arm, Async, BlockCheckMode, Expr, ExprKind, Label, Movability, RangeLimits}; use rustc_ast::{ClosureBinder, StmtKind}; use rustc_ast_pretty::pprust; -use rustc_data_structures::thin_vec::ThinVec; -use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, PResult}; +use rustc_errors::{ + Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult, + StashKey, +}; +use rustc_session::errors::ExprParenthesesNeeded; use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP; use rustc_session::lint::BuiltinLintDiagnostics; use rustc_span::source_map::{self, Span, Spanned}; @@ -45,20 +70,12 @@ macro_rules! maybe_whole_expr { token::NtPath(path) => { let path = (**path).clone(); $p.bump(); - return Ok($p.mk_expr( - $p.prev_token.span, - ExprKind::Path(None, path), - AttrVec::new(), - )); + return Ok($p.mk_expr($p.prev_token.span, ExprKind::Path(None, path))); } token::NtBlock(block) => { let block = block.clone(); $p.bump(); - return Ok($p.mk_expr( - $p.prev_token.span, - ExprKind::Block(block, None), - AttrVec::new(), - )); + return Ok($p.mk_expr($p.prev_token.span, ExprKind::Block(block, None))); } _ => {} }; @@ -120,7 +137,7 @@ impl<'a> Parser<'a> { // Special-case handling of `foo(_, _, _)` err.emit(); self.bump(); - Ok(self.mk_expr(self.prev_token.span, ExprKind::Err, AttrVec::new())) + Ok(self.mk_expr(self.prev_token.span, ExprKind::Err)) } _ => Err(err), }, @@ -225,15 +242,18 @@ impl<'a> Parser<'a> { AssocOp::Equal => "==", AssocOp::NotEqual => "!=", _ => unreachable!(), - }; - self.struct_span_err(sp, &format!("invalid comparison operator `{sugg}=`")) - .span_suggestion_short( - sp, - &format!("`{s}=` is not a valid comparison operator, use `{s}`", s = sugg), - sugg, - Applicability::MachineApplicable, - ) - .emit(); + } + .into(); + let invalid = format!("{}=", &sugg); + self.sess.emit_err(InvalidComparisonOperator { + span: sp, + invalid: invalid.clone(), + sub: InvalidComparisonOperatorSub::Correctable { + span: sp, + invalid, + correct: sugg, + }, + }); self.bump(); } @@ -243,14 +263,15 @@ impl<'a> Parser<'a> { && self.prev_token.span.hi() == self.token.span.lo() { let sp = op.span.to(self.token.span); - self.struct_span_err(sp, "invalid comparison operator `<>`") - .span_suggestion_short( - sp, - "`<>` is not a valid comparison operator, use `!=`", - "!=", - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(InvalidComparisonOperator { + span: sp, + invalid: "<>".into(), + sub: InvalidComparisonOperatorSub::Correctable { + span: sp, + invalid: "<>".into(), + correct: "!=".into(), + }, + }); self.bump(); } @@ -260,12 +281,11 @@ impl<'a> Parser<'a> { && self.prev_token.span.hi() == self.token.span.lo() { let sp = op.span.to(self.token.span); - self.struct_span_err(sp, "invalid comparison operator `<=>`") - .span_label( - sp, - "`<=>` is not a valid comparison operator, use `std::cmp::Ordering`", - ) - .emit(); + self.sess.emit_err(InvalidComparisonOperator { + span: sp, + invalid: "<=>".into(), + sub: InvalidComparisonOperatorSub::Spaceship(sp), + }); self.bump(); } @@ -329,11 +349,9 @@ impl<'a> Parser<'a> { | AssocOp::GreaterEqual => { let ast_op = op.to_ast_binop().unwrap(); let binary = self.mk_binary(source_map::respan(cur_op_span, ast_op), lhs, rhs); - self.mk_expr(span, binary, AttrVec::new()) - } - AssocOp::Assign => { - self.mk_expr(span, ExprKind::Assign(lhs, rhs, cur_op_span), AttrVec::new()) + self.mk_expr(span, binary) } + AssocOp::Assign => self.mk_expr(span, ExprKind::Assign(lhs, rhs, cur_op_span)), AssocOp::AssignOp(k) => { let aop = match k { token::Plus => BinOpKind::Add, @@ -348,7 +366,7 @@ impl<'a> Parser<'a> { token::Shr => BinOpKind::Shr, }; let aopexpr = self.mk_assign_op(source_map::respan(cur_op_span, aop), lhs, rhs); - self.mk_expr(span, aopexpr, AttrVec::new()) + self.mk_expr(span, aopexpr) } AssocOp::As | AssocOp::Colon | AssocOp::DotDot | AssocOp::DotDotEq => { self.span_bug(span, "AssocOp should have been handled by special case") @@ -412,13 +430,11 @@ impl<'a> Parser<'a> { /// but the next token implies this should be parsed as an expression. /// For example: `if let Some(x) = x { x } else { 0 } / 2`. fn error_found_expr_would_be_stmt(&self, lhs: &Expr) { - let mut err = self.struct_span_err( - self.token.span, - &format!("expected expression, found `{}`", pprust::token_to_string(&self.token),), - ); - err.span_label(self.token.span, "expected expression"); - self.sess.expr_parentheses_needed(&mut err, lhs.span); - err.emit(); + self.sess.emit_err(FoundExprWouldBeStmt { + span: self.token.span, + token: self.token.clone(), + suggestion: ExprParenthesesNeeded::surrounding(lhs.span), + }); } /// Possibly translate the current token to an associative operator. @@ -441,11 +457,19 @@ impl<'a> Parser<'a> { } (Some(op), _) => (op, self.token.span), (None, Some((Ident { name: sym::and, span }, false))) => { - self.error_bad_logical_op("and", "&&", "conjunction"); + self.sess.emit_err(InvalidLogicalOperator { + span: self.token.span, + incorrect: "and".into(), + sub: InvalidLogicalOperatorSub::Conjunction(self.token.span), + }); (AssocOp::LAnd, span) } (None, Some((Ident { name: sym::or, span }, false))) => { - self.error_bad_logical_op("or", "||", "disjunction"); + self.sess.emit_err(InvalidLogicalOperator { + span: self.token.span, + incorrect: "or".into(), + sub: InvalidLogicalOperatorSub::Disjunction(self.token.span), + }); (AssocOp::LOr, span) } _ => return None, @@ -453,19 +477,6 @@ impl<'a> Parser<'a> { Some(source_map::respan(span, op)) } - /// Error on `and` and `or` suggesting `&&` and `||` respectively. - fn error_bad_logical_op(&self, bad: &str, good: &str, english: &str) { - self.struct_span_err(self.token.span, &format!("`{bad}` is not a logical operator")) - .span_suggestion_short( - self.token.span, - &format!("use `{good}` to perform logical {english}"), - good, - Applicability::MachineApplicable, - ) - .note("unlike in e.g., python and PHP, `&&` and `||` are used for logical operators") - .emit(); - } - /// Checks if this expression is a successfully parsed statement. fn expr_is_complete(&self, e: &Expr) -> bool { self.restrictions.contains(Restrictions::STMT_EXPR) @@ -491,7 +502,7 @@ impl<'a> Parser<'a> { let limits = if op == AssocOp::DotDot { RangeLimits::HalfOpen } else { RangeLimits::Closed }; let range = self.mk_range(Some(lhs), rhs, limits); - Ok(self.mk_expr(span, range, AttrVec::new())) + Ok(self.mk_expr(span, range)) } fn is_at_start_of_range_notation_rhs(&self) -> bool { @@ -540,7 +551,7 @@ impl<'a> Parser<'a> { (lo, None) }; let range = this.mk_range(None, opt_end, limits); - Ok(this.mk_expr(span, range, attrs.into())) + Ok(this.mk_expr_with_attrs(span, range, attrs)) }) } @@ -553,7 +564,7 @@ impl<'a> Parser<'a> { ($this:ident, $attrs:expr, |this, _| $body:expr) => { $this.collect_tokens_for_expr($attrs, |$this, attrs| { let (hi, ex) = $body?; - Ok($this.mk_expr(lo.to(hi), ex, attrs.into())) + Ok($this.mk_expr_with_attrs(lo.to(hi), ex, attrs)) }) }; } @@ -574,21 +585,16 @@ impl<'a> Parser<'a> { make_it!(this, attrs, |this, _| this.parse_borrow_expr(lo)) } token::BinOp(token::Plus) if this.look_ahead(1, |tok| tok.is_numeric_lit()) => { - let mut err = this.struct_span_err(lo, "leading `+` is not supported"); - err.span_label(lo, "unexpected `+`"); + let mut err = + LeadingPlusNotSupported { span: lo, remove_plus: None, add_parentheses: None }; // a block on the LHS might have been intended to be an expression instead if let Some(sp) = this.sess.ambiguous_block_expr_parse.borrow().get(&lo) { - this.sess.expr_parentheses_needed(&mut err, *sp); + err.add_parentheses = Some(ExprParenthesesNeeded::surrounding(*sp)); } else { - err.span_suggestion_verbose( - lo, - "try removing the `+`", - "", - Applicability::MachineApplicable, - ); + err.remove_plus = Some(lo); } - err.emit(); + this.sess.emit_err(err); this.bump(); this.parse_prefix_expr(None) @@ -630,14 +636,7 @@ impl<'a> Parser<'a> { // Recover on `!` suggesting for bitwise negation instead. fn recover_tilde_expr(&mut self, lo: Span) -> PResult<'a, (Span, ExprKind)> { - self.struct_span_err(lo, "`~` cannot be used as a unary operator") - .span_suggestion_short( - lo, - "use `!` to perform bitwise not", - "!", - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(TildeAsUnaryOperator(lo)); self.parse_unary_expr(lo, UnOp::Not) } @@ -663,20 +662,25 @@ impl<'a> Parser<'a> { /// Recover on `not expr` in favor of `!expr`. fn recover_not_expr(&mut self, lo: Span) -> PResult<'a, (Span, ExprKind)> { // Emit the error... - let not_token = self.look_ahead(1, |t| t.clone()); - self.struct_span_err( - not_token.span, - &format!("unexpected {} after identifier", super::token_descr(¬_token)), - ) - .span_suggestion_short( + let negated_token = self.look_ahead(1, |t| t.clone()); + + let sub_diag = if negated_token.is_numeric_lit() { + NotAsNegationOperatorSub::SuggestNotBitwise + } else if negated_token.is_bool_lit() { + NotAsNegationOperatorSub::SuggestNotLogical + } else { + NotAsNegationOperatorSub::SuggestNotDefault + }; + + self.sess.emit_err(NotAsNegationOperator { + negated: negated_token.span, + negated_desc: super::token_descr(&negated_token), // Span the `not` plus trailing whitespace to avoid // trailing whitespace after the `!` in our suggestion - self.sess.source_map().span_until_non_whitespace(lo.to(not_token.span)), - "use `!` to perform logical negation", - "!", - Applicability::MachineApplicable, - ) - .emit(); + sub: sub_diag( + self.sess.source_map().span_until_non_whitespace(lo.to(negated_token.span)), + ), + }); // ...and recover! self.parse_unary_expr(lo, UnOp::Not) @@ -705,11 +709,7 @@ impl<'a> Parser<'a> { expr_kind: fn(P<Expr>, P<Ty>) -> ExprKind, ) -> PResult<'a, P<Expr>> { let mk_expr = |this: &mut Self, lhs: P<Expr>, rhs: P<Ty>| { - this.mk_expr( - this.mk_expr_sp(&lhs, lhs_span, rhs.span), - expr_kind(lhs, rhs), - AttrVec::new(), - ) + this.mk_expr(this.mk_expr_sp(&lhs, lhs_span, rhs.span), expr_kind(lhs, rhs)) }; // Save the state of the parser before parsing type normally, in case there is a @@ -737,17 +737,13 @@ impl<'a> Parser<'a> { segments[0].ident.span, ), }; - match self.parse_labeled_expr(label, AttrVec::new(), false) { + match self.parse_labeled_expr(label, false) { Ok(expr) => { type_err.cancel(); - self.struct_span_err(label.ident.span, "malformed loop label") - .span_suggestion( - label.ident.span, - "use the correct loop label format", - label.ident, - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(MalformedLoopLabel { + span: label.ident.span, + correct_label: label.ident, + }); return Ok(expr); } Err(err) => { @@ -761,9 +757,34 @@ impl<'a> Parser<'a> { match self.parse_path(PathStyle::Expr) { Ok(path) => { - let (op_noun, op_verb) = match self.token.kind { - token::Lt => ("comparison", "comparing"), - token::BinOp(token::Shl) => ("shift", "shifting"), + let span_after_type = parser_snapshot_after_type.token.span; + let expr = mk_expr( + self, + lhs, + self.mk_ty(path.span, TyKind::Path(None, path.clone())), + ); + + let args_span = self.look_ahead(1, |t| t.span).to(span_after_type); + let suggestion = ComparisonOrShiftInterpretedAsGenericSugg { + left: expr.span.shrink_to_lo(), + right: expr.span.shrink_to_hi(), + }; + + match self.token.kind { + token::Lt => self.sess.emit_err(ComparisonInterpretedAsGeneric { + comparison: self.token.span, + r#type: path, + args: args_span, + suggestion, + }), + token::BinOp(token::Shl) => { + self.sess.emit_err(ShiftInterpretedAsGeneric { + shift: self.token.span, + r#type: path, + args: args_span, + suggestion, + }) + } _ => { // We can end up here even without `<` being the next token, for // example because `parse_ty_no_plus` returns `Err` on keywords, @@ -777,33 +798,7 @@ impl<'a> Parser<'a> { // Successfully parsed the type path leaving a `<` yet to parse. type_err.cancel(); - // Report non-fatal diagnostics, keep `x as usize` as an expression - // in AST and continue parsing. - let msg = format!( - "`<` is interpreted as a start of generic arguments for `{}`, not a {}", - pprust::path_to_string(&path), - op_noun, - ); - let span_after_type = parser_snapshot_after_type.token.span; - let expr = - mk_expr(self, lhs, self.mk_ty(path.span, TyKind::Path(None, path))); - - self.struct_span_err(self.token.span, &msg) - .span_label( - self.look_ahead(1, |t| t.span).to(span_after_type), - "interpreted as generic arguments", - ) - .span_label(self.token.span, format!("not interpreted as {op_noun}")) - .multipart_suggestion( - &format!("try {op_verb} the cast value"), - vec![ - (expr.span.shrink_to_lo(), "(".to_string()), - (expr.span.shrink_to_hi(), ")".to_string()), - ], - Applicability::MachineApplicable, - ) - .emit(); - + // Keep `x as usize` as an expression in AST and continue parsing. expr } Err(path_err) => { @@ -850,7 +845,7 @@ impl<'a> Parser<'a> { ExprKind::Index(_, _) => "indexing", ExprKind::Try(_) => "`?`", ExprKind::Field(_, _) => "a field access", - ExprKind::MethodCall(_, _, _) => "a method call", + ExprKind::MethodCall(_, _, _, _) => "a method call", ExprKind::Call(_, _) => "a function call", ExprKind::Await(_) => "`.await`", ExprKind::Err => return Ok(with_postfix), @@ -859,7 +854,7 @@ impl<'a> Parser<'a> { ); let mut err = self.struct_span_err(span, &msg); - let suggest_parens = |err: &mut DiagnosticBuilder<'_, _>| { + let suggest_parens = |err: &mut Diagnostic| { let suggestions = vec![ (span.shrink_to_lo(), "(".to_string()), (span.shrink_to_hi(), ")".to_string()), @@ -925,15 +920,7 @@ impl<'a> Parser<'a> { } fn error_remove_borrow_lifetime(&self, span: Span, lt_span: Span) { - self.struct_span_err(span, "borrow expressions cannot be annotated with lifetimes") - .span_label(lt_span, "annotated with lifetime here") - .span_suggestion( - lt_span, - "remove the lifetime annotation", - "", - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(LifetimeInBorrowExpression { span, lifetime_span: lt_span }); } /// Parse `mut?` or `raw [ const | mut ]`. @@ -965,18 +952,23 @@ impl<'a> Parser<'a> { &mut self, e0: P<Expr>, lo: Span, - mut attrs: Vec<ast::Attribute>, + mut attrs: ast::AttrVec, ) -> PResult<'a, P<Expr>> { // Stitch the list of outer attributes onto the return value. // A little bit ugly, but the best way given the current code // structure - self.parse_dot_or_call_expr_with_(e0, lo).map(|expr| { - expr.map(|mut expr| { - attrs.extend::<Vec<_>>(expr.attrs.into()); - expr.attrs = attrs.into(); - expr + let res = self.parse_dot_or_call_expr_with_(e0, lo); + if attrs.is_empty() { + res + } else { + res.map(|expr| { + expr.map(|mut expr| { + attrs.extend(expr.attrs); + expr.attrs = attrs; + expr + }) }) - }) + } } fn parse_dot_or_call_expr_with_(&mut self, mut e: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> { @@ -990,7 +982,7 @@ impl<'a> Parser<'a> { }; if has_question { // `expr?` - e = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Try(e), AttrVec::new()); + e = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Try(e)); continue; } let has_dot = if self.prev_token.kind == TokenKind::Ident(kw::Return, false) { @@ -1167,8 +1159,10 @@ impl<'a> Parser<'a> { } let span = self.prev_token.span; let field = ExprKind::Field(base, Ident::new(field, span)); - self.expect_no_suffix(span, "a tuple index", suffix); - self.mk_expr(lo.to(span), field, AttrVec::new()) + if let Some(suffix) = suffix { + self.expect_no_tuple_index_suffix(span, suffix); + } + self.mk_expr(lo.to(span), field) } /// Parse a function call expression, `expr(...)`. @@ -1182,9 +1176,9 @@ impl<'a> Parser<'a> { }; let open_paren = self.token.span; - let mut seq = self.parse_paren_expr_seq().map(|args| { - self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args), AttrVec::new()) - }); + let mut seq = self + .parse_paren_expr_seq() + .map(|args| self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args))); if let Some(expr) = self.maybe_recover_struct_lit_bad_delims(lo, open_paren, &mut seq, snapshot) { @@ -1205,9 +1199,8 @@ impl<'a> Parser<'a> { ) -> Option<P<Expr>> { match (seq.as_mut(), snapshot) { (Err(err), Some((mut snapshot, ExprKind::Path(None, path)))) => { - let name = pprust::path_to_string(&path); snapshot.bump(); // `(` - match snapshot.parse_struct_fields(path, false, Delimiter::Parenthesis) { + match snapshot.parse_struct_fields(path.clone(), false, Delimiter::Parenthesis) { Ok((fields, ..)) if snapshot.eat(&token::CloseDelim(Delimiter::Parenthesis)) => { @@ -1217,29 +1210,25 @@ impl<'a> Parser<'a> { let close_paren = self.prev_token.span; let span = lo.to(self.prev_token.span); if !fields.is_empty() { - let replacement_err = self.struct_span_err( + let mut replacement_err = ParenthesesWithStructFields { span, - "invalid `struct` delimiters or `fn` call arguments", - ); - mem::replace(err, replacement_err).cancel(); - - err.multipart_suggestion( - &format!("if `{name}` is a struct, use braces as delimiters"), - vec![ - (open_paren, " { ".to_string()), - (close_paren, " }".to_string()), - ], - Applicability::MaybeIncorrect, - ); - err.multipart_suggestion( - &format!("if `{name}` is a function, use the arguments directly"), - fields - .into_iter() - .map(|field| (field.span.until(field.expr.span), String::new())) - .collect(), - Applicability::MaybeIncorrect, - ); - err.emit(); + r#type: path, + braces_for_struct: BracesForStructLiteral { + first: open_paren, + second: close_paren, + }, + no_fields_for_fn: NoFieldsForFnCall { + fields: fields + .into_iter() + .map(|field| field.span.until(field.expr.span)) + .collect(), + }, + } + .into_diagnostic(&self.sess.span_diagnostic); + replacement_err.emit(); + + let old_err = mem::replace(err, replacement_err); + old_err.cancel(); } else { err.emit(); } @@ -1258,10 +1247,13 @@ impl<'a> Parser<'a> { /// Parse an indexing expression `expr[...]`. fn parse_index_expr(&mut self, lo: Span, base: P<Expr>) -> PResult<'a, P<Expr>> { + let prev_span = self.prev_token.span; + let open_delim_span = self.token.span; self.bump(); // `[` let index = self.parse_expr()?; + self.suggest_missing_semicolon_before_array(prev_span, open_delim_span)?; self.expect(&token::CloseDelim(Delimiter::Bracket))?; - Ok(self.mk_expr(lo.to(self.prev_token.span), self.mk_index(base, index), AttrVec::new())) + Ok(self.mk_expr(lo.to(self.prev_token.span), self.mk_index(base, index))) } /// Assuming we have just parsed `.`, continue parsing into an expression. @@ -1277,24 +1269,18 @@ impl<'a> Parser<'a> { if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { // Method call `expr.f()` - let mut args = self.parse_paren_expr_seq()?; - args.insert(0, self_arg); - + let args = self.parse_paren_expr_seq()?; let fn_span = fn_span_lo.to(self.prev_token.span); let span = lo.to(self.prev_token.span); - Ok(self.mk_expr(span, ExprKind::MethodCall(segment, args, fn_span), AttrVec::new())) + Ok(self.mk_expr(span, ExprKind::MethodCall(segment, self_arg, args, fn_span))) } else { // Field access `expr.f` if let Some(args) = segment.args { - self.struct_span_err( - args.span(), - "field expressions cannot have generic arguments", - ) - .emit(); + self.sess.emit_err(FieldExpressionWithGeneric(args.span())); } let span = lo.to(self.prev_token.span); - Ok(self.mk_expr(span, ExprKind::Field(self_arg, segment.ident), AttrVec::new())) + Ok(self.mk_expr(span, ExprKind::Field(self_arg, segment.ident))) } } @@ -1309,10 +1295,6 @@ impl<'a> Parser<'a> { // Outer attributes are already parsed and will be // added to the return value after the fact. - // - // Therefore, prevent sub-parser from parsing - // attributes by giving them an empty "already-parsed" list. - let attrs = AttrVec::new(); // Note: when adding new syntax here, don't forget to adjust `TokenKind::can_begin_expr()`. let lo = self.token.span; @@ -1320,80 +1302,81 @@ impl<'a> Parser<'a> { // 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_lit_expr(attrs) + self.parse_lit_expr() } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { - self.parse_tuple_parens_expr(attrs) + self.parse_tuple_parens_expr() } else if self.check(&token::OpenDelim(Delimiter::Brace)) { - self.parse_block_expr(None, lo, BlockCheckMode::Default, attrs) + self.parse_block_expr(None, lo, BlockCheckMode::Default) } else if self.check(&token::BinOp(token::Or)) || self.check(&token::OrOr) { - self.parse_closure_expr(attrs).map_err(|mut err| { + self.parse_closure_expr().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) { - self.sess.expr_parentheses_needed(&mut err, *sp); + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); } err }) } else if self.check(&token::OpenDelim(Delimiter::Bracket)) { - self.parse_array_or_repeat_expr(attrs, Delimiter::Bracket) + self.parse_array_or_repeat_expr(Delimiter::Bracket) } else if self.check_path() { - self.parse_path_start_expr(attrs) + self.parse_path_start_expr() } else if self.check_keyword(kw::Move) || self.check_keyword(kw::Static) { - self.parse_closure_expr(attrs) + self.parse_closure_expr() } else if self.eat_keyword(kw::If) { - self.parse_if_expr(attrs) + self.parse_if_expr() } else if self.check_keyword(kw::For) { if self.choose_generics_over_qpath(1) { - self.parse_closure_expr(attrs) + self.parse_closure_expr() } else { assert!(self.eat_keyword(kw::For)); - self.parse_for_expr(None, self.prev_token.span, attrs) + self.parse_for_expr(None, self.prev_token.span) } } else if self.eat_keyword(kw::While) { - self.parse_while_expr(None, self.prev_token.span, attrs) + self.parse_while_expr(None, self.prev_token.span) } else if let Some(label) = self.eat_label() { - self.parse_labeled_expr(label, attrs, true) + self.parse_labeled_expr(label, true) } else if self.eat_keyword(kw::Loop) { let sp = self.prev_token.span; - self.parse_loop_expr(None, self.prev_token.span, attrs).map_err(|mut err| { + self.parse_loop_expr(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::Continue) { let kind = ExprKind::Continue(self.eat_label()); - Ok(self.mk_expr(lo.to(self.prev_token.span), kind, attrs)) + Ok(self.mk_expr(lo.to(self.prev_token.span), kind)) } else if self.eat_keyword(kw::Match) { let match_sp = self.prev_token.span; - self.parse_match_expr(attrs).map_err(|mut err| { + self.parse_match_expr().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_block_expr(None, lo, BlockCheckMode::Unsafe(ast::UserProvided), attrs) - .map_err(|mut err| { + self.parse_block_expr(None, lo, BlockCheckMode::Unsafe(ast::UserProvided)).map_err( + |mut err| { err.span_label(sp, "while parsing this `unsafe` expression"); err - }) + }, + ) } else if self.check_inline_const(0) { self.parse_const_block(lo.to(self.token.span), false) } else if self.is_do_catch_block() { - self.recover_do_catch(attrs) + self.recover_do_catch() } else if self.is_try_block() { self.expect_keyword(kw::Try)?; - self.parse_try_block(lo, attrs) + self.parse_try_block(lo) } else if self.eat_keyword(kw::Return) { - self.parse_return_expr(attrs) + self.parse_return_expr() } else if self.eat_keyword(kw::Break) { - self.parse_break_expr(attrs) + self.parse_break_expr() } else if self.eat_keyword(kw::Yield) { - self.parse_yield_expr(attrs) + self.parse_yield_expr() } else if self.is_do_yeet() { - self.parse_yeet_expr(attrs) + self.parse_yeet_expr() } else if self.check_keyword(kw::Let) { - self.parse_let_expr(attrs) + self.parse_let_expr() } else if self.eat_keyword(kw::Underscore) { - Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore, attrs)) + Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore)) } else if !self.unclosed_delims.is_empty() && self.check(&token::Semi) { // Don't complain about bare semicolons after unclosed braces // recovery in order to keep the error count down. Fixing the @@ -1412,32 +1395,32 @@ impl<'a> Parser<'a> { if self.check_keyword(kw::Async) { if self.is_async_block() { // Check for `async {` and `async move {`. - self.parse_async_block(attrs) + self.parse_async_block() } else { - self.parse_closure_expr(attrs) + self.parse_closure_expr() } } else if self.eat_keyword(kw::Await) { - self.recover_incorrect_await_syntax(lo, self.prev_token.span, attrs) + self.recover_incorrect_await_syntax(lo, self.prev_token.span) } else { - self.parse_lit_expr(attrs) + self.parse_lit_expr() } } else { - self.parse_lit_expr(attrs) + self.parse_lit_expr() } } - fn parse_lit_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_lit_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; match self.parse_opt_lit() { Some(literal) => { - let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Lit(literal), attrs); + let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Lit(literal)); self.maybe_recover_from_bad_qpath(expr) } None => self.try_macro_suggestion(), } } - fn parse_tuple_parens_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_tuple_parens_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; self.expect(&token::OpenDelim(Delimiter::Parenthesis))?; let (es, trailing_comma) = match self.parse_seq_to_end( @@ -1457,15 +1440,11 @@ impl<'a> Parser<'a> { // `(e,)` is a tuple with only one field, `e`. ExprKind::Tup(es) }; - let expr = self.mk_expr(lo.to(self.prev_token.span), kind, attrs); + let expr = self.mk_expr(lo.to(self.prev_token.span), kind); self.maybe_recover_from_bad_qpath(expr) } - fn parse_array_or_repeat_expr( - &mut self, - attrs: AttrVec, - close_delim: Delimiter, - ) -> PResult<'a, P<Expr>> { + fn parse_array_or_repeat_expr(&mut self, close_delim: Delimiter) -> PResult<'a, P<Expr>> { let lo = self.token.span; self.bump(); // `[` or other open delim @@ -1494,81 +1473,94 @@ impl<'a> Parser<'a> { ExprKind::Array(vec![first_expr]) } }; - let expr = self.mk_expr(lo.to(self.prev_token.span), kind, attrs); + let expr = self.mk_expr(lo.to(self.prev_token.span), kind); self.maybe_recover_from_bad_qpath(expr) } - fn parse_path_start_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_path_start_expr(&mut self) -> PResult<'a, P<Expr>> { let (qself, path) = if self.eat_lt() { let (qself, path) = self.parse_qpath(PathStyle::Expr)?; (Some(qself), path) } else { (None, self.parse_path(PathStyle::Expr)?) }; - let lo = path.span; // `!`, as an operator, is prefix, so we know this isn't that. - let (hi, kind) = if self.eat(&token::Not) { + let (span, kind) = if self.eat(&token::Not) { // MACRO INVOCATION expression if qself.is_some() { - self.struct_span_err(path.span, "macros cannot use qualified paths").emit(); + self.sess.emit_err(MacroInvocationWithQualifiedPath(path.span)); } - let mac = MacCall { + let lo = path.span; + let mac = P(MacCall { path, args: self.parse_mac_args()?, prior_type_ascription: self.last_type_ascription, - }; - (self.prev_token.span, ExprKind::MacCall(mac)) - } else if self.check(&token::OpenDelim(Delimiter::Brace)) { - if let Some(expr) = self.maybe_parse_struct_expr(qself.as_ref(), &path, &attrs) { + }); + (lo.to(self.prev_token.span), ExprKind::MacCall(mac)) + } else if self.check(&token::OpenDelim(Delimiter::Brace)) && + let Some(expr) = self.maybe_parse_struct_expr(qself.as_ref(), &path) { if qself.is_some() { self.sess.gated_spans.gate(sym::more_qualified_paths, path.span); } return expr; - } else { - (path.span, ExprKind::Path(qself, path)) - } } else { (path.span, ExprKind::Path(qself, path)) }; - let expr = self.mk_expr(lo.to(hi), kind, attrs); + let expr = self.mk_expr(span, kind); self.maybe_recover_from_bad_qpath(expr) } /// Parse `'label: $expr`. The label is already parsed. fn parse_labeled_expr( &mut self, - label: Label, - attrs: AttrVec, + label_: Label, mut consume_colon: bool, ) -> PResult<'a, P<Expr>> { - let lo = label.ident.span; - let label = Some(label); + let lo = label_.ident.span; + let label = Some(label_); let ate_colon = self.eat(&token::Colon); let expr = if self.eat_keyword(kw::While) { - self.parse_while_expr(label, lo, attrs) + self.parse_while_expr(label, lo) } else if self.eat_keyword(kw::For) { - self.parse_for_expr(label, lo, attrs) + self.parse_for_expr(label, lo) } else if self.eat_keyword(kw::Loop) { - self.parse_loop_expr(label, lo, attrs) + self.parse_loop_expr(label, lo) } else if self.check_noexpect(&token::OpenDelim(Delimiter::Brace)) || self.token.is_whole_block() { - self.parse_block_expr(label, lo, BlockCheckMode::Default, attrs) + self.parse_block_expr(label, lo, BlockCheckMode::Default) + } else if !ate_colon + && (matches!(self.token.kind, token::CloseDelim(_) | token::Comma) + || self.token.is_op()) + { + let lit = self.recover_unclosed_char(label_.ident, |self_| { + self_.sess.create_err(UnexpectedTokenAfterLabel { + span: self_.token.span, + remove_label: None, + enclose_in_block: None, + }) + }); + consume_colon = false; + Ok(self.mk_expr(lo, ExprKind::Lit(lit))) } else if !ate_colon && (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt)) { // We're probably inside of a `Path<'a>` that needs a turbofish - let msg = "expected `while`, `for`, `loop` or `{` after a label"; - self.struct_span_err(self.token.span, msg).span_label(self.token.span, msg).emit(); + self.sess.emit_err(UnexpectedTokenAfterLabel { + span: self.token.span, + remove_label: None, + enclose_in_block: None, + }); consume_colon = false; Ok(self.mk_expr_err(lo)) } else { - let msg = "expected `while`, `for`, `loop` or `{` after a label"; - - let mut err = self.struct_span_err(self.token.span, msg); - err.span_label(self.token.span, msg); + let mut err = UnexpectedTokenAfterLabel { + span: self.token.span, + remove_label: None, + enclose_in_block: None, + }; // Continue as an expression in an effort to recover on `'label: non_block_expr`. let expr = self.parse_expr().map(|expr| { @@ -1595,78 +1587,81 @@ impl<'a> Parser<'a> { // If there are no breaks that may use this label, suggest removing the label and // recover to the unmodified expression. if !found_labeled_breaks { - let msg = "consider removing the label"; - err.span_suggestion_verbose( - lo.until(span), - msg, - "", - Applicability::MachineApplicable, - ); + err.remove_label = Some(lo.until(span)); return expr; } - let sugg_msg = "consider enclosing expression in a block"; - let suggestions = vec![ - (span.shrink_to_lo(), "{ ".to_owned()), - (span.shrink_to_hi(), " }".to_owned()), - ]; - - err.multipart_suggestion_verbose( - sugg_msg, - suggestions, - Applicability::MachineApplicable, - ); + err.enclose_in_block = Some(UnexpectedTokenAfterLabelSugg { + left: span.shrink_to_lo(), + right: span.shrink_to_hi(), + }); - // Replace `'label: non_block_expr` with `'label: {non_block_expr}` in order to supress future errors about `break 'label`. + // Replace `'label: non_block_expr` with `'label: {non_block_expr}` in order to suppress future errors about `break 'label`. let stmt = self.mk_stmt(span, StmtKind::Expr(expr)); let blk = self.mk_block(vec![stmt], BlockCheckMode::Default, span); - self.mk_expr(span, ExprKind::Block(blk, label), ThinVec::new()) + self.mk_expr(span, ExprKind::Block(blk, label)) }); - err.emit(); + self.sess.emit_err(err); expr }?; if !ate_colon && consume_colon { - self.error_labeled_expr_must_be_followed_by_colon(lo, expr.span); + self.sess.emit_err(RequireColonAfterLabeledExpression { + span: expr.span, + label: lo, + label_end: lo.shrink_to_hi(), + }); } Ok(expr) } - fn error_labeled_expr_must_be_followed_by_colon(&self, lo: Span, span: Span) { - self.struct_span_err(span, "labeled expression must be followed by `:`") - .span_label(lo, "the label") - .span_suggestion_short( - lo.shrink_to_hi(), - "add `:` after the label", - ": ", - Applicability::MachineApplicable, + /// Emit an error when a char is parsed as a lifetime because of a missing quote + pub(super) fn recover_unclosed_char( + &mut self, + lifetime: Ident, + err: impl FnOnce(&mut Self) -> DiagnosticBuilder<'a, ErrorGuaranteed>, + ) -> ast::Lit { + if let Some(mut diag) = + self.sess.span_diagnostic.steal_diagnostic(lifetime.span, StashKey::LifetimeIsChar) + { + diag.span_suggestion_verbose( + lifetime.span.shrink_to_hi(), + "add `'` to close the char literal", + "'", + Applicability::MaybeIncorrect, ) - .note("labels are used before loops and blocks, allowing e.g., `break 'label` to them") .emit(); + } else { + err(self) + .span_suggestion_verbose( + lifetime.span.shrink_to_hi(), + "add `'` to close the char literal", + "'", + Applicability::MaybeIncorrect, + ) + .emit(); + } + ast::Lit { + token_lit: token::Lit::new(token::LitKind::Char, lifetime.name, None), + kind: ast::LitKind::Char(lifetime.name.as_str().chars().next().unwrap_or('_')), + span: lifetime.span, + } } /// Recover on the syntax `do catch { ... }` suggesting `try { ... }` instead. - fn recover_do_catch(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn recover_do_catch(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; self.bump(); // `do` self.bump(); // `catch` - let span_dc = lo.to(self.prev_token.span); - self.struct_span_err(span_dc, "found removed `do catch` syntax") - .span_suggestion( - span_dc, - "replace with the new syntax", - "try", - Applicability::MachineApplicable, - ) - .note("following RFC #2388, the new non-placeholder syntax is `try`") - .emit(); + let span = lo.to(self.prev_token.span); + self.sess.emit_err(DoCatchSyntaxRemoved { span }); - self.parse_try_block(lo, attrs) + self.parse_try_block(lo) } /// Parse an expression if the token can begin one. @@ -1675,15 +1670,15 @@ impl<'a> Parser<'a> { } /// Parse `"return" expr?`. - fn parse_return_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_return_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.prev_token.span; let kind = ExprKind::Ret(self.parse_expr_opt()?); - let expr = self.mk_expr(lo.to(self.prev_token.span), kind, attrs); + let expr = self.mk_expr(lo.to(self.prev_token.span), kind); self.maybe_recover_from_bad_qpath(expr) } /// Parse `"do" "yeet" expr?`. - fn parse_yeet_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_yeet_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; self.bump(); // `do` @@ -1693,7 +1688,7 @@ impl<'a> Parser<'a> { let span = lo.to(self.prev_token.span); self.sess.gated_spans.gate(sym::yeet_expr, span); - let expr = self.mk_expr(span, kind, attrs); + let expr = self.mk_expr(span, kind); self.maybe_recover_from_bad_qpath(expr) } @@ -1705,26 +1700,20 @@ impl<'a> Parser<'a> { /// `break 'lbl: loop {}`); a labeled break with an unlabeled loop as its value /// expression only gets a warning for compatibility reasons; and a labeled break /// with a labeled loop does not even get a warning because there is no ambiguity. - fn parse_break_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_break_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.prev_token.span; let mut label = self.eat_label(); let kind = if label.is_some() && self.token == token::Colon { // The value expression can be a labeled loop, see issue #86948, e.g.: // `loop { break 'label: loop { break 'label 42; }; }` - let lexpr = self.parse_labeled_expr(label.take().unwrap(), AttrVec::new(), true)?; - self.struct_span_err( - lexpr.span, - "parentheses are required around this expression to avoid confusion with a labeled break expression", - ) - .multipart_suggestion( - "wrap the expression in parentheses", - vec![ - (lexpr.span.shrink_to_lo(), "(".to_string()), - (lexpr.span.shrink_to_hi(), ")".to_string()), - ], - Applicability::MachineApplicable, - ) - .emit(); + let lexpr = self.parse_labeled_expr(label.take().unwrap(), true)?; + self.sess.emit_err(LabeledLoopInBreak { + span: lexpr.span, + sub: WrapExpressionInParentheses { + left: lexpr.span.shrink_to_lo(), + right: lexpr.span.shrink_to_hi(), + }, + }); Some(lexpr) } else if self.token != token::OpenDelim(Delimiter::Brace) || !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL) @@ -1753,17 +1742,17 @@ impl<'a> Parser<'a> { } else { None }; - let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Break(label, kind), attrs); + let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Break(label, kind)); self.maybe_recover_from_bad_qpath(expr) } /// Parse `"yield" expr?`. - fn parse_yield_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_yield_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.prev_token.span; let kind = ExprKind::Yield(self.parse_expr_opt()?); let span = lo.to(self.prev_token.span); self.sess.gated_spans.gate(sym::generators, span); - let expr = self.mk_expr(span, kind, attrs); + let expr = self.mk_expr(span, kind); self.maybe_recover_from_bad_qpath(expr) } @@ -1775,8 +1764,8 @@ impl<'a> Parser<'a> { Some(lit) => match lit.kind { ast::LitKind::Str(symbol_unescaped, style) => Ok(ast::StrLit { style, - symbol: lit.token.symbol, - suffix: lit.token.suffix, + symbol: lit.token_lit.symbol, + suffix: lit.token_lit.suffix, span: lit.span, symbol_unescaped, }), @@ -1787,7 +1776,7 @@ impl<'a> Parser<'a> { } pub(super) fn parse_lit(&mut self) -> PResult<'a, Lit> { - self.parse_opt_lit().ok_or_else(|| { + self.parse_opt_lit().ok_or(()).or_else(|()| { if let token::Interpolated(inner) = &self.token.kind { let expr = match inner.as_ref() { token::NtExpr(expr) => Some(expr), @@ -1796,16 +1785,25 @@ impl<'a> Parser<'a> { }; if let Some(expr) = expr { if matches!(expr.kind, ExprKind::Err) { - let mut err = self - .diagnostic() - .struct_span_err(self.token.span, "invalid interpolated expression"); + let mut err = InvalidInterpolatedExpression { span: self.token.span } + .into_diagnostic(&self.sess.span_diagnostic); err.downgrade_to_delayed_bug(); - return err; + return Err(err); } } } - let msg = format!("unexpected token: {}", super::token_descr(&self.token)); - self.struct_span_err(self.token.span, &msg) + let token = self.token.clone(); + let err = |self_: &mut Self| { + let msg = format!("unexpected token: {}", super::token_descr(&token)); + self_.struct_span_err(token.span, &msg) + }; + // On an error path, eagerly consider a lifetime to be an unclosed character lit + if self.token.is_lifetime() { + let lt = self.expect_lifetime(); + Ok(self.recover_unclosed_char(lt.ident, err)) + } else { + Err(err(self)) + } }) } @@ -1830,7 +1828,10 @@ impl<'a> Parser<'a> { }); if let Some(token) = &recovered { self.bump(); - self.error_float_lits_must_have_int_part(&token); + self.sess.emit_err(FloatLiteralRequiresIntegerPart { + span: token.span, + correct: pprust::token_to_string(token).into_owned(), + }); } } @@ -1853,22 +1854,11 @@ impl<'a> Parser<'a> { let suffixless_lit = token::Lit::new(lit.kind, lit.symbol, None); let symbol = Symbol::intern(&suffixless_lit.to_string()); let lit = token::Lit::new(token::Err, symbol, lit.suffix); - Some(Lit::from_lit_token(lit, span).unwrap_or_else(|_| unreachable!())) + Some(Lit::from_token_lit(lit, span).unwrap_or_else(|_| unreachable!())) } } } - fn error_float_lits_must_have_int_part(&self, token: &Token) { - self.struct_span_err(token.span, "float literals must have an integer part") - .span_suggestion( - token.span, - "must have an integer part", - pprust::token_to_string(token), - Applicability::MachineApplicable, - ) - .emit(); - } - fn report_lit_error(&self, err: LitError, lit: token::Lit, span: Span) { // Checks if `s` looks like i32 or u1234 etc. fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool { @@ -1897,39 +1887,24 @@ impl<'a> Parser<'a> { // by lexer, so here we don't report it the second time. LitError::LexerError => {} LitError::InvalidSuffix => { - self.expect_no_suffix( - span, - &format!("{} {} literal", kind.article(), kind.descr()), - suffix, - ); + if let Some(suffix) = suffix { + self.sess.emit_err(InvalidLiteralSuffix { + span, + kind: format!("{}", kind.descr()), + suffix, + }); + } } LitError::InvalidIntSuffix => { let suf = suffix.expect("suffix error with no suffix"); let suf = suf.as_str(); if looks_like_width_suffix(&['i', 'u'], &suf) { // If it looks like a width, try to be helpful. - let msg = format!("invalid width `{}` for integer literal", &suf[1..]); - self.struct_span_err(span, &msg) - .help("valid widths are 8, 16, 32, 64 and 128") - .emit(); + self.sess.emit_err(InvalidIntLiteralWidth { span, width: suf[1..].into() }); } else if let Some(fixed) = fix_base_capitalisation(suf) { - let msg = "invalid base prefix for number literal"; - - self.struct_span_err(span, msg) - .note("base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase") - .span_suggestion( - span, - "try making the prefix lowercase", - fixed, - Applicability::MaybeIncorrect, - ) - .emit(); + self.sess.emit_err(InvalidNumLiteralBasePrefix { span, fixed }); } else { - let msg = format!("invalid suffix `{suf}` for number literal"); - self.struct_span_err(span, &msg) - .span_label(span, format!("invalid suffix `{suf}`")) - .help("the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.)") - .emit(); + self.sess.emit_err(InvalidNumLiteralSuffix { span, suffix: suf.to_string() }); } } LitError::InvalidFloatSuffix => { @@ -1937,65 +1912,37 @@ impl<'a> Parser<'a> { let suf = suf.as_str(); if looks_like_width_suffix(&['f'], suf) { // If it looks like a width, try to be helpful. - let msg = format!("invalid width `{}` for float literal", &suf[1..]); - self.struct_span_err(span, &msg).help("valid widths are 32 and 64").emit(); + self.sess + .emit_err(InvalidFloatLiteralWidth { span, width: suf[1..].to_string() }); } else { - let msg = format!("invalid suffix `{suf}` for float literal"); - self.struct_span_err(span, &msg) - .span_label(span, format!("invalid suffix `{suf}`")) - .help("valid suffixes are `f32` and `f64`") - .emit(); + self.sess.emit_err(InvalidFloatLiteralSuffix { span, suffix: suf.to_string() }); } } LitError::NonDecimalFloat(base) => { - let descr = match base { - 16 => "hexadecimal", - 8 => "octal", - 2 => "binary", + match base { + 16 => self.sess.emit_err(HexadecimalFloatLiteralNotSupported { span }), + 8 => self.sess.emit_err(OctalFloatLiteralNotSupported { span }), + 2 => self.sess.emit_err(BinaryFloatLiteralNotSupported { span }), _ => unreachable!(), }; - self.struct_span_err(span, &format!("{descr} float literal is not supported")) - .span_label(span, "not supported") - .emit(); } LitError::IntTooLarge => { - self.struct_span_err(span, "integer literal is too large").emit(); + self.sess.emit_err(IntLiteralTooLarge { span }); } } } - pub(super) fn expect_no_suffix(&self, sp: Span, kind: &str, suffix: Option<Symbol>) { - if let Some(suf) = suffix { - let mut err = if kind == "a tuple index" - && [sym::i32, sym::u32, sym::isize, sym::usize].contains(&suf) - { - // #59553: warn instead of reject out of hand to allow the fix to percolate - // through the ecosystem when people fix their macros - let mut err = self - .sess - .span_diagnostic - .struct_span_warn(sp, &format!("suffixes on {kind} are invalid")); - err.note(&format!( - "`{}` is *temporarily* accepted on tuple index fields as it was \ - incorrectly accepted on stable for a few releases", - suf, - )); - err.help( - "on proc macros, you'll want to use `syn::Index::from` or \ - `proc_macro::Literal::*_unsuffixed` for code that will desugar \ - to tuple field access", - ); - err.note( - "see issue #60210 <https://github.com/rust-lang/rust/issues/60210> \ - for more information", - ); - err - } else { - self.struct_span_err(sp, &format!("suffixes on {kind} are invalid")) - .forget_guarantee() - }; - err.span_label(sp, format!("invalid suffix `{suf}`")); - err.emit(); + pub(super) fn expect_no_tuple_index_suffix(&self, span: Span, suffix: Symbol) { + if [sym::i32, sym::u32, sym::isize, sym::usize].contains(&suffix) { + // #59553: warn instead of reject out of hand to allow the fix to percolate + // through the ecosystem when people fix their macros + self.sess.emit_warning(InvalidLiteralSuffixOnTupleIndex { + span, + suffix, + exception: Some(()), + }); + } else { + self.sess.emit_err(InvalidLiteralSuffixOnTupleIndex { span, suffix, exception: None }); } } @@ -2007,14 +1954,10 @@ impl<'a> Parser<'a> { let lo = self.token.span; let minus_present = self.eat(&token::BinOp(token::Minus)); let lit = self.parse_lit()?; - let expr = self.mk_expr(lit.span, ExprKind::Lit(lit), AttrVec::new()); + let expr = self.mk_expr(lit.span, ExprKind::Lit(lit)); if minus_present { - Ok(self.mk_expr( - lo.to(self.prev_token.span), - self.mk_unary(UnOp::Neg, expr), - AttrVec::new(), - )) + Ok(self.mk_expr(lo.to(self.prev_token.span), self.mk_unary(UnOp::Neg, expr))) } else { Ok(expr) } @@ -2029,22 +1972,17 @@ impl<'a> Parser<'a> { /// Emits a suggestion if it looks like the user meant an array but /// accidentally used braces, causing the code to be interpreted as a block /// expression. - fn maybe_suggest_brackets_instead_of_braces( - &mut self, - lo: Span, - attrs: AttrVec, - ) -> Option<P<Expr>> { + fn maybe_suggest_brackets_instead_of_braces(&mut self, lo: Span) -> Option<P<Expr>> { let mut snapshot = self.create_snapshot_for_diagnostic(); - match snapshot.parse_array_or_repeat_expr(attrs, Delimiter::Brace) { + match snapshot.parse_array_or_repeat_expr(Delimiter::Brace) { Ok(arr) => { - let hi = snapshot.prev_token.span; - self.struct_span_err(arr.span, "this is a block expression, not an array") - .multipart_suggestion( - "to make an array, use square brackets instead of curly braces", - vec![(lo, "[".to_owned()), (hi, "]".to_owned())], - Applicability::MaybeIncorrect, - ) - .emit(); + self.sess.emit_err(ArrayBracketsInsteadOfSpaces { + span: arr.span, + sub: ArrayBracketsInsteadOfSpacesSugg { + left: lo, + right: snapshot.prev_token.span, + }, + }); self.restore_snapshot(snapshot); Some(self.mk_expr_err(arr.span)) @@ -2056,43 +1994,76 @@ impl<'a> Parser<'a> { } } + fn suggest_missing_semicolon_before_array( + &self, + prev_span: Span, + open_delim_span: Span, + ) -> PResult<'a, ()> { + if self.token.kind == token::Comma { + if !self.sess.source_map().is_multiline(prev_span.until(self.token.span)) { + return Ok(()); + } + let mut snapshot = self.create_snapshot_for_diagnostic(); + snapshot.bump(); + match snapshot.parse_seq_to_before_end( + &token::CloseDelim(Delimiter::Bracket), + SeqSep::trailing_allowed(token::Comma), + |p| p.parse_expr(), + ) { + Ok(_) + // When the close delim is `)`, `token.kind` is expected to be `token::CloseDelim(Delimiter::Parenthesis)`, + // but the actual `token.kind` is `token::CloseDelim(Delimiter::Bracket)`. + // This is because the `token.kind` of the close delim is treated as the same as + // that of the open delim in `TokenTreesReader::parse_token_tree`, even if the delimiters of them are different. + // Therefore, `token.kind` should not be compared here. + if snapshot + .span_to_snippet(snapshot.token.span) + .map_or(false, |snippet| snippet == "]") => + { + return Err(MissingSemicolonBeforeArray { + open_delim: open_delim_span, + semicolon: prev_span.shrink_to_hi(), + }.into_diagnostic(&self.sess.span_diagnostic)); + } + Ok(_) => (), + Err(err) => err.cancel(), + } + } + Ok(()) + } + /// Parses a block or unsafe block. pub(super) fn parse_block_expr( &mut self, opt_label: Option<Label>, lo: Span, blk_mode: BlockCheckMode, - mut attrs: AttrVec, ) -> PResult<'a, P<Expr>> { if self.is_array_like_block() { - if let Some(arr) = self.maybe_suggest_brackets_instead_of_braces(lo, attrs.clone()) { + if let Some(arr) = self.maybe_suggest_brackets_instead_of_braces(lo) { return Ok(arr); } } - if let Some(label) = opt_label { - self.sess.gated_spans.gate(sym::label_break_value, label.ident.span); - } - if self.token.is_whole_block() { - self.struct_span_err(self.token.span, "cannot use a `block` macro fragment here") - .span_label(lo.to(self.token.span), "the `block` fragment is within this context") - .emit(); + self.sess.emit_err(InvalidBlockMacroSegment { + span: self.token.span, + context: lo.to(self.token.span), + }); } - let (inner_attrs, blk) = self.parse_block_common(lo, blk_mode)?; - attrs.extend(inner_attrs); - Ok(self.mk_expr(blk.span, ExprKind::Block(blk, opt_label), attrs)) + let (attrs, blk) = self.parse_block_common(lo, blk_mode)?; + Ok(self.mk_expr_with_attrs(blk.span, ExprKind::Block(blk, opt_label), attrs)) } /// Parse a block which takes no attributes and has no label fn parse_simple_block(&mut self) -> PResult<'a, P<Expr>> { let blk = self.parse_block()?; - Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new())) + Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None))) } /// Parses a closure expression (e.g., `move |args| expr`). - fn parse_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_closure_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; let binder = if self.check_keyword(kw::For) { @@ -2127,7 +2098,7 @@ impl<'a> Parser<'a> { _ => { // If an explicit return type is given, require a block to appear (RFC 968). let body_lo = self.token.span; - self.parse_block_expr(None, body_lo, BlockCheckMode::Default, AttrVec::new())? + self.parse_block_expr(None, body_lo, BlockCheckMode::Default)? } }; @@ -2138,6 +2109,12 @@ impl<'a> Parser<'a> { if self.token.kind == TokenKind::Semi && matches!(self.token_cursor.frame.delim_sp, Some((Delimiter::Parenthesis, _))) + // HACK: This is needed so we can detect whether we're inside a macro, + // where regular assumptions about what tokens can follow other tokens + // don't necessarily apply. + && self.may_recover() + // FIXME(Nilstrieb): Remove this check once `may_recover` actually stops recovery + && self.subparser_name.is_none() { // It is likely that the closure body is a block but where the // braces have been removed. We will recover and eat the next @@ -2158,7 +2135,6 @@ impl<'a> Parser<'a> { body, lo.to(decl_hi), ), - attrs, ); // Disable recovery for closure body @@ -2175,7 +2151,8 @@ impl<'a> Parser<'a> { // Check for `move async` and recover if self.check_keyword(kw::Async) { let move_async_span = self.token.span.with_lo(self.prev_token.span.data().lo); - Err(self.incorrect_move_async_order_found(move_async_span)) + Err(AsyncMoveOrderIncorrect { span: move_async_span } + .into_diagnostic(&self.sess.span_diagnostic)) } else { Ok(CaptureBy::Value) } @@ -2221,10 +2198,10 @@ impl<'a> Parser<'a> { Ok(( Param { - attrs: attrs.into(), + attrs, ty, pat, - span: lo.to(this.token.span), + span: lo.to(this.prev_token.span), id: DUMMY_NODE_ID, is_placeholder: false, }, @@ -2234,19 +2211,13 @@ impl<'a> Parser<'a> { } /// Parses an `if` expression (`if` token already eaten). - fn parse_if_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_if_expr(&mut self) -> PResult<'a, P<Expr>> { let lo = self.prev_token.span; let cond = self.parse_cond_expr()?; - - self.parse_if_after_cond(attrs, lo, cond) + self.parse_if_after_cond(lo, cond) } - fn parse_if_after_cond( - &mut self, - attrs: AttrVec, - lo: Span, - mut cond: P<Expr>, - ) -> PResult<'a, P<Expr>> { + fn parse_if_after_cond(&mut self, lo: Span, mut cond: P<Expr>) -> PResult<'a, P<Expr>> { let cond_span = cond.span; // Tries to interpret `cond` as either a missing expression if it's a block, // or as an unfinished expression if it's a binop and the RHS is a block. @@ -2255,11 +2226,19 @@ impl<'a> Parser<'a> { let block = match &mut cond.kind { ExprKind::Binary(Spanned { span: binop_span, .. }, _, right) if let ExprKind::Block(_, None) = right.kind => { - this.error_missing_if_then_block(lo, cond_span.shrink_to_lo().to(*binop_span), true).emit(); + self.sess.emit_err(IfExpressionMissingThenBlock { + if_span: lo, + sub: IfExpressionMissingThenBlockSub::UnfinishedCondition( + cond_span.shrink_to_lo().to(*binop_span) + ), + }); std::mem::replace(right, this.mk_expr_err(binop_span.shrink_to_hi())) }, ExprKind::Block(_, None) => { - this.error_missing_if_cond(lo, cond_span).emit(); + self.sess.emit_err(IfExpressionMissingCondition { + if_span: lo.shrink_to_hi(), + block_span: self.sess.source_map().start_point(cond_span), + }); std::mem::replace(&mut cond, this.mk_expr_err(cond_span.shrink_to_hi())) } _ => { @@ -2277,7 +2256,10 @@ impl<'a> Parser<'a> { if let Some(block) = recover_block_from_condition(self) { block } else { - self.error_missing_if_then_block(lo, cond_span, false).emit(); + self.sess.emit_err(IfExpressionMissingThenBlock { + if_span: lo, + sub: IfExpressionMissingThenBlockSub::AddThenBlock(cond_span.shrink_to_hi()), + }); self.mk_block_err(cond_span.shrink_to_hi()) } } else { @@ -2302,45 +2284,13 @@ impl<'a> Parser<'a> { block }; let els = if self.eat_keyword(kw::Else) { Some(self.parse_else_expr()?) } else { None }; - Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::If(cond, thn, els), attrs)) - } - - fn error_missing_if_then_block( - &self, - if_span: Span, - cond_span: Span, - is_unfinished: bool, - ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { - let mut err = self.struct_span_err( - if_span, - "this `if` expression is missing a block after the condition", - ); - if is_unfinished { - err.span_help(cond_span, "this binary operation is possibly unfinished"); - } else { - err.span_help(cond_span.shrink_to_hi(), "add a block here"); - } - err - } - - fn error_missing_if_cond( - &self, - lo: Span, - span: Span, - ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { - let next_span = self.sess.source_map().next_point(lo); - let mut err = self.struct_span_err(next_span, "missing condition for `if` expression"); - err.span_label(next_span, "expected condition here"); - err.span_label( - self.sess.source_map().start_point(span), - "if this block is the condition of the `if` expression, then it must be followed by another block" - ); - err + Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::If(cond, thn, els))) } /// Parses the condition of a `if` or `while` expression. fn parse_cond_expr(&mut self) -> PResult<'a, P<Expr>> { - let cond = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, None)?; + let cond = + self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, None)?; if let ExprKind::Let(..) = cond.kind { // Remove the last feature gating of a `let` expression since it's stable. @@ -2351,7 +2301,7 @@ impl<'a> Parser<'a> { } /// Parses a `let $pat = $expr` pseudo-expression. - fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_let_expr(&mut self) -> PResult<'a, P<Expr>> { // 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. @@ -2360,8 +2310,7 @@ impl<'a> Parser<'a> { TokenKind::AndAnd | TokenKind::Ident(kw::If, _) | TokenKind::Ident(kw::While, _) ); if !self.restrictions.contains(Restrictions::ALLOW_LET) || not_in_chain { - self.struct_span_err(self.token.span, "expected expression, found `let` statement") - .emit(); + self.sess.emit_err(ExpectedExpressionFoundLet { span: self.token.span }); } self.bump(); // Eat `let` token @@ -2378,7 +2327,7 @@ impl<'a> Parser<'a> { })?; 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), attrs)) + Ok(self.mk_expr(span, ExprKind::Let(pat, expr, span))) } /// Parses an `else { ... }` expression (`else` token already eaten). @@ -2386,7 +2335,7 @@ impl<'a> Parser<'a> { let else_span = self.prev_token.span; // `else` let attrs = self.parse_outer_attributes()?.take_for_recovery(); // For recovery. let expr = if self.eat_keyword(kw::If) { - self.parse_if_expr(AttrVec::new())? + self.parse_if_expr()? } else if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) { self.parse_simple_block()? } else { @@ -2400,16 +2349,13 @@ impl<'a> Parser<'a> { if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) && classify::expr_requires_semi_to_be_stmt(&cond) => { - self.struct_span_err(first_tok_span, format!("expected `{{`, found {first_tok}")) - .span_label(else_span, "expected an `if` or a block after this `else`") - .span_suggestion( - cond.span.shrink_to_lo(), - "add an `if` if this is the condition of a chained `else if` statement", - "if ", - Applicability::MaybeIncorrect, - ) - .emit(); - self.parse_if_after_cond(AttrVec::new(), cond.span.shrink_to_lo(), cond)? + self.sess.emit_err(ExpectedElseBlock { + first_tok_span, + first_tok, + else_span, + condition_start: cond.span.shrink_to_lo(), + }); + self.parse_if_after_cond(cond.span.shrink_to_lo(), cond)? } Err(e) => { e.cancel(); @@ -2433,25 +2379,22 @@ impl<'a> Parser<'a> { branch_span: Span, attrs: &[ast::Attribute], ) { - let (span, last) = match attrs { + let (attributes, last) = match attrs { [] => return, [x0 @ xn] | [x0, .., xn] => (x0.span.to(xn.span), xn.span), }; let ctx = if is_ctx_else { "else" } else { "if" }; - self.struct_span_err(last, "outer attributes are not allowed on `if` and `else` branches") - .span_label(branch_span, "the attributes are attached to this branch") - .span_label(ctx_span, format!("the branch belongs to this `{ctx}`")) - .span_suggestion(span, "remove the attributes", "", Applicability::MachineApplicable) - .emit(); + self.sess.emit_err(OuterAttributeNotAllowedOnIfElse { + last, + branch_span, + ctx_span, + ctx: ctx.to_string(), + attributes, + }); } /// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten). - fn parse_for_expr( - &mut self, - opt_label: Option<Label>, - lo: Span, - mut attrs: AttrVec, - ) -> PResult<'a, P<Expr>> { + fn parse_for_expr(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> { // Record whether we are about to parse `for (`. // This is used below for recovery in case of `for ( $stuff ) $block` // in which case we will suggest `for $stuff $block`. @@ -2474,63 +2417,51 @@ impl<'a> Parser<'a> { let pat = self.recover_parens_around_for_head(pat, begin_paren); - let (iattrs, loop_block) = self.parse_inner_attrs_and_block()?; - attrs.extend(iattrs); + let (attrs, loop_block) = self.parse_inner_attrs_and_block()?; let kind = ExprKind::ForLoop(pat, expr, loop_block, opt_label); - Ok(self.mk_expr(lo.to(self.prev_token.span), kind, attrs)) + Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs)) } fn error_missing_in_for_loop(&mut self) { - let (span, msg, sugg) = if self.token.is_ident_named(sym::of) { + let (span, sub): (_, fn(_) -> _) = if self.token.is_ident_named(sym::of) { // Possibly using JS syntax (#75311). let span = self.token.span; self.bump(); - (span, "try using `in` here instead", "in") + (span, MissingInInForLoopSub::InNotOf) } else { - (self.prev_token.span.between(self.token.span), "try adding `in` here", " in ") + (self.prev_token.span.between(self.token.span), MissingInInForLoopSub::AddIn) }; - self.struct_span_err(span, "missing `in` in `for` loop") - .span_suggestion_short( - span, - msg, - sugg, - // Has been misleading, at least in the past (closed Issue #48492). - Applicability::MaybeIncorrect, - ) - .emit(); + + self.sess.emit_err(MissingInInForLoop { span, sub: sub(span) }); } /// Parses a `while` or `while let` expression (`while` token already eaten). - fn parse_while_expr( - &mut self, - opt_label: Option<Label>, - lo: Span, - mut attrs: AttrVec, - ) -> PResult<'a, P<Expr>> { + fn parse_while_expr(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> { let cond = self.parse_cond_expr().map_err(|mut err| { err.span_label(lo, "while parsing the condition of this `while` expression"); err })?; - let (iattrs, body) = self.parse_inner_attrs_and_block().map_err(|mut err| { + let (attrs, body) = self.parse_inner_attrs_and_block().map_err(|mut err| { err.span_label(lo, "while parsing the body of this `while` expression"); err.span_label(cond.span, "this `while` condition successfully parsed"); err })?; - attrs.extend(iattrs); - Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::While(cond, body, opt_label), attrs)) + Ok(self.mk_expr_with_attrs( + lo.to(self.prev_token.span), + ExprKind::While(cond, body, opt_label), + attrs, + )) } /// Parses `loop { ... }` (`loop` token already eaten). - fn parse_loop_expr( - &mut self, - opt_label: Option<Label>, - lo: Span, - mut attrs: AttrVec, - ) -> PResult<'a, P<Expr>> { - let (iattrs, body) = self.parse_inner_attrs_and_block()?; - attrs.extend(iattrs); - Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::Loop(body, opt_label), attrs)) + fn parse_loop_expr(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> { + let (attrs, body) = self.parse_inner_attrs_and_block()?; + Ok(self.mk_expr_with_attrs( + lo.to(self.prev_token.span), + ExprKind::Loop(body, opt_label), + attrs, + )) } pub(crate) fn eat_label(&mut self) -> Option<Label> { @@ -2541,7 +2472,7 @@ impl<'a> Parser<'a> { } /// Parses a `match ... { ... }` expression (`match` token already eaten). - fn parse_match_expr(&mut self, mut attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_match_expr(&mut self) -> PResult<'a, P<Expr>> { let match_span = self.prev_token.span; let lo = self.prev_token.span; let scrutinee = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?; @@ -2561,7 +2492,7 @@ impl<'a> Parser<'a> { return Err(e); } } - attrs.extend(self.parse_inner_attributes()?); + let attrs = self.parse_inner_attributes()?; let mut arms: Vec<Arm> = Vec::new(); while self.token != token::CloseDelim(Delimiter::Brace) { @@ -2575,13 +2506,17 @@ impl<'a> Parser<'a> { if self.token == token::CloseDelim(Delimiter::Brace) { self.bump(); } - return Ok(self.mk_expr(span, ExprKind::Match(scrutinee, arms), attrs)); + return Ok(self.mk_expr_with_attrs( + span, + ExprKind::Match(scrutinee, arms), + attrs, + )); } } } let hi = self.token.span; self.bump(); - Ok(self.mk_expr(lo.to(hi), ExprKind::Match(scrutinee, arms), attrs)) + Ok(self.mk_expr_with_attrs(lo.to(hi), ExprKind::Match(scrutinee, arms), attrs)) } /// Attempt to recover from match arm body with statements and no surrounding braces. @@ -2598,39 +2533,22 @@ impl<'a> Parser<'a> { self.bump(); // `;` let mut stmts = vec![self.mk_stmt(first_expr.span, ast::StmtKind::Expr(first_expr.clone()))]; - let err = |this: &mut Parser<'_>, stmts: Vec<ast::Stmt>| { + let err = |this: &Parser<'_>, stmts: Vec<ast::Stmt>| { let span = stmts[0].span.to(stmts[stmts.len() - 1].span); - let mut err = this.struct_span_err(span, "`match` arm body without braces"); - let (these, s, are) = - if stmts.len() > 1 { ("these", "s", "are") } else { ("this", "", "is") }; - err.span_label( - span, - &format!( - "{these} statement{s} {are} not surrounded by a body", - these = these, - s = s, - are = are - ), - ); - err.span_label(arrow_span, "while parsing the `match` arm starting here"); - if stmts.len() > 1 { - err.multipart_suggestion( - &format!("surround the statement{s} with a body"), - vec![ - (span.shrink_to_lo(), "{ ".to_string()), - (span.shrink_to_hi(), " }".to_string()), - ], - Applicability::MachineApplicable, - ); - } else { - err.span_suggestion( - semi_sp, - "use a comma to end a `match` arm expression", - ",", - Applicability::MachineApplicable, - ); - } - err.emit(); + + this.sess.emit_err(MatchArmBodyWithoutBraces { + statements: span, + arrow: arrow_span, + num_statements: stmts.len(), + sub: if stmts.len() > 1 { + MatchArmBodyWithoutBracesSugg::AddBraces { + left: span.shrink_to_lo(), + right: span.shrink_to_hi(), + } + } else { + MatchArmBodyWithoutBracesSugg::UseComma { semicolon: semi_sp } + }, + }); this.mk_expr_err(span) }; // We might have either a `,` -> `;` typo, or a block without braces. We need @@ -2681,7 +2599,7 @@ impl<'a> Parser<'a> { } pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> { - // Used to check the `let_chains` and `if_let_guard` features mostly by scaning + // Used to check the `let_chains` and `if_let_guard` features mostly by scanning // `&&` tokens. fn check_let_expr(expr: &Expr) -> (bool, bool) { match expr.kind { @@ -2756,7 +2674,7 @@ impl<'a> Parser<'a> { let span = body.span; return Ok(( ast::Arm { - attrs: attrs.into(), + attrs, pat, guard, body, @@ -2811,17 +2729,9 @@ impl<'a> Parser<'a> { .is_ok(); if pattern_follows && snapshot.check(&TokenKind::FatArrow) { err.cancel(); - this.struct_span_err( - hi.shrink_to_hi(), - "expected `,` following `match` arm", - ) - .span_suggestion( - hi.shrink_to_hi(), - "missing a comma here to end this `match` arm", - ",", - Applicability::MachineApplicable, - ) - .emit(); + this.sess.emit_err(MissingCommaAfterMatchArm { + span: hi.shrink_to_hi(), + }); return Ok(true); } } @@ -2834,7 +2744,7 @@ impl<'a> Parser<'a> { Ok(( ast::Arm { - attrs: attrs.into(), + attrs, pat, guard, body: expr, @@ -2848,21 +2758,15 @@ impl<'a> Parser<'a> { } /// Parses a `try {...}` expression (`try` token already eaten). - fn parse_try_block(&mut self, span_lo: Span, mut attrs: AttrVec) -> PResult<'a, P<Expr>> { - let (iattrs, body) = self.parse_inner_attrs_and_block()?; - attrs.extend(iattrs); + fn parse_try_block(&mut self, span_lo: Span) -> PResult<'a, P<Expr>> { + let (attrs, body) = self.parse_inner_attrs_and_block()?; if self.eat_keyword(kw::Catch) { - let mut error = self.struct_span_err( - self.prev_token.span, - "keyword `catch` cannot follow a `try` block", - ); - error.help("try using `match` on the result of the `try` block instead"); - error.emit(); - Err(error) + Err(CatchAfterTry { span: self.prev_token.span } + .into_diagnostic(&self.sess.span_diagnostic)) } else { let span = span_lo.to(body.span); self.sess.gated_spans.gate(sym::try_blocks, span); - Ok(self.mk_expr(span, ExprKind::TryBlock(body), attrs)) + Ok(self.mk_expr_with_attrs(span, ExprKind::TryBlock(body), attrs)) } } @@ -2884,14 +2788,13 @@ impl<'a> Parser<'a> { } /// Parses an `async move? {...}` expression. - fn parse_async_block(&mut self, mut attrs: AttrVec) -> PResult<'a, P<Expr>> { + fn parse_async_block(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; self.expect_keyword(kw::Async)?; let capture_clause = self.parse_capture_clause()?; - let (iattrs, body) = self.parse_inner_attrs_and_block()?; - attrs.extend(iattrs); + let (attrs, body) = self.parse_inner_attrs_and_block()?; let kind = ExprKind::Async(capture_clause, DUMMY_NODE_ID, body); - Ok(self.mk_expr(lo.to(self.prev_token.span), kind, attrs)) + Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs)) } fn is_async_block(&self) -> bool { @@ -2925,33 +2828,28 @@ impl<'a> Parser<'a> { &mut self, qself: Option<&ast::QSelf>, path: &ast::Path, - attrs: &AttrVec, ) -> Option<PResult<'a, P<Expr>>> { let struct_allowed = !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL); if struct_allowed || self.is_certainly_not_a_block() { if let Err(err) = self.expect(&token::OpenDelim(Delimiter::Brace)) { return Some(Err(err)); } - let expr = self.parse_struct_expr(qself.cloned(), path.clone(), attrs.clone(), true); + let expr = self.parse_struct_expr(qself.cloned(), path.clone(), true); if let (Ok(expr), false) = (&expr, struct_allowed) { // This is a struct literal, but we don't can't accept them here. - self.error_struct_lit_not_allowed_here(path.span, expr.span); + self.sess.emit_err(StructLiteralNotAllowedHere { + span: expr.span, + sub: StructLiteralNotAllowedHereSugg { + left: path.span.shrink_to_lo(), + right: expr.span.shrink_to_hi(), + }, + }); } return Some(expr); } None } - fn error_struct_lit_not_allowed_here(&self, lo: Span, sp: Span) { - self.struct_span_err(sp, "struct literals are not allowed here") - .multipart_suggestion( - "surround the struct literal with parentheses", - vec![(lo.shrink_to_lo(), "(".to_string()), (sp.shrink_to_hi(), ")".to_string())], - Applicability::MachineApplicable, - ) - .emit(); - } - pub(super) fn parse_struct_fields( &mut self, pth: ast::Path, @@ -3069,7 +2967,6 @@ impl<'a> Parser<'a> { &mut self, qself: Option<ast::QSelf>, pth: ast::Path, - attrs: AttrVec, recover: bool, ) -> PResult<'a, P<Expr>> { let lo = pth.span; @@ -3082,7 +2979,7 @@ impl<'a> Parser<'a> { } else { ExprKind::Struct(P(ast::StructExpr { qself, path: pth, fields, rest: base })) }; - Ok(self.mk_expr(span, expr, attrs)) + Ok(self.mk_expr(span, expr)) } /// Use in case of error after field-looking code: `S { foo: () with a }`. @@ -3110,18 +3007,10 @@ impl<'a> Parser<'a> { if self.token != token::Comma { return; } - self.struct_span_err( - span.to(self.prev_token.span), - "cannot use a comma after the base struct", - ) - .span_suggestion_short( - self.token.span, - "remove this comma", - "", - Applicability::MachineApplicable, - ) - .note("the base struct must always be the last field") - .emit(); + self.sess.emit_err(CommaAfterBaseStruct { + span: span.to(self.prev_token.span), + comma: self.token.span, + }); self.recover_stmt(); } @@ -3137,7 +3026,7 @@ impl<'a> Parser<'a> { // Mimic `x: x` for the `x` field shorthand. let ident = this.parse_ident_common(false)?; let path = ast::Path::from_ident(ident); - (ident, this.mk_expr(ident.span, ExprKind::Path(None, path), AttrVec::new())) + (ident, this.mk_expr(ident.span, ExprKind::Path(None, path))) } else { let ident = this.parse_field_name()?; this.error_on_eq_field_init(ident); @@ -3151,7 +3040,7 @@ impl<'a> Parser<'a> { span: lo.to(expr.span), expr, is_shorthand, - attrs: attrs.into(), + attrs, id: DUMMY_NODE_ID, is_placeholder: false, }, @@ -3167,43 +3056,18 @@ impl<'a> Parser<'a> { return; } - self.struct_span_err(self.token.span, "expected `:`, found `=`") - .span_suggestion( - field_name.span.shrink_to_hi().to(self.token.span), - "replace equals symbol with a colon", - ":", - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(EqFieldInit { + span: self.token.span, + eq: field_name.span.shrink_to_hi().to(self.token.span), + }); } fn err_dotdotdot_syntax(&self, span: Span) { - self.struct_span_err(span, "unexpected token: `...`") - .span_suggestion( - span, - "use `..` for an exclusive range", - "..", - Applicability::MaybeIncorrect, - ) - .span_suggestion( - span, - "or `..=` for an inclusive range", - "..=", - Applicability::MaybeIncorrect, - ) - .emit(); + self.sess.emit_err(DotDotDot { span }); } fn err_larrow_operator(&self, span: Span) { - self.struct_span_err(span, "unexpected token: `<-`") - .span_suggestion( - span, - "if you meant to write a comparison against a negative value, add a \ - space in between `<` and `-`", - "< -", - Applicability::MaybeIncorrect, - ) - .emit(); + self.sess.emit_err(LeftArrowOperator { span }); } fn mk_assign_op(&self, binop: BinOp, lhs: P<Expr>, rhs: P<Expr>) -> ExprKind { @@ -3242,17 +3106,21 @@ impl<'a> Parser<'a> { fn mk_await_expr(&mut self, self_arg: P<Expr>, lo: Span) -> P<Expr> { let span = lo.to(self.prev_token.span); - let await_expr = self.mk_expr(span, ExprKind::Await(self_arg), AttrVec::new()); + let await_expr = self.mk_expr(span, ExprKind::Await(self_arg)); self.recover_from_await_method_call(); await_expr } - pub(crate) fn mk_expr(&self, span: Span, kind: ExprKind, attrs: AttrVec) -> P<Expr> { + pub(crate) fn mk_expr_with_attrs(&self, span: Span, kind: ExprKind, attrs: AttrVec) -> P<Expr> { P(Expr { kind, span, attrs, id: DUMMY_NODE_ID, tokens: None }) } + pub(crate) fn mk_expr(&self, span: Span, kind: ExprKind) -> P<Expr> { + P(Expr { kind, span, attrs: AttrVec::new(), id: DUMMY_NODE_ID, tokens: None }) + } + pub(super) fn mk_expr_err(&self, span: Span) -> P<Expr> { - self.mk_expr(span, ExprKind::Err, AttrVec::new()) + self.mk_expr(span, ExprKind::Err) } /// Create expression span ensuring the span of the parent node @@ -3268,7 +3136,7 @@ impl<'a> Parser<'a> { fn collect_tokens_for_expr( &mut self, attrs: AttrWrapper, - f: impl FnOnce(&mut Self, Vec<ast::Attribute>) -> PResult<'a, P<Expr>>, + f: impl FnOnce(&mut Self, ast::AttrVec) -> PResult<'a, P<Expr>>, ) -> PResult<'a, P<Expr>> { self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { let res = f(this, attrs)?; @@ -3276,6 +3144,8 @@ impl<'a> Parser<'a> { && this.token.kind == token::Semi { TrailingToken::Semi + } else if this.token.kind == token::Gt { + TrailingToken::Gt } else { // FIXME - pass this through from the place where we know // we need a comma, rather than assuming that `#[attr] expr,` diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 1acfd93d8..fa75670b2 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -2,7 +2,7 @@ use super::{ForceCollect, Parser, TrailingToken}; use rustc_ast::token; use rustc_ast::{ - self as ast, Attribute, GenericBounds, GenericParam, GenericParamKind, WhereClause, + self as ast, AttrVec, GenericBounds, GenericParam, GenericParamKind, TyKind, WhereClause, }; use rustc_errors::{Applicability, PResult}; use rustc_span::symbol::kw; @@ -26,24 +26,54 @@ impl<'a> Parser<'a> { } /// Matches `typaram = IDENT (`?` unbound)? optbounds ( EQ ty )?`. - fn parse_ty_param(&mut self, preceding_attrs: Vec<Attribute>) -> PResult<'a, GenericParam> { + fn parse_ty_param(&mut self, preceding_attrs: AttrVec) -> PResult<'a, GenericParam> { let ident = self.parse_ident()?; // Parse optional colon and param bounds. let mut colon_span = None; let bounds = if self.eat(&token::Colon) { colon_span = Some(self.prev_token.span); + // recover from `impl Trait` in type param bound + if self.token.is_keyword(kw::Impl) { + let impl_span = self.token.span; + let snapshot = self.create_snapshot_for_diagnostic(); + match self.parse_ty() { + Ok(p) => { + if let TyKind::ImplTrait(_, bounds) = &(*p).kind { + let span = impl_span.to(self.token.span.shrink_to_lo()); + let mut err = self.struct_span_err( + span, + "expected trait bound, found `impl Trait` type", + ); + err.span_label(span, "not a trait"); + if let [bound, ..] = &bounds[..] { + err.span_suggestion_verbose( + impl_span.until(bound.span()), + "use the trait bounds directly", + String::new(), + Applicability::MachineApplicable, + ); + } + err.emit(); + return Err(err); + } + } + Err(err) => { + err.cancel(); + } + } + self.restore_snapshot(snapshot); + } self.parse_generic_bounds(colon_span)? } else { Vec::new() }; let default = if self.eat(&token::Eq) { Some(self.parse_ty()?) } else { None }; - Ok(GenericParam { ident, id: ast::DUMMY_NODE_ID, - attrs: preceding_attrs.into(), + attrs: preceding_attrs, bounds, kind: GenericParamKind::Type { default }, is_placeholder: false, @@ -53,7 +83,7 @@ impl<'a> Parser<'a> { pub(crate) fn parse_const_param( &mut self, - preceding_attrs: Vec<Attribute>, + preceding_attrs: AttrVec, ) -> PResult<'a, GenericParam> { let const_span = self.token.span; @@ -68,7 +98,7 @@ impl<'a> Parser<'a> { Ok(GenericParam { ident, id: ast::DUMMY_NODE_ID, - attrs: preceding_attrs.into(), + attrs: preceding_attrs, bounds: Vec::new(), kind: GenericParamKind::Const { ty, kw_span: const_span, default }, is_placeholder: false, @@ -109,7 +139,7 @@ impl<'a> Parser<'a> { Some(ast::GenericParam { ident: lifetime.ident, id: lifetime.id, - attrs: attrs.into(), + attrs, bounds, kind: ast::GenericParamKind::Lifetime, is_placeholder: false, @@ -314,7 +344,6 @@ impl<'a> Parser<'a> { span: lo.to(self.prev_token.span), lhs_ty: ty, rhs_ty, - id: ast::DUMMY_NODE_ID, })) } else { self.maybe_recover_bounds_doubled_colon(&ty)?; diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 567072925..bda301c52 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1,4 +1,6 @@ -use super::diagnostics::{dummy_arg, ConsumeClosingDelim, Error}; +use crate::errors::{DocCommentDoesNotDocumentAnything, UseEmptyBlockNotSemi}; + +use super::diagnostics::{dummy_arg, ConsumeClosingDelim}; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{AttrWrapper, FollowedByType, ForceCollect, Parser, PathStyle, TrailingToken}; @@ -8,12 +10,12 @@ use rustc_ast::token::{self, Delimiter, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::{self as ast, AttrVec, Attribute, DUMMY_NODE_ID}; use rustc_ast::{Async, Const, Defaultness, IsAuto, Mutability, Unsafe, UseTree, UseTreeKind}; -use rustc_ast::{BindingMode, Block, FnDecl, FnSig, Param, SelfKind}; +use rustc_ast::{BindingAnnotation, Block, FnDecl, FnSig, Param, SelfKind}; use rustc_ast::{EnumDef, FieldDef, Generics, TraitRef, Ty, TyKind, Variant, VariantData}; use rustc_ast::{FnHeader, ForeignItem, Path, PathSegment, Visibility, VisibilityKind}; use rustc_ast::{MacArgs, MacCall, MacDelimiter}; use rustc_ast_pretty::pprust; -use rustc_errors::{struct_span_err, Applicability, PResult, StashKey}; +use rustc_errors::{struct_span_err, Applicability, IntoDiagnostic, PResult, StashKey}; use rustc_span::edition::Edition; use rustc_span::lev_distance::lev_distance; use rustc_span::source_map::{self, Span}; @@ -22,7 +24,6 @@ use rustc_span::DUMMY_SP; use std::convert::TryFrom; use std::mem; -use tracing::debug; impl<'a> Parser<'a> { /// Parses a source module as a crate. This is the main entry point for the parser. @@ -32,7 +33,7 @@ impl<'a> Parser<'a> { } /// Parses a `mod <foo> { ... }` or `mod <foo>;` item. - fn parse_item_mod(&mut self, attrs: &mut Vec<Attribute>) -> PResult<'a, ItemInfo> { + fn parse_item_mod(&mut self, attrs: &mut AttrVec) -> PResult<'a, ItemInfo> { let unsafety = self.parse_unsafety(); self.expect_keyword(kw::Mod)?; let id = self.parse_ident()?; @@ -40,9 +41,9 @@ impl<'a> Parser<'a> { ModKind::Unloaded } else { self.expect(&token::OpenDelim(Delimiter::Brace))?; - let (mut inner_attrs, items, inner_span) = + let (inner_attrs, items, inner_span) = self.parse_mod(&token::CloseDelim(Delimiter::Brace))?; - attrs.append(&mut inner_attrs); + attrs.extend(inner_attrs); ModKind::Loaded(items, Inline::Yes, inner_span) }; Ok((id, ItemKind::Mod(unsafety, mod_kind))) @@ -52,7 +53,7 @@ impl<'a> Parser<'a> { pub fn parse_mod( &mut self, term: &TokenKind, - ) -> PResult<'a, (Vec<Attribute>, Vec<P<Item>>, ModSpans)> { + ) -> PResult<'a, (AttrVec, Vec<P<Item>>, ModSpans)> { let lo = self.token.span; let attrs = self.parse_inner_attributes()?; @@ -68,7 +69,12 @@ 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); - err.span_label(self.token.span, "expected item"); + let label = if self.is_kw_followed_by_ident(kw::Let) { + "consider using `const` or `static` instead of `let` for global variables" + } else { + "expected item" + }; + err.span_label(self.token.span, label); return Err(err); } } @@ -129,7 +135,7 @@ impl<'a> Parser<'a> { fn parse_item_common_( &mut self, - mut attrs: Vec<Attribute>, + mut attrs: AttrVec, mac_allowed: bool, attrs_allowed: bool, fn_parse_mode: FnParseMode, @@ -193,7 +199,7 @@ impl<'a> Parser<'a> { /// Parses one of the items allowed by the flags. fn parse_item_kind( &mut self, - attrs: &mut Vec<Attribute>, + attrs: &mut AttrVec, macros_allowed: bool, lo: Span, vis: &Visibility, @@ -271,7 +277,10 @@ impl<'a> Parser<'a> { // MACRO_RULES ITEM self.parse_item_macro_rules(vis, has_bang)? } else if self.isnt_macro_invocation() - && (self.token.is_ident_named(sym::import) || self.token.is_ident_named(sym::using)) + && (self.token.is_ident_named(sym::import) + || self.token.is_ident_named(sym::using) + || self.token.is_ident_named(sym::include) + || self.token.is_ident_named(sym::require)) { return self.recover_import_as_use(); } else if self.isnt_macro_invocation() && vis.kind.is_pub() { @@ -279,7 +288,7 @@ impl<'a> Parser<'a> { return Ok(None); } else if macros_allowed && self.check_path() { // MACRO INVOCATION ITEM - (Ident::empty(), ItemKind::MacCall(self.parse_item_macro(vis)?)) + (Ident::empty(), ItemKind::MacCall(P(self.parse_item_macro(vis)?))) } else { return Ok(None); }; @@ -526,7 +535,7 @@ impl<'a> Parser<'a> { /// ``` fn parse_item_impl( &mut self, - attrs: &mut Vec<Attribute>, + attrs: &mut AttrVec, defaultness: Defaultness, ) -> PResult<'a, ItemInfo> { let unsafety = self.parse_unsafety(); @@ -653,12 +662,20 @@ impl<'a> Parser<'a> { fn parse_item_list<T>( &mut self, - attrs: &mut Vec<Attribute>, + attrs: &mut AttrVec, mut parse_item: impl FnMut(&mut Parser<'a>) -> PResult<'a, Option<Option<T>>>, ) -> PResult<'a, Vec<T>> { let open_brace_span = self.token.span; + + // Recover `impl Ty;` instead of `impl Ty {}` + if self.token == TokenKind::Semi { + self.sess.emit_err(UseEmptyBlockNotSemi { span: self.token.span }); + self.bump(); + return Ok(vec![]); + } + self.expect(&token::OpenDelim(Delimiter::Brace))?; - attrs.append(&mut self.parse_inner_attributes()?); + attrs.extend(self.parse_inner_attributes()?); let mut items = Vec::new(); while !self.eat(&token::CloseDelim(Delimiter::Brace)) { @@ -667,14 +684,55 @@ impl<'a> Parser<'a> { } match parse_item(self) { Ok(None) => { + let is_unnecessary_semicolon = !items.is_empty() + // When the close delim is `)` in a case like the following, `token.kind` is expected to be `token::CloseDelim(Delimiter::Parenthesis)`, + // but the actual `token.kind` is `token::CloseDelim(Delimiter::Bracket)`. + // This is because the `token.kind` of the close delim is treated as the same as + // that of the open delim in `TokenTreesReader::parse_token_tree`, even if the delimiters of them are different. + // Therefore, `token.kind` should not be compared here. + // + // issue-60075.rs + // ``` + // trait T { + // fn qux() -> Option<usize> { + // let _ = if true { + // }); + // ^ this close delim + // Some(4) + // } + // ``` + && self + .span_to_snippet(self.prev_token.span) + .map_or(false, |snippet| snippet == "}") + && self.token.kind == token::Semi; + let semicolon_span = self.token.span; // We have to bail or we'll potentially never make progress. let non_item_span = self.token.span; + let is_let = self.token.is_keyword(kw::Let); + + let mut err = self.struct_span_err(non_item_span, "non-item in item list"); self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes); - self.struct_span_err(non_item_span, "non-item in item list") - .span_label(open_brace_span, "item list starts here") - .span_label(non_item_span, "non-item starts here") - .span_label(self.prev_token.span, "item list ends here") - .emit(); + if is_let { + err.span_suggestion( + non_item_span, + "consider using `const` instead of `let` for associated const", + "const", + Applicability::MachineApplicable, + ); + } else { + err.span_label(open_brace_span, "item list starts here") + .span_label(non_item_span, "non-item starts here") + .span_label(self.prev_token.span, "item list ends here"); + } + if is_unnecessary_semicolon { + err.span_suggestion( + semicolon_span, + "consider removing this semicolon", + "", + Applicability::MaybeIncorrect, + ); + } + err.emit(); break; } Ok(Some(item)) => items.extend(item), @@ -702,8 +760,8 @@ impl<'a> Parser<'a> { ) .span_label(self.token.span, "this doc comment doesn't document anything") .help( - "doc comments must come before what they document, maybe a \ - comment was intended with `//`?", + "doc comments must come before what they document, if a comment was \ + intended use `//`", ) .emit(); self.bump(); @@ -737,7 +795,7 @@ impl<'a> Parser<'a> { } /// Parses `unsafe? auto? trait Foo { ... }` or `trait Foo = Bar;`. - fn parse_item_trait(&mut self, attrs: &mut Vec<Attribute>, lo: Span) -> PResult<'a, ItemInfo> { + fn parse_item_trait(&mut self, attrs: &mut AttrVec, lo: Span) -> PResult<'a, ItemInfo> { let unsafety = self.parse_unsafety(); // Parse optional `auto` prefix. let is_auto = if self.eat_keyword(kw::Auto) { IsAuto::Yes } else { IsAuto::No }; @@ -1023,7 +1081,7 @@ impl<'a> Parser<'a> { /// ``` fn parse_item_foreign_mod( &mut self, - attrs: &mut Vec<Attribute>, + attrs: &mut AttrVec, mut unsafety: Unsafe, ) -> PResult<'a, ItemInfo> { let abi = self.parse_abi(); // ABI? @@ -1124,6 +1182,16 @@ impl<'a> Parser<'a> { Applicability::MaybeIncorrect, ) .emit(); + } else if self.eat_keyword(kw::Let) { + let span = self.prev_token.span; + self.struct_span_err(const_span.to(span), "`const` and `let` are mutually exclusive") + .span_suggestion( + const_span.to(span), + "remove `let`", + "const", + Applicability::MaybeIncorrect, + ) + .emit(); } } @@ -1131,7 +1199,7 @@ impl<'a> Parser<'a> { fn recover_const_impl( &mut self, const_span: Span, - attrs: &mut Vec<Attribute>, + attrs: &mut AttrVec, defaultness: Defaultness, ) -> PResult<'a, ItemInfo> { let impl_span = self.token.span; @@ -1179,10 +1247,11 @@ impl<'a> Parser<'a> { // Parse the type of a `const` or `static mut?` item. // That is, the `":" $ty` fragment. - let ty = if self.eat(&token::Colon) { - self.parse_ty()? - } else { - self.recover_missing_const_type(id, m) + let ty = match (self.eat(&token::Colon), self.check(&token::Eq) | self.check(&token::Semi)) + { + // If there wasn't a `:` or the colon was followed by a `=` or `;` recover a missing type. + (true, false) => self.parse_ty()?, + (colon, _) => self.recover_missing_const_type(colon, m), }; let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None }; @@ -1190,9 +1259,9 @@ impl<'a> Parser<'a> { Ok((id, ty, expr)) } - /// We were supposed to parse `:` but the `:` was missing. + /// We were supposed to parse `":" $ty` but the `:` or the type was missing. /// This means that the type is missing. - fn recover_missing_const_type(&mut self, id: Ident, m: Option<Mutability>) -> P<Ty> { + fn recover_missing_const_type(&mut self, colon_present: bool, m: Option<Mutability>) -> P<Ty> { // Construct the error and stash it away with the hope // that typeck will later enrich the error with a type. let kind = match m { @@ -1200,29 +1269,34 @@ impl<'a> Parser<'a> { Some(Mutability::Not) => "static", None => "const", }; - let mut err = self.struct_span_err(id.span, &format!("missing type for `{kind}` item")); + + let colon = match colon_present { + true => "", + false => ":", + }; + + let span = self.prev_token.span.shrink_to_hi(); + let mut err = self.struct_span_err(span, &format!("missing type for `{kind}` item")); err.span_suggestion( - id.span, + span, "provide a type for the item", - format!("{id}: <type>"), + format!("{colon} <type>"), Applicability::HasPlaceholders, ); - err.stash(id.span, StashKey::ItemNoType); + err.stash(span, StashKey::ItemNoType); // The user intended that the type be inferred, // so treat this as if the user wrote e.g. `const A: _ = expr;`. - P(Ty { kind: TyKind::Infer, span: id.span, id: ast::DUMMY_NODE_ID, tokens: None }) + P(Ty { kind: TyKind::Infer, span, id: ast::DUMMY_NODE_ID, tokens: None }) } /// Parses an enum declaration. fn parse_item_enum(&mut self) -> PResult<'a, ItemInfo> { if self.token.is_keyword(kw::Struct) { - let mut err = self.struct_span_err( - self.prev_token.span.to(self.token.span), - "`enum` and `struct` are mutually exclusive", - ); + let span = self.prev_token.span.to(self.token.span); + let mut err = self.struct_span_err(span, "`enum` and `struct` are mutually exclusive"); err.span_suggestion( - self.prev_token.span.to(self.token.span), + span, "replace `enum struct` with", "enum", Applicability::MachineApplicable, @@ -1239,12 +1313,20 @@ impl<'a> Parser<'a> { let mut generics = self.parse_generics()?; generics.where_clause = self.parse_where_clause()?; - let (variants, _) = self - .parse_delim_comma_seq(Delimiter::Brace, |p| p.parse_enum_variant()) - .map_err(|e| { - self.recover_stmt(); - e - })?; + // Possibly recover `enum Foo;` instead of `enum Foo {}` + let (variants, _) = if self.token == TokenKind::Semi { + self.sess.emit_err(UseEmptyBlockNotSemi { span: self.token.span }); + self.bump(); + (vec![], false) + } else { + self.parse_delim_comma_seq(Delimiter::Brace, |p| p.parse_enum_variant()).map_err( + |mut e| { + e.span_label(id.span, "while parsing this enum"); + self.recover_stmt(); + e + }, + )? + }; let enum_definition = EnumDef { variants: variants.into_iter().flatten().collect() }; Ok((id, ItemKind::Enum(enum_definition, generics))) @@ -1266,7 +1348,8 @@ impl<'a> Parser<'a> { let struct_def = if this.check(&token::OpenDelim(Delimiter::Brace)) { // Parse a struct variant. - let (fields, recovered) = this.parse_record_struct_body("struct", false)?; + let (fields, recovered) = + this.parse_record_struct_body("struct", ident.span, false)?; VariantData::Struct(fields, recovered) } else if this.check(&token::OpenDelim(Delimiter::Parenthesis)) { VariantData::Tuple(this.parse_tuple_struct_body()?, DUMMY_NODE_ID) @@ -1281,7 +1364,7 @@ impl<'a> Parser<'a> { ident, vis, id: DUMMY_NODE_ID, - attrs: variant_attrs.into(), + attrs: variant_attrs, data: struct_def, disr_expr, span: vlo.to(this.prev_token.span), @@ -1320,8 +1403,11 @@ impl<'a> Parser<'a> { VariantData::Unit(DUMMY_NODE_ID) } else { // If we see: `struct Foo<T> where T: Copy { ... }` - let (fields, recovered) = - self.parse_record_struct_body("struct", generics.where_clause.has_where_token)?; + let (fields, recovered) = self.parse_record_struct_body( + "struct", + class_name.span, + generics.where_clause.has_where_token, + )?; VariantData::Struct(fields, recovered) } // No `where` so: `struct Foo<T>;` @@ -1329,8 +1415,11 @@ impl<'a> Parser<'a> { VariantData::Unit(DUMMY_NODE_ID) // Record-style struct definition } else if self.token == token::OpenDelim(Delimiter::Brace) { - let (fields, recovered) = - self.parse_record_struct_body("struct", generics.where_clause.has_where_token)?; + let (fields, recovered) = self.parse_record_struct_body( + "struct", + class_name.span, + generics.where_clause.has_where_token, + )?; VariantData::Struct(fields, recovered) // Tuple-style struct definition with optional where-clause. } else if self.token == token::OpenDelim(Delimiter::Parenthesis) { @@ -1359,12 +1448,18 @@ impl<'a> Parser<'a> { let vdata = if self.token.is_keyword(kw::Where) { generics.where_clause = self.parse_where_clause()?; - let (fields, recovered) = - self.parse_record_struct_body("union", generics.where_clause.has_where_token)?; + let (fields, recovered) = self.parse_record_struct_body( + "union", + class_name.span, + generics.where_clause.has_where_token, + )?; VariantData::Struct(fields, recovered) } else if self.token == token::OpenDelim(Delimiter::Brace) { - let (fields, recovered) = - self.parse_record_struct_body("union", generics.where_clause.has_where_token)?; + let (fields, recovered) = self.parse_record_struct_body( + "union", + class_name.span, + generics.where_clause.has_where_token, + )?; VariantData::Struct(fields, recovered) } else { let token_str = super::token_descr(&self.token); @@ -1380,6 +1475,7 @@ impl<'a> Parser<'a> { fn parse_record_struct_body( &mut self, adt_ty: &str, + ident_span: Span, parsed_where: bool, ) -> PResult<'a, (Vec<FieldDef>, /* recovered */ bool)> { let mut fields = Vec::new(); @@ -1394,6 +1490,7 @@ impl<'a> Parser<'a> { match field { Ok(field) => fields.push(field), Err(mut err) => { + err.span_label(ident_span, format!("while parsing this {adt_ty}")); err.emit(); break; } @@ -1438,7 +1535,7 @@ impl<'a> Parser<'a> { ident: None, id: DUMMY_NODE_ID, ty, - attrs: attrs.into(), + attrs, is_placeholder: false, }, TrailingToken::MaybeComma, @@ -1464,13 +1561,24 @@ impl<'a> Parser<'a> { adt_ty: &str, lo: Span, vis: Visibility, - attrs: Vec<Attribute>, + attrs: AttrVec, ) -> PResult<'a, FieldDef> { let mut seen_comma: bool = false; let a_var = self.parse_name_and_ty(adt_ty, lo, vis, attrs)?; if self.token == token::Comma { seen_comma = true; } + if self.eat(&token::Semi) { + let sp = self.prev_token.span; + let mut err = self.struct_span_err(sp, format!("{adt_ty} fields are separated by `,`")); + err.span_suggestion_short( + sp, + "replace `;` with `,`", + ",", + Applicability::MachineApplicable, + ); + return Err(err); + } match self.token.kind { token::Comma => { self.bump(); @@ -1478,7 +1586,10 @@ impl<'a> Parser<'a> { token::CloseDelim(Delimiter::Brace) => {} token::DocComment(..) => { let previous_span = self.prev_token.span; - let mut err = self.span_err(self.token.span, Error::UselessDocComment); + let mut err = DocCommentDoesNotDocumentAnything { + span: self.token.span, + missing_comma: None, + }; self.bump(); // consume the doc comment let comma_after_doc_seen = self.eat(&token::Comma); // `seen_comma` is always false, because we are inside doc block @@ -1487,18 +1598,13 @@ impl<'a> Parser<'a> { seen_comma = true; } if comma_after_doc_seen || self.token == token::CloseDelim(Delimiter::Brace) { - err.emit(); + self.sess.emit_err(err); } else { if !seen_comma { - let sp = self.sess.source_map().next_point(previous_span); - err.span_suggestion( - sp, - "missing comma here", - ",", - Applicability::MachineApplicable, - ); + let sp = previous_span.shrink_to_hi(); + err.missing_comma = Some(sp); } - return Err(err); + return Err(err.into_diagnostic(&self.sess.span_diagnostic)); } } _ => { @@ -1528,8 +1634,12 @@ impl<'a> Parser<'a> { } } - if self.token.is_ident() { - // This is likely another field; emit the diagnostic and keep going + if self.token.is_ident() + || (self.token.kind == TokenKind::Pound + && (self.look_ahead(1, |t| t == &token::OpenDelim(Delimiter::Bracket)))) + { + // This is likely another field, TokenKind::Pound is used for `#[..]` attribute for next field, + // emit the diagnostic and keep going err.span_suggestion( sp, "try adding a comma", @@ -1590,7 +1700,7 @@ impl<'a> Parser<'a> { adt_ty: &str, lo: Span, vis: Visibility, - attrs: Vec<Attribute>, + attrs: AttrVec, ) -> PResult<'a, FieldDef> { let name = self.parse_field_ident(adt_ty, lo)?; self.expect_field_ty_separator()?; @@ -1624,7 +1734,7 @@ impl<'a> Parser<'a> { vis, id: DUMMY_NODE_ID, ty, - attrs: attrs.into(), + attrs, is_placeholder: false, }) } @@ -1634,6 +1744,7 @@ impl<'a> Parser<'a> { fn parse_field_ident(&mut self, adt_ty: &str, lo: Span) -> PResult<'a, Ident> { let (ident, is_raw) = self.ident_or_err()?; if !is_raw && ident.is_reserved() { + let snapshot = self.create_snapshot_for_diagnostic(); let err = if self.check_fn_front_matter(false) { let inherited_vis = Visibility { span: rustc_span::DUMMY_SP, @@ -1642,20 +1753,63 @@ impl<'a> Parser<'a> { }; // We use `parse_fn` to get a span for the function let fn_parse_mode = FnParseMode { req_name: |_| true, req_body: true }; - if let Err(mut db) = - self.parse_fn(&mut Vec::new(), fn_parse_mode, lo, &inherited_vis) + match self.parse_fn(&mut AttrVec::new(), fn_parse_mode, lo, &inherited_vis) { + Ok(_) => { + let mut err = self.struct_span_err( + lo.to(self.prev_token.span), + &format!("functions are not allowed in {adt_ty} definitions"), + ); + err.help( + "unlike in C++, Java, and C#, functions are declared in `impl` blocks", + ); + err.help("see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information"); + err + } + Err(err) => { + err.cancel(); + self.restore_snapshot(snapshot); + self.expected_ident_found() + } + } + } else if self.eat_keyword(kw::Struct) { + match self.parse_item_struct() { + Ok((ident, _)) => { + let mut err = self.struct_span_err( + lo.with_hi(ident.span.hi()), + &format!("structs are not allowed in {adt_ty} definitions"), + ); + err.help("consider creating a new `struct` definition instead of nesting"); + err + } + Err(err) => { + err.cancel(); + self.restore_snapshot(snapshot); + self.expected_ident_found() + } + } + } else { + let mut err = self.expected_ident_found(); + if self.eat_keyword_noexpect(kw::Let) + && let removal_span = self.prev_token.span.until(self.token.span) + && let Ok(ident) = self.parse_ident_common(false) + // Cancel this error, we don't need it. + .map_err(|err| err.cancel()) + && self.token.kind == TokenKind::Colon { - db.delay_as_bug(); + err.span_suggestion( + removal_span, + "remove this `let` keyword", + String::new(), + Applicability::MachineApplicable, + ); + err.note("the `let` keyword is not allowed in `struct` fields"); + err.note("see <https://doc.rust-lang.org/book/ch05-01-defining-structs.html> for more information"); + err.emit(); + return Ok(ident); + } else { + self.restore_snapshot(snapshot); } - let mut err = self.struct_span_err( - lo.to(self.prev_token.span), - &format!("functions are not allowed in {adt_ty} definitions"), - ); - err.help("unlike in C++, Java, and C#, functions are declared in `impl` blocks"); - err.help("see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information"); err - } else { - self.expected_ident_found() }; return Err(err); } @@ -1919,7 +2073,7 @@ impl<'a> Parser<'a> { /// Parse a function starting from the front matter (`const ...`) to the body `{ ... }` or `;`. fn parse_fn( &mut self, - attrs: &mut Vec<Attribute>, + attrs: &mut AttrVec, fn_parse_mode: FnParseMode, sig_lo: Span, vis: &Visibility, @@ -1942,7 +2096,7 @@ impl<'a> Parser<'a> { /// or e.g. a block when the function is a provided one. fn parse_fn_body( &mut self, - attrs: &mut Vec<Attribute>, + attrs: &mut AttrVec, ident: &Ident, sig_hi: &mut Span, req_body: bool, @@ -1957,7 +2111,7 @@ impl<'a> Parser<'a> { // Include the trailing semicolon in the span of the signature self.expect_semi()?; *sig_hi = self.prev_token.span; - (Vec::new(), None) + (AttrVec::new(), None) } else if self.check(&token::OpenDelim(Delimiter::Brace)) || self.token.is_whole_block() { self.parse_inner_attrs_and_block().map(|(attrs, body)| (attrs, Some(body)))? } else if self.token.kind == token::Eq { @@ -1974,7 +2128,7 @@ impl<'a> Parser<'a> { Applicability::MachineApplicable, ) .emit(); - (Vec::new(), Some(self.mk_block_err(span))) + (AttrVec::new(), Some(self.mk_block_err(span))) } else { let expected = if req_body { &[token::OpenDelim(Delimiter::Brace)][..] @@ -1991,7 +2145,7 @@ impl<'a> Parser<'a> { return Err(err); } } - (Vec::new(), None) + (AttrVec::new(), None) }; attrs.extend(inner_attrs); Ok(body) @@ -2220,7 +2374,7 @@ impl<'a> Parser<'a> { self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { // Possibly parse `self`. Recover if we parsed it and it wasn't allowed here. if let Some(mut param) = this.parse_self_param()? { - param.attrs = attrs.into(); + param.attrs = attrs; let res = if first_param { Ok(param) } else { this.recover_bad_self_param(param) }; return Ok((res?, TrailingToken::None)); } @@ -2249,7 +2403,7 @@ impl<'a> Parser<'a> { (pat, this.parse_ty_for_param()?) } else { debug!("parse_param_general ident_to_pat"); - let parser_snapshot_before_ty = this.clone(); + let parser_snapshot_before_ty = this.create_snapshot_for_diagnostic(); this.eat_incorrect_doc_comment_for_param_type(); let mut ty = this.parse_ty_for_param(); if ty.is_ok() @@ -2263,7 +2417,7 @@ impl<'a> Parser<'a> { match ty { Ok(ty) => { let ident = Ident::new(kw::Empty, this.prev_token.span); - let bm = BindingMode::ByValue(Mutability::Not); + let bm = BindingAnnotation::NONE; let pat = this.mk_pat_ident(ty.span, bm, ident); (pat, ty) } @@ -2272,23 +2426,16 @@ impl<'a> Parser<'a> { // Recover from attempting to parse the argument as a type without pattern. Err(err) => { err.cancel(); - *this = parser_snapshot_before_ty; + this.restore_snapshot(parser_snapshot_before_ty); this.recover_arg_parse()? } } }; - let span = lo.until(this.token.span); + let span = lo.to(this.prev_token.span); Ok(( - Param { - attrs: attrs.into(), - id: ast::DUMMY_NODE_ID, - is_placeholder: false, - pat, - span, - ty, - }, + Param { attrs, id: ast::DUMMY_NODE_ID, is_placeholder: false, pat, span, ty }, TrailingToken::None, )) }) diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 0c523ad22..5fe29062b 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -13,7 +13,6 @@ mod ty; use crate::lexer::UnmatchedBrace; pub use attr_wrapper::AttrWrapper; pub use diagnostics::AttemptLocalParseRecovery; -use diagnostics::Error; pub(crate) use item::FnParseMode; pub use pat::{CommaRecoveryMode, RecoverColon, RecoverComma}; pub use path::PathStyle; @@ -32,16 +31,20 @@ use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; use rustc_errors::PResult; use rustc_errors::{ - struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, FatalError, MultiSpan, + Applicability, DiagnosticBuilder, ErrorGuaranteed, FatalError, IntoDiagnostic, MultiSpan, }; use rustc_session::parse::ParseSess; use rustc_span::source_map::{Span, DUMMY_SP}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use tracing::debug; use std::ops::Range; use std::{cmp, mem, slice}; +use crate::errors::{ + DocCommentDoesNotDocumentAnything, IncorrectVisibilityRestriction, MismatchedClosingDelimiter, + NonStringAbiLiteral, +}; + bitflags::bitflags! { struct Restrictions: u8 { const STMT_EXPR = 1 << 0; @@ -76,6 +79,7 @@ pub enum ForceCollect { pub enum TrailingToken { None, Semi, + Gt, /// If the trailing token is a comma, then capture it /// Otherwise, ignore the trailing token MaybeComma, @@ -111,6 +115,12 @@ macro_rules! maybe_recover_from_interpolated_ty_qpath { }; } +#[derive(Clone, Copy)] +pub enum Recovery { + Allowed, + Forbidden, +} + #[derive(Clone)] pub struct Parser<'a> { pub sess: &'a ParseSess, @@ -148,12 +158,15 @@ pub struct Parser<'a> { /// This allows us to recover when the user forget to add braces around /// multiple statements in the closure body. pub current_closure: Option<ClosureSpans>, + /// Whether the parser is allowed to do recovery. + /// This is disabled when parsing macro arguments, see #103534 + pub recovery: Recovery, } -// This type is used a lot, e.g. it's cloned when matching many declarative macro rules. Make sure +// This type is used a lot, e.g. it's cloned when matching many declarative macro rules with nonterminals. Make sure // it doesn't unintentionally get bigger. #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -rustc_data_structures::static_assert_size!(Parser<'_>, 328); +rustc_data_structures::static_assert_size!(Parser<'_>, 336); /// Stores span information about a closure. #[derive(Clone)] @@ -171,7 +184,7 @@ pub struct ClosureSpans { /// attribute, we parse a nested AST node that has `#[cfg]` or `#[cfg_attr]` /// In this case, we use a `ReplaceRange` to replace the entire inner AST node /// with `FlatToken::AttrTarget`, allowing us to perform eager cfg-expansion -/// on an `AttrAnnotatedTokenStream` +/// on an `AttrTokenStream`. /// /// 2. When we parse an inner attribute while collecting tokens. We /// remove inner attributes from the token stream entirely, and @@ -184,7 +197,7 @@ pub type ReplaceRange = (Range<u32>, Vec<(FlatToken, Spacing)>); /// Controls how we capture tokens. Capturing can be expensive, /// so we try to avoid performing capturing in cases where -/// we will never need an `AttrAnnotatedTokenStream` +/// we will never need an `AttrTokenStream`. #[derive(Copy, Clone)] pub enum Capturing { /// We aren't performing any capturing - this is the default mode. @@ -238,7 +251,7 @@ struct TokenCursor { // the trailing `>>` token. The `break_last_token` // field is used to track this token - it gets // appended to the captured stream when - // we evaluate a `LazyTokenStream` + // we evaluate a `LazyAttrTokenStream`. break_last_token: bool, } @@ -281,7 +294,7 @@ impl TokenCursor { if delim != Delimiter::Invisible { return (Token::new(token::OpenDelim(delim), sp.open), Spacing::Alone); } - // No open delimeter to return; continue on to the next iteration. + // No open delimiter to return; continue on to the next iteration. } }; } else if let Some(frame) = self.stack.pop() { @@ -299,7 +312,10 @@ impl TokenCursor { fn desugar(&mut self, attr_style: AttrStyle, data: Symbol, span: Span) -> (Token, Spacing) { // Searches for the occurrences of `"#*` and returns the minimum number of `#`s - // required to wrap the text. + // required to wrap the text. E.g. + // - `abc d` is wrapped as `r"abc d"` (num_of_hashes = 0) + // - `abc "d"` is wrapped as `r#"abc "d""#` (num_of_hashes = 1) + // - `abc "##d##"` is wrapped as `r###"abc "d""###` (num_of_hashes = 3) let mut num_of_hashes = 0; let mut count = 0; for ch in data.as_str().chars() { @@ -311,6 +327,7 @@ impl TokenCursor { num_of_hashes = cmp::max(num_of_hashes, count); } + // `/// foo` becomes `doc = r"foo". let delim_span = DelimSpan::from_single(span); let body = TokenTree::Delimited( delim_span, @@ -407,24 +424,39 @@ pub enum FollowedByType { No, } -fn token_descr_opt(token: &Token) -> Option<&'static str> { - Some(match token.kind { - _ if token.is_special_ident() => "reserved identifier", - _ if token.is_used_keyword() => "keyword", - _ if token.is_unused_keyword() => "reserved keyword", - token::DocComment(..) => "doc comment", - _ => return None, - }) +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum TokenDescription { + ReservedIdentifier, + Keyword, + ReservedKeyword, + DocComment, } -pub(super) fn token_descr(token: &Token) -> String { - let token_str = pprust::token_to_string(token); - match token_descr_opt(token) { - Some(prefix) => format!("{} `{}`", prefix, token_str), - _ => format!("`{}`", token_str), +impl TokenDescription { + pub fn from_token(token: &Token) -> Option<Self> { + match token.kind { + _ if token.is_special_ident() => Some(TokenDescription::ReservedIdentifier), + _ if token.is_used_keyword() => Some(TokenDescription::Keyword), + _ if token.is_unused_keyword() => Some(TokenDescription::ReservedKeyword), + token::DocComment(..) => Some(TokenDescription::DocComment), + _ => None, + } } } +pub(super) fn token_descr(token: &Token) -> String { + let name = pprust::token_to_string(token).to_string(); + + let kind = TokenDescription::from_token(token).map(|kind| match kind { + TokenDescription::ReservedIdentifier => "reserved identifier", + TokenDescription::Keyword => "keyword", + TokenDescription::ReservedKeyword => "reserved keyword", + TokenDescription::DocComment => "doc comment", + }); + + if let Some(kind) = kind { format!("{} `{}`", kind, name) } else { format!("`{}`", name) } +} + impl<'a> Parser<'a> { pub fn new( sess: &'a ParseSess, @@ -460,6 +492,7 @@ impl<'a> Parser<'a> { inner_attr_ranges: Default::default(), }, current_closure: None, + recovery: Recovery::Allowed, }; // Make parser point to the first token. @@ -468,6 +501,22 @@ impl<'a> Parser<'a> { parser } + pub fn forbid_recovery(mut self) -> Self { + self.recovery = Recovery::Forbidden; + self + } + + /// Whether the parser is allowed to recover from broken code. + /// + /// If this returns false, recovering broken code into valid code (especially if this recovery does lookahead) + /// is not allowed. All recovery done by the parser must be gated behind this check. + /// + /// Technically, this only needs to restrict eager recovery by doing lookahead at more tokens. + /// But making the distinction is very subtle, and simply forbidding all recovery is a lot simpler to uphold. + fn may_recover(&self) -> bool { + matches!(self.recovery, Recovery::Allowed) + } + pub fn unexpected<T>(&mut self) -> PResult<'a, T> { match self.expect_one_of(&[], &[]) { Err(e) => Err(e), @@ -519,9 +568,11 @@ impl<'a> Parser<'a> { fn ident_or_err(&mut self) -> PResult<'a, (Ident, /* is_raw */ bool)> { self.token.ident().ok_or_else(|| match self.prev_token.kind { - TokenKind::DocComment(..) => { - self.span_err(self.prev_token.span, Error::UselessDocComment) + TokenKind::DocComment(..) => DocCommentDoesNotDocumentAnything { + span: self.prev_token.span, + missing_comma: None, } + .into_diagnostic(&self.sess.span_diagnostic), _ => self.expected_ident_found(), }) } @@ -1116,10 +1167,14 @@ impl<'a> Parser<'a> { let (attrs, blk) = self.parse_inner_attrs_and_block()?; let anon_const = AnonConst { id: DUMMY_NODE_ID, - value: self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new()), + value: self.mk_expr(blk.span, ExprKind::Block(blk, None)), }; let blk_span = anon_const.value.span; - Ok(self.mk_expr(span.to(blk_span), ExprKind::ConstBlock(anon_const), AttrVec::from(attrs))) + Ok(self.mk_expr_with_attrs( + span.to(blk_span), + ExprKind::ConstBlock(anon_const), + AttrVec::from(attrs), + )) } /// Parses mutability (`mut` or nothing). @@ -1141,7 +1196,9 @@ impl<'a> Parser<'a> { fn parse_field_name(&mut self) -> PResult<'a, Ident> { if let token::Literal(token::Lit { kind: token::Integer, symbol, suffix }) = self.token.kind { - self.expect_no_suffix(self.token.span, "a tuple index", suffix); + if let Some(suffix) = suffix { + self.expect_no_tuple_index_suffix(self.token.span, suffix); + } self.bump(); Ok(Ident::new(symbol, self.prev_token.span)) } else { @@ -1295,7 +1352,11 @@ impl<'a> Parser<'a> { self.bump(); // `in` let path = self.parse_path(PathStyle::Mod)?; // `path` self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; // `)` - let vis = VisibilityKind::Restricted { path: P(path), id: ast::DUMMY_NODE_ID }; + let vis = VisibilityKind::Restricted { + path: P(path), + id: ast::DUMMY_NODE_ID, + shorthand: false, + }; return Ok(Visibility { span: lo.to(self.prev_token.span), kind: vis, @@ -1308,7 +1369,11 @@ impl<'a> Parser<'a> { self.bump(); // `(` let path = self.parse_path(PathStyle::Mod)?; // `crate`/`super`/`self` self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; // `)` - let vis = VisibilityKind::Restricted { path: P(path), id: ast::DUMMY_NODE_ID }; + let vis = VisibilityKind::Restricted { + path: P(path), + id: ast::DUMMY_NODE_ID, + shorthand: true, + }; return Ok(Visibility { span: lo.to(self.prev_token.span), kind: vis, @@ -1331,23 +1396,8 @@ impl<'a> Parser<'a> { let path = self.parse_path(PathStyle::Mod)?; self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; // `)` - let msg = "incorrect visibility restriction"; - let suggestion = r##"some possible visibility restrictions are: -`pub(crate)`: visible only on the current crate -`pub(super)`: visible only in the current module's parent -`pub(in path::to::module)`: visible only on the specified path"##; - let path_str = pprust::path_to_string(&path); - - struct_span_err!(self.sess.span_diagnostic, path.span, E0704, "{}", msg) - .help(suggestion) - .span_suggestion( - path.span, - &format!("make this visible only to module `{}` with `in`", path_str), - format!("in {}", path_str), - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(IncorrectVisibilityRestriction { span: path.span, inner_str: path_str }); Ok(()) } @@ -1371,16 +1421,9 @@ impl<'a> Parser<'a> { match self.parse_str_lit() { Ok(str_lit) => Some(str_lit), Err(Some(lit)) => match lit.kind { - ast::LitKind::Err(_) => None, + ast::LitKind::Err => None, _ => { - self.struct_span_err(lit.span, "non-string ABI literal") - .span_suggestion( - lit.span, - "specify the ABI with a string literal", - "\"C\"", - Applicability::MaybeIncorrect, - ) - .emit(); + self.sess.emit_err(NonStringAbiLiteral { span: lit.span }); None } }, @@ -1421,25 +1464,18 @@ pub(crate) fn make_unclosed_delims_error( // `None` here means an `Eof` was found. We already emit those errors elsewhere, we add them to // `unmatched_braces` only for error recovery in the `Parser`. let found_delim = unmatched.found_delim?; - let span: MultiSpan = if let Some(sp) = unmatched.unclosed_span { - vec![unmatched.found_span, sp].into() - } else { - unmatched.found_span.into() - }; - let mut err = sess.span_diagnostic.struct_span_err( - span, - &format!( - "mismatched closing delimiter: `{}`", - pprust::token_kind_to_string(&token::CloseDelim(found_delim)), - ), - ); - err.span_label(unmatched.found_span, "mismatched closing delimiter"); - if let Some(sp) = unmatched.candidate_span { - err.span_label(sp, "closing delimiter possibly meant for this"); - } + let mut spans = vec![unmatched.found_span]; if let Some(sp) = unmatched.unclosed_span { - err.span_label(sp, "unclosed delimiter"); - } + spans.push(sp); + }; + let err = MismatchedClosingDelimiter { + spans, + delimiter: pprust::token_kind_to_string(&token::CloseDelim(found_delim)).to_string(), + unmatched: unmatched.found_span, + opening_candidate: unmatched.candidate_span, + unclosed: unmatched.unclosed_span, + } + .into_diagnostic(&sess.span_diagnostic); Some(err) } @@ -1453,11 +1489,11 @@ pub fn emit_unclosed_delims(unclosed_delims: &mut Vec<UnmatchedBrace>, sess: &Pa } } -/// A helper struct used when building an `AttrAnnotatedTokenStream` from -/// a `LazyTokenStream`. Both delimiter and non-delimited tokens +/// A helper struct used when building an `AttrTokenStream` from +/// a `LazyAttrTokenStream`. Both delimiter and non-delimited tokens /// are stored as `FlatToken::Token`. A vector of `FlatToken`s -/// is then 'parsed' to build up an `AttrAnnotatedTokenStream` with nested -/// `AttrAnnotatedTokenTree::Delimited` tokens +/// is then 'parsed' to build up an `AttrTokenStream` with nested +/// `AttrTokenTree::Delimited` tokens. #[derive(Debug, Clone)] pub enum FlatToken { /// A token - this holds both delimiter (e.g. '{' and '}') @@ -1465,11 +1501,11 @@ pub enum FlatToken { Token(Token), /// Holds the `AttributesData` for an AST node. The /// `AttributesData` is inserted directly into the - /// constructed `AttrAnnotatedTokenStream` as - /// an `AttrAnnotatedTokenTree::Attributes` + /// constructed `AttrTokenStream` as + /// an `AttrTokenTree::Attributes`. AttrTarget(AttributesData), /// A special 'empty' token that is ignored during the conversion - /// to an `AttrAnnotatedTokenStream`. This is used to simplify the + /// to an `AttrTokenStream`. This is used to simplify the /// handling of replace ranges. Empty, } diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index e215b6872..103dd8012 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -66,18 +66,18 @@ impl<'a> Parser<'a> { }, NonterminalKind::PatParam { .. } | NonterminalKind::PatWithOr { .. } => { match token.kind { - token::Ident(..) | // box, ref, mut, and other identifiers (can stricten) - token::OpenDelim(Delimiter::Parenthesis) | // tuple pattern - token::OpenDelim(Delimiter::Bracket) | // slice pattern - token::BinOp(token::And) | // reference - token::BinOp(token::Minus) | // negative literal - token::AndAnd | // double reference - token::Literal(..) | // literal - token::DotDot | // range pattern (future compat) - token::DotDotDot | // range pattern (future compat) - token::ModSep | // path - token::Lt | // path (UFCS constant) - token::BinOp(token::Shl) => true, // path (double UFCS) + token::Ident(..) | // box, ref, mut, and other identifiers (can stricten) + token::OpenDelim(Delimiter::Parenthesis) | // tuple pattern + token::OpenDelim(Delimiter::Bracket) | // slice pattern + token::BinOp(token::And) | // reference + token::BinOp(token::Minus) | // negative literal + token::AndAnd | // double reference + token::Literal(..) | // literal + token::DotDot | // range pattern (future compat) + token::DotDotDot | // range pattern (future compat) + token::ModSep | // path + token::Lt | // path (UFCS constant) + token::BinOp(token::Shl) => true, // path (double UFCS) // leading vert `|` or-pattern token::BinOp(token::Or) => matches!(kind, NonterminalKind::PatWithOr {..}), token::Interpolated(ref nt) => may_be_ident(nt), diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index ba77a3958..52c11b4e3 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1,14 +1,16 @@ use super::{ForceCollect, Parser, PathStyle, TrailingToken}; +use crate::errors::RemoveLet; use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor}; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter}; use rustc_ast::{ - self as ast, AttrVec, Attribute, BindingMode, Expr, ExprKind, MacCall, Mutability, Pat, + self as ast, AttrVec, BindingAnnotation, ByRef, Expr, ExprKind, MacCall, Mutability, Pat, PatField, PatKind, Path, QSelf, RangeEnd, RangeSyntax, }; use rustc_ast_pretty::pprust; use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, PResult}; +use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::source_map::{respan, Span, Spanned}; use rustc_span::symbol::{kw, sym, Ident}; @@ -320,7 +322,13 @@ impl<'a> Parser<'a> { maybe_recover_from_interpolated_ty_qpath!(self, true); maybe_whole!(self, NtPat, |x| x); - let lo = self.token.span; + let mut lo = self.token.span; + + if self.token.is_keyword(kw::Let) && self.look_ahead(1, |tok| tok.can_begin_pattern()) { + self.bump(); + self.sess.emit_err(RemoveLet { span: lo }); + lo = self.token.span; + } let pat = if self.check(&token::BinOp(token::And)) || self.token.kind == token::AndAnd { self.parse_pat_deref(expected)? @@ -353,7 +361,7 @@ impl<'a> Parser<'a> { } else if self.eat_keyword(kw::Ref) { // Parse ref ident @ pat / ref mut ident @ pat let mutbl = self.parse_mutability(); - self.parse_pat_ident(BindingMode::ByRef(mutbl))? + self.parse_pat_ident(BindingAnnotation(ByRef::Yes, mutbl))? } else if self.eat_keyword(kw::Box) { self.parse_pat_box()? } else if self.check_inline_const(0) { @@ -369,7 +377,7 @@ impl<'a> Parser<'a> { // Parse `ident @ pat` // This can give false positives and parse nullary enums, // they are dealt with later in resolve. - self.parse_pat_ident(BindingMode::ByValue(Mutability::Not))? + self.parse_pat_ident(BindingAnnotation::NONE)? } else if self.is_start_of_pat_with_path() { // Parse pattern starting with a path let (qself, path) = if self.eat_lt() { @@ -385,7 +393,7 @@ impl<'a> Parser<'a> { if qself.is_none() && self.check(&token::Not) { self.parse_pat_mac_invoc(path)? } else if let Some(form) = self.parse_range_end() { - let begin = self.mk_expr(span, ExprKind::Path(qself, path), AttrVec::new()); + let begin = self.mk_expr(span, ExprKind::Path(qself, path)); self.parse_pat_range_begin_with(begin, form)? } else if self.check(&token::OpenDelim(Delimiter::Brace)) { self.parse_pat_struct(qself, path)? @@ -394,6 +402,25 @@ impl<'a> Parser<'a> { } else { PatKind::Path(qself, path) } + } else if matches!(self.token.kind, token::Lifetime(_)) + // In pattern position, we're totally fine with using "next token isn't colon" + // as a heuristic. We could probably just always try to recover if it's a lifetime, + // because we never have `'a: label {}` in a pattern position anyways, but it does + // keep us from suggesting something like `let 'a: Ty = ..` => `let 'a': Ty = ..` + && !self.look_ahead(1, |token| matches!(token.kind, token::Colon)) + { + // Recover a `'a` as a `'a'` literal + let lt = self.expect_lifetime(); + let lit = self.recover_unclosed_char(lt.ident, |self_| { + let expected = expected.unwrap_or("pattern"); + let msg = + format!("expected {}, found {}", expected, super::token_descr(&self_.token)); + + let mut err = self_.struct_span_err(self_.token.span, &msg); + err.span_label(self_.token.span, format!("expected {}", expected)); + err + }); + PatKind::Lit(self.mk_expr(lo, ExprKind::Lit(lit))) } else { // Try to parse everything else as literal with optional minus match self.parse_literal_maybe_minus() { @@ -578,7 +605,8 @@ impl<'a> Parser<'a> { let mut pat = self.parse_pat_no_top_alt(Some("identifier"))?; // If we don't have `mut $ident (@ pat)?`, error. - if let PatKind::Ident(BindingMode::ByValue(m @ Mutability::Not), ..) = &mut pat.kind { + if let PatKind::Ident(BindingAnnotation(ByRef::No, m @ Mutability::Not), ..) = &mut pat.kind + { // Don't recurse into the subpattern. // `mut` on the outer binding doesn't affect the inner bindings. *m = Mutability::Mut; @@ -604,7 +632,7 @@ impl<'a> Parser<'a> { ) .emit(); - self.parse_pat_ident(BindingMode::ByRef(Mutability::Mut)) + self.parse_pat_ident(BindingAnnotation::REF_MUT) } /// Turn all by-value immutable bindings in a pattern into mutable bindings. @@ -613,7 +641,8 @@ impl<'a> Parser<'a> { struct AddMut(bool); impl MutVisitor for AddMut { fn visit_pat(&mut self, pat: &mut P<Pat>) { - if let PatKind::Ident(BindingMode::ByValue(m @ Mutability::Not), ..) = &mut pat.kind + if let PatKind::Ident(BindingAnnotation(ByRef::No, m @ Mutability::Not), ..) = + &mut pat.kind { self.0 = true; *m = Mutability::Mut; @@ -665,7 +694,7 @@ impl<'a> Parser<'a> { fn parse_pat_mac_invoc(&mut self, path: Path) -> PResult<'a, PatKind> { self.bump(); let args = self.parse_mac_args()?; - let mac = MacCall { path, args, prior_type_ascription: self.last_type_ascription }; + let mac = P(MacCall { path, args, prior_type_ascription: self.last_type_ascription }); Ok(PatKind::MacCall(mac)) } @@ -684,7 +713,7 @@ impl<'a> Parser<'a> { let sp = self.sess.source_map().start_point(self.token.span); if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&sp) { - self.sess.expr_parentheses_needed(&mut err, *sp); + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); } Err(err) @@ -767,7 +796,6 @@ impl<'a> Parser<'a> { /// expression syntax `...expr` for splatting in expressions. fn parse_pat_range_to(&mut self, mut re: Spanned<RangeEnd>) -> PResult<'a, PatKind> { let end = self.parse_pat_range_end()?; - self.sess.gated_spans.gate(sym::half_open_range_patterns, re.span.to(self.prev_token.span)); if let RangeEnd::Included(ref mut syn @ RangeSyntax::DotDotDot) = &mut re.node { *syn = RangeSyntax::DotDotEq; self.struct_span_err(re.span, "range-to patterns with `...` are not allowed") @@ -790,6 +818,7 @@ impl<'a> Parser<'a> { || t.kind == token::Dot // e.g. `.5` for recovery; || t.can_begin_literal_maybe_minus() // e.g. `42`. || t.is_whole_expr() + || t.is_lifetime() // recover `'a` instead of `'a'` }) } @@ -807,7 +836,7 @@ impl<'a> Parser<'a> { (None, self.parse_path(PathStyle::Expr)?) }; let hi = self.prev_token.span; - Ok(self.mk_expr(lo.to(hi), ExprKind::Path(qself, path), AttrVec::new())) + Ok(self.mk_expr(lo.to(hi), ExprKind::Path(qself, path))) } else { self.parse_literal_maybe_minus() } @@ -838,7 +867,7 @@ impl<'a> Parser<'a> { /// Parses `ident` or `ident @ pat`. /// Used by the copy foo and ref foo patterns to give a good /// error message when parsing mistakes like `ref foo(a, b)`. - fn parse_pat_ident(&mut self, binding_mode: BindingMode) -> PResult<'a, PatKind> { + fn parse_pat_ident(&mut self, binding_annotation: BindingAnnotation) -> PResult<'a, PatKind> { let ident = self.parse_ident()?; let sub = if self.eat(&token::At) { Some(self.parse_pat_no_top_alt(Some("binding pattern"))?) @@ -856,7 +885,7 @@ impl<'a> Parser<'a> { .struct_span_err(self.prev_token.span, "expected identifier, found enum pattern")); } - Ok(PatKind::Ident(binding_mode, ident, sub)) + Ok(PatKind::Ident(binding_annotation, ident, sub)) } /// Parse a struct ("record") pattern (e.g. `Foo { ... }` or `Foo::Bar { ... }`). @@ -936,11 +965,7 @@ impl<'a> Parser<'a> { None }; - Ok(PatKind::Ident( - BindingMode::ByValue(Mutability::Not), - Ident::new(kw::Box, box_span), - sub, - )) + Ok(PatKind::Ident(BindingAnnotation::NONE, Ident::new(kw::Box, box_span), sub)) } else { let pat = self.parse_pat_with_range_pat(false, None)?; self.sess.gated_spans.gate(sym::box_patterns, box_span.to(self.prev_token.span)); @@ -1093,7 +1118,7 @@ impl<'a> Parser<'a> { .emit(); } - fn parse_pat_field(&mut self, lo: Span, attrs: Vec<Attribute>) -> PResult<'a, PatField> { + fn parse_pat_field(&mut self, lo: Span, attrs: AttrVec) -> PResult<'a, PatField> { // Check if a colon exists one ahead. This means we're parsing a fieldname. let hi; let (subpat, fieldname, is_shorthand) = if self.look_ahead(1, |t| t == &token::Colon) { @@ -1117,14 +1142,12 @@ impl<'a> Parser<'a> { let fieldname = self.parse_field_name()?; hi = self.prev_token.span; - let bind_type = match (is_ref, is_mut) { - (true, true) => BindingMode::ByRef(Mutability::Mut), - (true, false) => BindingMode::ByRef(Mutability::Not), - (false, true) => BindingMode::ByValue(Mutability::Mut), - (false, false) => BindingMode::ByValue(Mutability::Not), + let mutability = match is_mut { + false => Mutability::Not, + true => Mutability::Mut, }; - - let fieldpat = self.mk_pat_ident(boxed_span.to(hi), bind_type, fieldname); + let ann = BindingAnnotation(ByRef::from(is_ref), mutability); + let fieldpat = self.mk_pat_ident(boxed_span.to(hi), ann, fieldname); let subpat = if is_box { self.mk_pat(lo.to(hi), PatKind::Box(fieldpat)) } else { fieldpat }; (subpat, fieldname, true) @@ -1134,15 +1157,15 @@ impl<'a> Parser<'a> { ident: fieldname, pat: subpat, is_shorthand, - attrs: attrs.into(), + attrs, id: ast::DUMMY_NODE_ID, span: lo.to(hi), is_placeholder: false, }) } - pub(super) fn mk_pat_ident(&self, span: Span, bm: BindingMode, ident: Ident) -> P<Pat> { - self.mk_pat(span, PatKind::Ident(bm, ident, None)) + pub(super) fn mk_pat_ident(&self, span: Span, ann: BindingAnnotation, ident: Ident) -> P<Pat> { + self.mk_pat(span, PatKind::Ident(ann, ident, None)) } pub(super) fn mk_pat(&self, span: Span, kind: PatKind) -> P<Pat> { diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 5cf1758c3..fdc1af27f 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -13,7 +13,6 @@ use rustc_span::source_map::{BytePos, Span}; use rustc_span::symbol::{kw, sym, Ident}; use std::mem; -use tracing::debug; /// Specifies how to parse a path. #[derive(Copy, Clone, PartialEq)] @@ -527,7 +526,7 @@ impl<'a> Parser<'a> { Ok(ident_gen_args) => ident_gen_args, Err(()) => return Ok(Some(AngleBracketedArg::Arg(arg))), }; - if binder.is_some() { + if binder { // FIXME(compiler-errors): this could be improved by suggesting lifting // this up to the trait, at least before this becomes real syntax. // e.g. `Trait<for<'a> Assoc = Ty>` -> `for<'a> Trait<Assoc = Ty>` @@ -652,12 +651,7 @@ impl<'a> Parser<'a> { pub(super) fn parse_const_arg(&mut self) -> PResult<'a, AnonConst> { // Parse const argument. let value = if let token::OpenDelim(Delimiter::Brace) = self.token.kind { - self.parse_block_expr( - None, - self.token.span, - BlockCheckMode::Default, - ast::AttrVec::new(), - )? + self.parse_block_expr(None, self.token.span, BlockCheckMode::Default)? } else { self.handle_unambiguous_unbraced_const_arg()? }; @@ -725,28 +719,24 @@ impl<'a> Parser<'a> { /// Given a arg inside of generics, we try to destructure it as if it were the LHS in /// `LHS = ...`, i.e. an associated type binding. - /// This returns (optionally, if they are present) any `for<'a, 'b>` binder args, the + /// This returns a bool indicating if there are any `for<'a, 'b>` binder args, the /// identifier, and any GAT arguments. fn get_ident_from_generic_arg( &self, gen_arg: &GenericArg, - ) -> Result<(Option<Vec<ast::GenericParam>>, Ident, Option<GenericArgs>), ()> { + ) -> Result<(bool, Ident, Option<GenericArgs>), ()> { if let GenericArg::Type(ty) = gen_arg { if let ast::TyKind::Path(qself, path) = &ty.kind && qself.is_none() && let [seg] = path.segments.as_slice() { - return Ok((None, seg.ident, seg.args.as_deref().cloned())); + return Ok((false, seg.ident, seg.args.as_deref().cloned())); } else if let ast::TyKind::TraitObject(bounds, ast::TraitObjectSyntax::None) = &ty.kind && let [ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifier::None)] = bounds.as_slice() && let [seg] = trait_ref.trait_ref.path.segments.as_slice() { - return Ok(( - Some(trait_ref.bound_generic_params.clone()), - seg.ident, - seg.args.as_deref().cloned(), - )); + return Ok((true, seg.ident, seg.args.as_deref().cloned())); } } Err(()) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 51bd9d2d3..12753c678 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -1,5 +1,5 @@ -use super::attr::DEFAULT_INNER_ATTR_FORBIDDEN; -use super::diagnostics::{AttemptLocalParseRecovery, Error}; +use super::attr::InnerAttrForbiddenReason; +use super::diagnostics::AttemptLocalParseRecovery; use super::expr::LhsExpr; use super::pat::RecoverComma; use super::path::PathStyle; @@ -7,6 +7,12 @@ use super::TrailingToken; use super::{ AttrWrapper, BlockMode, FnParseMode, ForceCollect, Parser, Restrictions, SemiColonMode, }; +use crate::errors::{ + AssignmentElseNotAllowed, CompoundAssignmentExpressionInLet, ConstLetMutuallyExclusive, + DocCommentDoesNotDocumentAnything, ExpectedStatementAfterOuterAttr, InvalidCurlyInLetElse, + InvalidExpressionInLetElse, InvalidVariableDeclaration, InvalidVariableDeclarationSub, + WrapExpressionInParentheses, +}; use crate::maybe_whole; use rustc_ast as ast; @@ -34,7 +40,7 @@ impl<'a> Parser<'a> { })) } - /// If `force_capture` is true, forces collection of tokens regardless of whether + /// If `force_collect` is [`ForceCollect::Yes`], forces collection of tokens regardless of whether /// or not we have attributes pub(crate) fn parse_stmt_without_recovery( &mut self, @@ -55,18 +61,25 @@ impl<'a> Parser<'a> { return Ok(Some(stmt.into_inner())); } + if self.token.is_keyword(kw::Mut) && self.is_keyword_ahead(1, &[kw::Let]) { + self.bump(); + let mut_let_span = lo.to(self.token.span); + self.sess.emit_err(InvalidVariableDeclaration { + span: mut_let_span, + sub: InvalidVariableDeclarationSub::SwitchMutLetOrder(mut_let_span), + }); + } + Ok(Some(if self.token.is_keyword(kw::Let) { self.parse_local_mk(lo, attrs, capture_semi, force_collect)? } else if self.is_kw_followed_by_ident(kw::Mut) { - self.recover_stmt_local(lo, attrs, "missing keyword", "let mut")? + self.recover_stmt_local(lo, attrs, InvalidVariableDeclarationSub::MissingLet)? } else if self.is_kw_followed_by_ident(kw::Auto) { self.bump(); // `auto` - let msg = "write `let` instead of `auto` to introduce a new variable"; - self.recover_stmt_local(lo, attrs, msg, "let")? + self.recover_stmt_local(lo, attrs, InvalidVariableDeclarationSub::UseLetNotAuto)? } else if self.is_kw_followed_by_ident(sym::var) { self.bump(); // `var` - let msg = "write `let` instead of `var` to introduce a new variable"; - self.recover_stmt_local(lo, attrs, msg, "let")? + self.recover_stmt_local(lo, attrs, InvalidVariableDeclarationSub::UseLetNotVar)? } else if self.check_path() && !self.token.is_qpath_start() && !self.is_path_start_item() { // We have avoided contextual keywords like `union`, items with `crate` visibility, // or `auto trait` items. We aim to parse an arbitrary path `a::b` but not something @@ -103,11 +116,7 @@ impl<'a> Parser<'a> { let bl = self.parse_block()?; // Destructuring assignment ... else. // This is not allowed, but point it out in a nice way. - let mut err = self.struct_span_err( - e.span.to(bl.span), - "<assignment> ... else { ... } is not allowed", - ); - err.emit(); + self.sess.emit_err(AssignmentElseNotAllowed { span: e.span.to(bl.span) }); } self.mk_stmt(lo.to(e.span), StmtKind::Expr(e)) } else { @@ -121,7 +130,7 @@ impl<'a> Parser<'a> { let path = this.parse_path(PathStyle::Expr)?; if this.eat(&token::Not) { - let stmt_mac = this.parse_stmt_mac(lo, attrs.into(), path)?; + let stmt_mac = this.parse_stmt_mac(lo, attrs, path)?; if this.token == token::Semi { return Ok((stmt_mac, TrailingToken::Semi)); } else { @@ -130,10 +139,10 @@ impl<'a> Parser<'a> { } let expr = if this.eat(&token::OpenDelim(Delimiter::Brace)) { - this.parse_struct_expr(None, path, AttrVec::new(), true)? + this.parse_struct_expr(None, path, true)? } else { let hi = this.prev_token.span; - this.mk_expr(lo.to(hi), ExprKind::Path(None, path), AttrVec::new()) + this.mk_expr(lo.to(hi), ExprKind::Path(None, path)) }; let expr = this.with_res(Restrictions::STMT_EXPR, |this| { @@ -168,7 +177,7 @@ impl<'a> Parser<'a> { None => unreachable!(), }; - let mac = MacCall { path, args, prior_type_ascription: self.last_type_ascription }; + let mac = P(MacCall { path, args, prior_type_ascription: self.last_type_ascription }); let kind = if (style == MacStmtStyle::Braces && self.token != token::Dot @@ -179,9 +188,9 @@ impl<'a> Parser<'a> { StmtKind::MacCall(P(MacCallStmt { mac, style, attrs, tokens: None })) } else { // Since none of the above applied, this is an expression statement macro. - let e = self.mk_expr(lo.to(hi), ExprKind::MacCall(mac), AttrVec::new()); + let e = self.mk_expr(lo.to(hi), ExprKind::MacCall(mac)); let e = self.maybe_recover_from_bad_qpath(e)?; - let e = self.parse_dot_or_call_expr_with(e, lo, attrs.into())?; + let e = self.parse_dot_or_call_expr_with(e, lo, attrs)?; let e = self.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(e))?; StmtKind::Expr(e) }; @@ -193,9 +202,12 @@ impl<'a> Parser<'a> { fn error_outer_attrs(&self, attrs: &[Attribute]) { if let [.., last] = attrs { if last.is_doc_comment() { - self.span_err(last.span, Error::UselessDocComment).emit(); + self.sess.emit_err(DocCommentDoesNotDocumentAnything { + span: last.span, + missing_comma: None, + }); } else if attrs.iter().any(|a| a.style == AttrStyle::Outer) { - self.struct_span_err(last.span, "expected statement after outer attribute").emit(); + self.sess.emit_err(ExpectedStatementAfterOuterAttr { span: last.span }); } } } @@ -204,13 +216,10 @@ impl<'a> Parser<'a> { &mut self, lo: Span, attrs: AttrWrapper, - msg: &str, - sugg: &str, + subdiagnostic: fn(Span) -> InvalidVariableDeclarationSub, ) -> PResult<'a, Stmt> { let stmt = self.recover_local_after_let(lo, attrs)?; - self.struct_span_err(lo, "invalid variable declaration") - .span_suggestion(lo, msg, sugg, Applicability::MachineApplicable) - .emit(); + self.sess.emit_err(InvalidVariableDeclaration { span: lo, sub: subdiagnostic(lo) }); Ok(stmt) } @@ -223,7 +232,7 @@ impl<'a> Parser<'a> { ) -> PResult<'a, Stmt> { self.collect_tokens_trailing_token(attrs, force_collect, |this, attrs| { this.expect_keyword(kw::Let)?; - let local = this.parse_local(attrs.into())?; + let local = this.parse_local(attrs)?; let trailing = if capture_semi && this.token.kind == token::Semi { TrailingToken::Semi } else { @@ -235,7 +244,7 @@ impl<'a> Parser<'a> { fn recover_local_after_let(&mut self, lo: Span, attrs: AttrWrapper) -> PResult<'a, Stmt> { self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { - let local = this.parse_local(attrs.into())?; + let local = this.parse_local(attrs)?; // FIXME - maybe capture semicolon in recovery? Ok(( this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Local(local)), @@ -247,6 +256,12 @@ impl<'a> Parser<'a> { /// Parses a local variable declaration. fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> { let lo = self.prev_token.span; + + if self.token.is_keyword(kw::Const) && self.look_ahead(1, |t| t.is_ident()) { + self.sess.emit_err(ConstLetMutuallyExclusive { span: lo.to(self.token.span) }); + self.bump(); + } + let (pat, colon) = self.parse_pat_before_ty(None, RecoverComma::Yes, "`let` bindings")?; let (err, ty) = if colon { @@ -341,44 +356,27 @@ impl<'a> Parser<'a> { fn check_let_else_init_bool_expr(&self, init: &ast::Expr) { if let ast::ExprKind::Binary(op, ..) = init.kind { if op.node.lazy() { - let suggs = vec![ - (init.span.shrink_to_lo(), "(".to_string()), - (init.span.shrink_to_hi(), ")".to_string()), - ]; - self.struct_span_err( - init.span, - &format!( - "a `{}` expression cannot be directly assigned in `let...else`", - op.node.to_string() - ), - ) - .multipart_suggestion( - "wrap the expression in parentheses", - suggs, - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(InvalidExpressionInLetElse { + span: init.span, + operator: op.node.to_string(), + sugg: WrapExpressionInParentheses { + left: init.span.shrink_to_lo(), + right: init.span.shrink_to_hi(), + }, + }); } } } fn check_let_else_init_trailing_brace(&self, init: &ast::Expr) { if let Some(trailing) = classify::expr_trailing_brace(init) { - let err_span = trailing.span.with_lo(trailing.span.hi() - BytePos(1)); - let suggs = vec![ - (trailing.span.shrink_to_lo(), "(".to_string()), - (trailing.span.shrink_to_hi(), ")".to_string()), - ]; - self.struct_span_err( - err_span, - "right curly brace `}` before `else` in a `let...else` statement not allowed", - ) - .multipart_suggestion( - "try wrapping the expression in parentheses", - suggs, - Applicability::MachineApplicable, - ) - .emit(); + self.sess.emit_err(InvalidCurlyInLetElse { + span: trailing.span.with_lo(trailing.span.hi() - BytePos(1)), + sugg: WrapExpressionInParentheses { + left: trailing.span.shrink_to_lo(), + right: trailing.span.shrink_to_hi(), + }, + }); } } @@ -387,18 +385,7 @@ impl<'a> Parser<'a> { let eq_consumed = match self.token.kind { token::BinOpEq(..) => { // Recover `let x <op>= 1` as `let x = 1` - self.struct_span_err( - self.token.span, - "can't reassign to an uninitialized variable", - ) - .span_suggestion_short( - self.token.span, - "initialize the variable", - "=", - Applicability::MaybeIncorrect, - ) - .help("if you meant to overwrite, remove the `let` binding") - .emit(); + self.sess.emit_err(CompoundAssignmentExpressionInLet { span: self.token.span }); self.bump(); true } @@ -412,7 +399,12 @@ impl<'a> Parser<'a> { pub(super) fn parse_block(&mut self) -> PResult<'a, P<Block>> { let (attrs, block) = self.parse_inner_attrs_and_block()?; if let [.., last] = &*attrs { - self.error_on_forbidden_inner_attr(last.span, DEFAULT_INNER_ATTR_FORBIDDEN); + self.error_on_forbidden_inner_attr( + last.span, + super::attr::InnerAttrPolicy::Forbidden(Some( + InnerAttrForbiddenReason::InCodeBlock, + )), + ); } Ok(block) } @@ -487,9 +479,7 @@ impl<'a> Parser<'a> { } /// Parses a block. Inner attributes are allowed. - pub(super) fn parse_inner_attrs_and_block( - &mut self, - ) -> PResult<'a, (Vec<Attribute>, P<Block>)> { + pub(super) fn parse_inner_attrs_and_block(&mut self) -> PResult<'a, (AttrVec, P<Block>)> { self.parse_block_common(self.token.span, BlockCheckMode::Default) } @@ -498,8 +488,8 @@ impl<'a> Parser<'a> { &mut self, lo: Span, blk_mode: BlockCheckMode, - ) -> PResult<'a, (Vec<Attribute>, P<Block>)> { - maybe_whole!(self, NtBlock, |x| (Vec::new(), x)); + ) -> PResult<'a, (AttrVec, P<Block>)> { + maybe_whole!(self, NtBlock, |x| (AttrVec::new(), x)); self.maybe_recover_unexpected_block_label(); if !self.eat(&token::OpenDelim(Delimiter::Brace)) { @@ -563,39 +553,46 @@ impl<'a> Parser<'a> { match stmt.kind { // Expression without semicolon. StmtKind::Expr(ref mut expr) - if self.token != token::Eof && classify::expr_requires_semi_to_be_stmt(expr) => - { + if self.token != token::Eof && classify::expr_requires_semi_to_be_stmt(expr) => { // Just check for errors and recover; do not eat semicolon yet. - if let Err(mut e) = - self.expect_one_of(&[], &[token::Semi, token::CloseDelim(Delimiter::Brace)]) - { - if let TokenKind::DocComment(..) = self.token.kind { - if let Ok(snippet) = self.span_to_snippet(self.token.span) { - let sp = self.token.span; - let marker = &snippet[..3]; - let (comment_marker, doc_comment_marker) = marker.split_at(2); - - e.span_suggestion( - sp.with_hi(sp.lo() + BytePos(marker.len() as u32)), - &format!( - "add a space before `{}` to use a regular comment", - doc_comment_marker, - ), - format!("{} {}", comment_marker, doc_comment_marker), - Applicability::MaybeIncorrect, - ); + // `expect_one_of` returns PResult<'a, bool /* recovered */> + let replace_with_err = + match self.expect_one_of(&[], &[token::Semi, token::CloseDelim(Delimiter::Brace)]) { + // Recover from parser, skip type error to avoid extra errors. + Ok(true) => true, + Err(mut e) => { + if let TokenKind::DocComment(..) = self.token.kind && + let Ok(snippet) = self.span_to_snippet(self.token.span) { + let sp = self.token.span; + let marker = &snippet[..3]; + let (comment_marker, doc_comment_marker) = marker.split_at(2); + + e.span_suggestion( + sp.with_hi(sp.lo() + BytePos(marker.len() as u32)), + &format!( + "add a space before `{}` to use a regular comment", + doc_comment_marker, + ), + format!("{} {}", comment_marker, doc_comment_marker), + Applicability::MaybeIncorrect, + ); } - } - if let Err(mut e) = - self.check_mistyped_turbofish_with_multiple_type_params(e, expr) - { - if recover.no() { - return Err(e); + + if let Err(mut e) = + self.check_mistyped_turbofish_with_multiple_type_params(e, expr) + { + if recover.no() { + return Err(e); + } + e.emit(); + self.recover_stmt(); } - e.emit(); - self.recover_stmt(); + true } - // Don't complain about type errors in body tail after parse error (#57383). + _ => false + }; + if replace_with_err { + // We already emitted an error, so don't emit another type error let sp = expr.span.to(self.prev_token.span); *expr = self.mk_expr_err(sp); } diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 31b40a83e..2a8512acf 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -397,10 +397,13 @@ impl<'a> Parser<'a> { fn parse_ty_ptr(&mut self) -> PResult<'a, TyKind> { let mutbl = self.parse_const_or_mut().unwrap_or_else(|| { let span = self.prev_token.span; - let msg = "expected mut or const in raw pointer type"; - self.struct_span_err(span, msg) - .span_label(span, msg) - .help("use `*mut T` or `*const T` as appropriate") + self.struct_span_err(span, "expected `mut` or `const` keyword in raw pointer type") + .span_suggestions( + span.shrink_to_hi(), + "add `mut` or `const` here", + ["mut ".to_string(), "const ".to_string()].into_iter(), + Applicability::HasPlaceholders, + ) .emit(); Mutability::Not }); @@ -567,7 +570,8 @@ impl<'a> Parser<'a> { self.check_keyword(kw::Dyn) && (!self.token.uninterpolated_span().rust_2015() || self.look_ahead(1, |t| { - t.can_begin_bound() && !can_continue_type_after_non_fn_ident(t) + (t.can_begin_bound() || t.kind == TokenKind::BinOp(token::Star)) + && !can_continue_type_after_non_fn_ident(t) })) } @@ -576,10 +580,18 @@ impl<'a> Parser<'a> { /// Note that this does *not* parse bare trait objects. fn parse_dyn_ty(&mut self, impl_dyn_multi: &mut bool) -> PResult<'a, TyKind> { self.bump(); // `dyn` + + // parse dyn* types + let syntax = if self.eat(&TokenKind::BinOp(token::Star)) { + TraitObjectSyntax::DynStar + } else { + TraitObjectSyntax::Dyn + }; + // Always parse bounds greedily for better error recovery. let bounds = self.parse_generic_bounds(None)?; *impl_dyn_multi = bounds.len() > 1 || self.prev_token.kind == TokenKind::BinOp(token::Plus); - Ok(TyKind::TraitObject(bounds, TraitObjectSyntax::Dyn)) + Ok(TyKind::TraitObject(bounds, syntax)) } /// Parses a type starting with a path. @@ -598,11 +610,11 @@ impl<'a> Parser<'a> { let path = self.parse_path_inner(PathStyle::Type, ty_generics)?; if self.eat(&token::Not) { // Macro invocation in type position - Ok(TyKind::MacCall(MacCall { + Ok(TyKind::MacCall(P(MacCall { path, args: self.parse_mac_args()?, prior_type_ascription: self.last_type_ascription, - })) + }))) } else if allow_plus == AllowPlus::Yes && self.check_plus() { // `Trait1 + Trait2 + 'a` self.parse_remaining_bounds_path(Vec::new(), path, lo, true) @@ -640,7 +652,13 @@ impl<'a> Parser<'a> { let mut bounds = Vec::new(); let mut negative_bounds = Vec::new(); - while self.can_begin_bound() || self.token.is_keyword(kw::Dyn) { + while self.can_begin_bound() + // Continue even if we find a keyword. + // This is necessary for error recover on, for example, `impl fn()`. + // + // The only keyword that can go after generic bounds is `where`, so stop if it's it. + || (self.token.is_reserved_ident() && !self.token.is_keyword(kw::Where)) + { if self.token.is_keyword(kw::Dyn) { // Account for `&dyn Trait + dyn Other`. self.struct_span_err(self.token.span, "invalid `dyn` keyword") @@ -804,6 +822,20 @@ impl<'a> Parser<'a> { let span = tilde.to(self.prev_token.span); self.sess.gated_spans.gate(sym::const_trait_impl, span); Some(span) + } else if self.eat_keyword(kw::Const) { + let span = self.prev_token.span; + self.sess.gated_spans.gate(sym::const_trait_impl, span); + + self.struct_span_err(span, "const bounds must start with `~`") + .span_suggestion( + span.shrink_to_lo(), + "add `~`", + "~", + Applicability::MachineApplicable, + ) + .emit(); + + Some(span) } else { None }; |