| use rustc_ast as ast; | |
| use rustc_ast::token::{self, MetaVarKind}; | |
| use rustc_ast::tokenstream::{ParserRange, WithTokens}; | |
| use rustc_ast::{Attribute, attr}; | |
| use rustc_errors::codes::*; | |
| use rustc_errors::{Diag, PResult, msg}; | |
| use rustc_span::{BytePos, Span}; | |
| use thin_vec::ThinVec; | |
| use tracing::debug; | |
| use super::{ | |
| AllowConstBlockItems, AttrWrapper, Capturing, FnParseMode, ForceCollect, Parser, PathStyle, | |
| Trailing, UsePreAttrPos, | |
| }; | |
| use crate::parser::FnContext; | |
| use crate::{diagnostics, exp}; | |
| // Public for rustfmt usage | |
| #[derive(Debug)] | |
| pub enum InnerAttrPolicy { | |
| Permitted, | |
| Forbidden(Option<InnerAttrForbiddenReason>), | |
| } | |
| #[derive(Clone, Copy, Debug)] | |
| pub enum InnerAttrForbiddenReason { | |
| InCodeBlock, | |
| AfterOuterDocComment { prev_doc_comment_span: Span }, | |
| AfterOuterAttribute { prev_outer_attr_sp: Span }, | |
| } | |
| enum OuterAttributeType { | |
| DocComment, | |
| DocBlockComment, | |
| Attribute, | |
| } | |
| 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 = ast::AttrVec::new(); | |
| let mut just_parsed_doc_comment = false; | |
| let start_pos = self.num_bump_calls; | |
| loop { | |
| let attr = if self.check(exp!(Pound)) { | |
| let prev_outer_attr_sp = outer_attrs.last().map(|attr: &Attribute| attr.span); | |
| let inner_error_reason = if just_parsed_doc_comment { | |
| Some(InnerAttrForbiddenReason::AfterOuterDocComment { | |
| prev_doc_comment_span: prev_outer_attr_sp.unwrap(), | |
| }) | |
| } else { | |
| prev_outer_attr_sp.map(|prev_outer_attr_sp| { | |
| InnerAttrForbiddenReason::AfterOuterAttribute { prev_outer_attr_sp } | |
| }) | |
| }; | |
| 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 { | |
| if attr_style != ast::AttrStyle::Outer { | |
| let span = self.token.span; | |
| let mut err = | |
| self.dcx().struct_span_err(span, msg!("expected outer doc comment")); | |
| err.code(E0753); | |
| if let Some(replacement_span) = self.annotate_following_item_if_applicable( | |
| &mut err, | |
| span, | |
| match comment_kind { | |
| token::CommentKind::Line => OuterAttributeType::DocComment, | |
| token::CommentKind::Block => OuterAttributeType::DocBlockComment, | |
| }, | |
| true, | |
| ) { | |
| err.note(msg!( | |
| "inner doc comments like this (starting with `//!` or `/*!`) can only appear before items" | |
| )); | |
| err.span_suggestion_verbose( | |
| replacement_span, | |
| msg!("you might have meant to write a regular comment"), | |
| "", | |
| rustc_errors::Applicability::MachineApplicable, | |
| ); | |
| } | |
| err.emit(); | |
| } | |
| self.bump(); | |
| just_parsed_doc_comment = true; | |
| // Always make an outer attribute - this allows us to recover from a misplaced | |
| // inner attribute. | |
| Some(attr::mk_doc_comment( | |
| &self.psess.attr_id_generator, | |
| comment_kind, | |
| ast::AttrStyle::Outer, | |
| data, | |
| self.prev_token.span, | |
| )) | |
| } else { | |
| None | |
| }; | |
| if let Some(attr) = attr { | |
| if attr.style == ast::AttrStyle::Outer { | |
| outer_attrs.push(attr); | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| Ok(AttrWrapper::new(outer_attrs, start_pos)) | |
| } | |
| /// Matches `attribute = # ! [ meta_item ]`. | |
| /// `inner_parse_policy` prescribes how to handle inner attributes. | |
| // Public for rustfmt usage. | |
| pub fn parse_attribute( | |
| &mut self, | |
| inner_parse_policy: InnerAttrPolicy, | |
| ) -> PResult<'a, ast::Attribute> { | |
| debug!( | |
| "parse_attribute: inner_parse_policy={:?} self.token={:?}", | |
| inner_parse_policy, self.token | |
| ); | |
| 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| { | |
| let pound_hi = this.token.span.hi(); | |
| assert!(this.eat(exp!(Pound)), "parse_attribute called in non-attribute position"); | |
| let not_lo = this.token.span.lo(); | |
| let style = | |
| if this.eat(exp!(Bang)) { ast::AttrStyle::Inner } else { ast::AttrStyle::Outer }; | |
| let mut bracket_res = this.expect(exp!(OpenBracket)); | |
| // If `#!` is not followed by `[` | |
| if let Err(err) = &mut bracket_res | |
| && style == ast::AttrStyle::Inner | |
| && pound_hi == not_lo | |
| { | |
| err.note( | |
| "the token sequence `#!` here looks like the start of \ | |
| a shebang interpreter directive but it is not", | |
| ); | |
| err.help( | |
| "if you meant this to be a shebang interpreter directive, \ | |
| move it to the very start of the file", | |
| ); | |
| } | |
| bracket_res?; | |
| let attr_item = this.parse_attr_item(ForceCollect::No)?; | |
| // `attr_item` will never have tokens: within `parse_attr_item`, `collect_tokens` | |
| // attaches tokens only if: | |
| // - `ForceCollect::Yes` is passed (not true), or | |
| // - attributes on the parsed node require tokens (not true, because attr items can't | |
| // have attributes of their own, hence the empty `HasAttrs` impl for `AttrItem`). | |
| assert!(attr_item.tokens.is_none()); | |
| this.expect(exp!(CloseBracket))?; | |
| let attr_sp = lo.to(this.prev_token.span); | |
| // 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, | |
| attr_item.node.is_valid_for_outer_style(), | |
| ); | |
| } | |
| Ok(attr::mk_attr_from_item( | |
| &self.psess.attr_id_generator, | |
| attr_item.node, | |
| None, | |
| style, | |
| attr_sp, | |
| )) | |
| }) | |
| } | |
| fn annotate_following_item_if_applicable( | |
| &self, | |
| err: &mut Diag<'_>, | |
| span: Span, | |
| attr_type: OuterAttributeType, | |
| suggest_to_outer: bool, | |
| ) -> Option<Span> { | |
| let mut snapshot = self.create_snapshot_for_diagnostic(); | |
| let lo = span.lo() | |
| + BytePos(match attr_type { | |
| OuterAttributeType::Attribute => 1, | |
| _ => 2, | |
| }); | |
| let hi = lo + BytePos(1); | |
| let replacement_span = span.with_lo(lo).with_hi(hi); | |
| if let OuterAttributeType::DocBlockComment | OuterAttributeType::DocComment = attr_type { | |
| snapshot.bump(); | |
| } | |
| loop { | |
| // skip any other attributes, we want the item | |
| if snapshot.token == token::Pound { | |
| if let Err(err) = snapshot.parse_attribute(InnerAttrPolicy::Permitted) { | |
| err.cancel(); | |
| return Some(replacement_span); | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| match snapshot.parse_item_common( | |
| AttrWrapper::empty(), | |
| true, | |
| false, | |
| FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }, | |
| ForceCollect::No, | |
| AllowConstBlockItems::Yes, | |
| ) { | |
| Ok(Some(item)) => { | |
| err.arg("item", item.kind.descr()); | |
| err.span_label( | |
| item.span, | |
| match attr_type { | |
| OuterAttributeType::Attribute => { | |
| msg!("the inner attribute doesn't annotate this {$item}") | |
| } | |
| OuterAttributeType::DocComment | OuterAttributeType::DocBlockComment => { | |
| msg!("the inner doc comment doesn't annotate this {$item}") | |
| } | |
| }, | |
| ); | |
| if suggest_to_outer { | |
| err.span_suggestion_verbose( | |
| replacement_span, | |
| match attr_type { | |
| OuterAttributeType::Attribute => msg!("to annotate the {$item}, change the attribute from inner to outer style"), | |
| OuterAttributeType::DocComment | OuterAttributeType::DocBlockComment => msg!("to annotate the {$item}, change the doc comment from inner to outer style"), | |
| }, | |
| match attr_type { | |
| OuterAttributeType::Attribute => "", | |
| OuterAttributeType::DocBlockComment => "*", | |
| OuterAttributeType::DocComment => "/", | |
| }, | |
| rustc_errors::Applicability::MachineApplicable, | |
| ); | |
| } | |
| return None; | |
| } | |
| Err(item_err) => { | |
| item_err.cancel(); | |
| } | |
| Ok(None) => {} | |
| } | |
| Some(replacement_span) | |
| } | |
| pub(super) fn error_on_forbidden_inner_attr( | |
| &self, | |
| attr_sp: Span, | |
| policy: InnerAttrPolicy, | |
| suggest_to_outer: bool, | |
| ) { | |
| if let InnerAttrPolicy::Forbidden(reason) = policy { | |
| let mut diag = match reason.as_ref().copied() { | |
| Some(InnerAttrForbiddenReason::AfterOuterDocComment { prev_doc_comment_span }) => { | |
| self.dcx() | |
| .struct_span_err( | |
| attr_sp, | |
| msg!( | |
| "an inner attribute is not permitted following an outer doc comment" | |
| ), | |
| ) | |
| .with_span_label( | |
| attr_sp, | |
| msg!("not permitted following an outer doc comment"), | |
| ) | |
| .with_span_label(prev_doc_comment_span, msg!("previous doc comment")) | |
| } | |
| Some(InnerAttrForbiddenReason::AfterOuterAttribute { prev_outer_attr_sp }) => self | |
| .dcx() | |
| .struct_span_err( | |
| attr_sp, | |
| msg!("an inner attribute is not permitted following an outer attribute"), | |
| ) | |
| .with_span_label(attr_sp, msg!("not permitted following an outer attribute")) | |
| .with_span_label(prev_outer_attr_sp, msg!("previous outer attribute")), | |
| Some(InnerAttrForbiddenReason::InCodeBlock) | None => self.dcx().struct_span_err( | |
| attr_sp, | |
| msg!("an inner attribute is not permitted in this context"), | |
| ), | |
| }; | |
| diag.note(msg!("inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files")); | |
| if self | |
| .annotate_following_item_if_applicable( | |
| &mut diag, | |
| attr_sp, | |
| OuterAttributeType::Attribute, | |
| suggest_to_outer, | |
| ) | |
| .is_some() | |
| { | |
| diag.note(msg!( | |
| "outer attributes, like `#[test]`, annotate the item following them" | |
| )); | |
| }; | |
| diag.emit(); | |
| } | |
| } | |
| /// Parses an inner part of an attribute (the path and following tokens). | |
| /// The tokens must be either a delimited token stream, or empty token stream, | |
| /// or the "legacy" key-value form. | |
| /// | |
| /// ```text | |
| /// PATH `(` TOKEN_STREAM `)` | |
| /// PATH `[` TOKEN_STREAM `]` | |
| /// PATH `{` TOKEN_STREAM `}` | |
| /// PATH | |
| /// PATH `=` UNSUFFIXED_LIT | |
| /// ``` | |
| /// | |
| /// The delimiters or `=` are still put into the resulting token stream. | |
| pub fn parse_attr_item( | |
| &mut self, | |
| force_collect: ForceCollect, | |
| ) -> PResult<'a, WithTokens<ast::AttrItem>> { | |
| if let Some(item) = self.eat_metavar_seq_with_matcher( | |
| |mv_kind| matches!(mv_kind, MetaVarKind::Meta { .. }), | |
| |this| this.parse_attr_item(force_collect), | |
| ) { | |
| return Ok(item); | |
| } | |
| // Attr items don't have attributes. | |
| self.collect_tokens(None, AttrWrapper::empty(), force_collect, |this, _empty_attrs| { | |
| let lo = this.token.span; | |
| let is_unsafe = this.eat_keyword(exp!(Unsafe)); | |
| let unsafety = if is_unsafe { | |
| let unsafe_span = this.prev_token.span; | |
| this.expect(exp!(OpenParen))?; | |
| ast::Safety::Unsafe(unsafe_span) | |
| } else { | |
| ast::Safety::Default | |
| }; | |
| let path = this.parse_path(PathStyle::Mod)?; | |
| let args = this.parse_attr_args()?; | |
| if is_unsafe { | |
| this.expect(exp!(CloseParen))?; | |
| } | |
| let span = lo.to(this.prev_token.span); | |
| Ok(( | |
| WithTokens::new(ast::AttrItem { unsafety, path, args, span }), | |
| Trailing::No, | |
| UsePreAttrPos::No, | |
| )) | |
| }) | |
| } | |
| /// Parses attributes that appear after the opening of an item. These should | |
| /// be preceded by an exclamation mark, but we accept and warn about one | |
| /// terminated by a semicolon. | |
| /// | |
| /// Matches `inner_attrs*`. | |
| pub fn parse_inner_attributes(&mut self) -> PResult<'a, ast::AttrVec> { | |
| let mut attrs = ast::AttrVec::new(); | |
| loop { | |
| let start_pos = self.num_bump_calls; | |
| // Only try to parse if it is an inner attribute (has `!`). | |
| let attr = if self.check(exp!(Pound)) && self.look_ahead(1, |t| t == &token::Bang) { | |
| Some(self.parse_attribute(InnerAttrPolicy::Permitted)?) | |
| } 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( | |
| &self.psess.attr_id_generator, | |
| comment_kind, | |
| attr_style, | |
| data, | |
| self.prev_token.span, | |
| )) | |
| } else { | |
| None | |
| } | |
| } else { | |
| None | |
| }; | |
| if let Some(attr) = attr { | |
| // If we are currently capturing tokens (i.e. we are within a call to | |
| // `Parser::collect_tokens`) record the token positions of this inner attribute, | |
| // for possible later processing in a `LazyAttrTokenStream`. | |
| if let Capturing::Yes = self.capture_state.capturing { | |
| let end_pos = self.num_bump_calls; | |
| let parser_range = ParserRange(start_pos..end_pos); | |
| self.capture_state.inner_attr_parser_ranges.insert(attr.id, parser_range); | |
| } | |
| attrs.push(attr); | |
| } else { | |
| break; | |
| } | |
| } | |
| Ok(attrs) | |
| } | |
| // Note: must be unsuffixed. | |
| pub(crate) fn parse_unsuffixed_meta_item_lit(&mut self) -> PResult<'a, ast::MetaItemLit> { | |
| let lit = self.parse_meta_item_lit()?; | |
| debug!("checking if {:?} is unsuffixed", lit); | |
| if !lit.kind.is_unsuffixed() { | |
| self.dcx().emit_err(diagnostics::SuffixedLiteralInAttribute { span: lit.span }); | |
| } | |
| Ok(lit) | |
| } | |
| /// Matches `COMMASEP(meta_item_inner)`. | |
| pub fn parse_meta_seq_top(&mut self) -> PResult<'a, ThinVec<ast::MetaItemInner>> { | |
| // Presumably, the majority of the time there will only be one attr. | |
| let mut nmis = ThinVec::with_capacity(1); | |
| while self.token != token::Eof { | |
| nmis.push(self.parse_meta_item_inner()?); | |
| if !self.eat(exp!(Comma)) { | |
| break; | |
| } | |
| } | |
| Ok(nmis) | |
| } | |
| /// Parse a meta item per RFC 1559. | |
| /// | |
| /// ```ebnf | |
| /// MetaItem = SimplePath ( '=' UNSUFFIXED_LIT | '(' MetaSeq? ')' )? ; | |
| /// MetaSeq = MetaItemInner (',' MetaItemInner)* ','? ; | |
| /// ``` | |
| pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> { | |
| if let Some(MetaVarKind::Meta { has_meta_form }) = self.token.is_metavar_seq() { | |
| return if has_meta_form { | |
| let attr_item = self | |
| .eat_metavar_seq(MetaVarKind::Meta { has_meta_form: true }, |this| { | |
| this.parse_attr_item(ForceCollect::No) | |
| }) | |
| .unwrap() | |
| .node; | |
| Ok(attr_item.meta(attr_item.span).unwrap()) | |
| } else { | |
| self.unexpected_any() | |
| }; | |
| } | |
| let lo = self.token.span; | |
| let path = self.parse_path(PathStyle::Mod)?; | |
| let kind = self.parse_meta_item_kind()?; | |
| let span = lo.to(self.prev_token.span); | |
| Ok(ast::MetaItem { unsafety: ast::Safety::Default, path, kind, span }) | |
| } | |
| pub(crate) fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> { | |
| Ok(if self.eat(exp!(Eq)) { | |
| ast::MetaItemKind::NameValue(self.parse_unsuffixed_meta_item_lit()?) | |
| } else if self.check(exp!(OpenParen)) { | |
| let (list, _) = self.parse_paren_comma_seq(|p| p.parse_meta_item_inner())?; | |
| ast::MetaItemKind::List(list) | |
| } else { | |
| ast::MetaItemKind::Word | |
| }) | |
| } | |
| /// Parse an inner meta item per RFC 1559. | |
| /// | |
| /// ```ebnf | |
| /// MetaItemInner = UNSUFFIXED_LIT | MetaItem ; | |
| /// ``` | |
| pub fn parse_meta_item_inner(&mut self) -> PResult<'a, ast::MetaItemInner> { | |
| match self.parse_unsuffixed_meta_item_lit() { | |
| Ok(lit) => return Ok(ast::MetaItemInner::Lit(lit)), | |
| Err(err) => err.cancel(), // we provide a better error below | |
| } | |
| match self.parse_meta_item() { | |
| Ok(mi) => return Ok(ast::MetaItemInner::MetaItem(mi)), | |
| Err(err) => err.cancel(), // we provide a better error below | |
| } | |
| let mut err = diagnostics::InvalidMetaItem { | |
| span: self.token.span, | |
| descr: super::token_descr(&self.token), | |
| quote_ident_sugg: None, | |
| }; | |
| // Suggest quoting idents, e.g. in `#[cfg(key = value)]`. We don't use `Token::ident` and | |
| // don't `uninterpolate` the token to avoid suggesting anything butchered or questionable | |
| // when macro metavariables are involved. | |
| if self.prev_token == token::Eq | |
| && let token::Ident(..) = self.token.kind | |
| { | |
| let before = self.token.span.shrink_to_lo(); | |
| while let token::Ident(..) = self.token.kind { | |
| self.bump(); | |
| } | |
| err.quote_ident_sugg = Some(diagnostics::InvalidMetaItemQuoteIdentSugg { | |
| before, | |
| after: self.prev_token.span.shrink_to_hi(), | |
| }); | |
| } | |
| Err(self.dcx().create_err(err)) | |
| } | |
| /// Recover from outer attributes in places where none were expected. | |
| pub fn recover_from_outer_attributes(&mut self, target: &str) -> PResult<'a, ()> { | |
| // We check the token ourselves first to prevent `#` | |
| // from getting added to the set of expected tokens. | |
| if !self.may_recover() || !matches!(self.token.kind, token::Pound | token::DocComment(..)) { | |
| return Ok(()); | |
| } | |
| let attrs = self.parse_outer_attributes()?; | |
| if attrs.is_empty() { | |
| return Ok(()); | |
| } | |
| let attrs = attrs.take_for_recovery(self.psess); | |
| let span = attrs.first().unwrap().span.to(attrs.last().unwrap().span); | |
| let subject = if attrs.iter().all(|attr| matches!(attr.kind, ast::AttrKind::DocComment(..))) | |
| { | |
| "doc comments" | |
| } else { | |
| "attributes" | |
| }; | |
| self.dcx() | |
| .struct_span_err(span, format!("{subject} cannot be applied to {target}")) | |
| .with_span_label(span, format!("{subject} are not allowed here")) | |
| .with_span_suggestion_with_style( | |
| span.until(self.token.span), | |
| format!("remove these {subject}"), | |
| String::new(), | |
| rustc_errors::Applicability::MachineApplicable, | |
| rustc_errors::SuggestionStyle::CompletelyHidden, | |
| ) | |
| .emit(); | |
| Ok(()) | |
| } | |
| } |