Make doc comments cheaper with AttrKind.

`AttrKind` is a new type with two variants, `Normal` and `DocComment`. It's a
big performance win (over 10% in some cases) because `DocComment` lets doc
comments (which are common) be represented very cheaply.

`Attribute` gets some new helper methods to ease the transition:
- `has_name()`: check if the attribute name matches a single `Symbol`; for
  `DocComment` variants it succeeds if the symbol is `sym::doc`.
- `is_doc_comment()`: check if it has a `DocComment` kind.
- `{get,unwrap}_normal_item()`: extract the item from a `Normal` variant;
  panic otherwise.

Fixes #60935.
This commit is contained in:
Nicholas Nethercote 2019-10-24 06:33:12 +11:00
parent 69bc4aba78
commit eea6f23a0e
25 changed files with 232 additions and 146 deletions

View file

@ -997,14 +997,20 @@ impl<'a> LoweringContext<'a> {
// Note that we explicitly do not walk the path. Since we don't really // Note that we explicitly do not walk the path. Since we don't really
// lower attributes (we use the AST version) there is nowhere to keep // lower attributes (we use the AST version) there is nowhere to keep
// the `HirId`s. We don't actually need HIR version of attributes anyway. // the `HirId`s. We don't actually need HIR version of attributes anyway.
let kind = match attr.kind {
AttrKind::Normal(ref item) => {
AttrKind::Normal(AttrItem {
path: item.path.clone(),
tokens: self.lower_token_stream(item.tokens.clone()),
})
}
AttrKind::DocComment(comment) => AttrKind::DocComment(comment)
};
Attribute { Attribute {
item: AttrItem { kind,
path: attr.item.path.clone(),
tokens: self.lower_token_stream(attr.item.tokens.clone()),
},
id: attr.id, id: attr.id,
style: attr.style, style: attr.style,
is_sugared_doc: attr.is_sugared_doc,
span: attr.span, span: attr.span,
} }
} }

View file

@ -177,7 +177,7 @@ impl<'a> HashStable<StableHashingContext<'a>> for [ast::Attribute] {
let filtered: SmallVec<[&ast::Attribute; 8]> = self let filtered: SmallVec<[&ast::Attribute; 8]> = self
.iter() .iter()
.filter(|attr| { .filter(|attr| {
!attr.is_sugared_doc && !attr.is_doc_comment() &&
!attr.ident().map_or(false, |ident| hcx.is_ignored_attr(ident.name)) !attr.ident().map_or(false, |ident| hcx.is_ignored_attr(ident.name))
}) })
.collect(); .collect();
@ -207,19 +207,16 @@ impl<'a> HashStable<StableHashingContext<'a>> for ast::Attribute {
fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
// Make sure that these have been filtered out. // Make sure that these have been filtered out.
debug_assert!(!self.ident().map_or(false, |ident| hcx.is_ignored_attr(ident.name))); debug_assert!(!self.ident().map_or(false, |ident| hcx.is_ignored_attr(ident.name)));
debug_assert!(!self.is_sugared_doc); debug_assert!(!self.is_doc_comment());
let ast::Attribute {
ref item,
id: _,
style,
is_sugared_doc: _,
span,
} = *self;
let ast::Attribute { kind, id: _, style, span } = self;
if let ast::AttrKind::Normal(item) = kind {
item.hash_stable(hcx, hasher); item.hash_stable(hcx, hasher);
style.hash_stable(hcx, hasher); style.hash_stable(hcx, hasher);
span.hash_stable(hcx, hasher); span.hash_stable(hcx, hasher);
} else {
unreachable!();
}
} }
} }

View file

@ -706,7 +706,7 @@ impl EarlyLintPass for DeprecatedAttr {
} }
} }
if attr.check_name(sym::no_start) || attr.check_name(sym::crate_id) { if attr.check_name(sym::no_start) || attr.check_name(sym::crate_id) {
let path_str = pprust::path_to_string(&attr.item.path); let path_str = pprust::path_to_string(&attr.get_normal_item().path);
let msg = format!("use of deprecated attribute `{}`: no longer used.", path_str); let msg = format!("use of deprecated attribute `{}`: no longer used.", path_str);
lint_deprecated_attr(cx, attr, &msg, None); lint_deprecated_attr(cx, attr, &msg, None);
} }
@ -736,7 +736,7 @@ impl UnusedDocComment {
let mut sugared_span: Option<Span> = None; let mut sugared_span: Option<Span> = None;
while let Some(attr) = attrs.next() { while let Some(attr) = attrs.next() {
if attr.is_sugared_doc { if attr.is_doc_comment() {
sugared_span = Some( sugared_span = Some(
sugared_span.map_or_else( sugared_span.map_or_else(
|| attr.span, || attr.span,
@ -745,7 +745,7 @@ impl UnusedDocComment {
); );
} }
if attrs.peek().map(|next_attr| next_attr.is_sugared_doc).unwrap_or_default() { if attrs.peek().map(|next_attr| next_attr.is_doc_comment()).unwrap_or_default() {
continue; continue;
} }

View file

@ -11,7 +11,7 @@ crate fn collect(tcx: TyCtxt<'_>) -> Vec<String> {
tcx.hir().krate().visit_all_item_likes(&mut collector); tcx.hir().krate().visit_all_item_likes(&mut collector);
for attr in tcx.hir().krate().attrs.iter() { for attr in tcx.hir().krate().attrs.iter() {
if attr.item.path == sym::link_args { if attr.has_name(sym::link_args) {
if let Some(linkarg) = attr.value_str() { if let Some(linkarg) = attr.value_str() {
collector.add_link_args(&linkarg.as_str()); collector.add_link_args(&linkarg.as_str());
} }

View file

@ -328,7 +328,7 @@ impl<'a> AstValidator<'a> {
let arr = [sym::allow, sym::cfg, sym::cfg_attr, sym::deny, sym::forbid, sym::warn]; let arr = [sym::allow, sym::cfg, sym::cfg_attr, sym::deny, sym::forbid, sym::warn];
!arr.contains(&attr.name_or_empty()) && is_builtin_attr(attr) !arr.contains(&attr.name_or_empty()) && is_builtin_attr(attr)
}) })
.for_each(|attr| if attr.is_sugared_doc { .for_each(|attr| if attr.is_doc_comment() {
let mut err = self.err_handler().struct_span_err( let mut err = self.err_handler().struct_span_err(
attr.span, attr.span,
"documentation comments cannot be applied to function parameters" "documentation comments cannot be applied to function parameters"

View file

@ -1229,8 +1229,10 @@ impl<'a, 'b> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b> {
} }
fn visit_attribute(&mut self, attr: &'b ast::Attribute) { fn visit_attribute(&mut self, attr: &'b ast::Attribute) {
if !attr.is_sugared_doc && is_builtin_attr(attr) { if !attr.is_doc_comment() && is_builtin_attr(attr) {
self.r.builtin_attrs.push((attr.item.path.segments[0].ident, self.parent_scope)); self.r.builtin_attrs.push(
(attr.get_normal_item().path.segments[0].ident, self.parent_scope)
);
} }
visit::walk_attribute(self, attr); visit::walk_attribute(self, attr);
} }

View file

@ -179,7 +179,7 @@ impl<'a> base::Resolver for Resolver<'a> {
let (path, kind, derives, after_derive) = match invoc.kind { let (path, kind, derives, after_derive) = match invoc.kind {
InvocationKind::Attr { ref attr, ref derives, after_derive, .. } => InvocationKind::Attr { ref attr, ref derives, after_derive, .. } =>
(&attr.item.path, (&attr.get_normal_item().path,
MacroKind::Attr, MacroKind::Attr,
self.arenas.alloc_ast_paths(derives), self.arenas.alloc_ast_paths(derives),
after_derive), after_derive),

View file

@ -885,7 +885,7 @@ impl<'l, 'tcx> SaveContext<'l, 'tcx> {
for attr in attrs { for attr in attrs {
if attr.check_name(sym::doc) { if attr.check_name(sym::doc) {
if let Some(val) = attr.value_str() { if let Some(val) = attr.value_str() {
if attr.is_sugared_doc { if attr.is_doc_comment() {
result.push_str(&strip_doc_comment_decoration(&val.as_str())); result.push_str(&strip_doc_comment_decoration(&val.as_str()));
} else { } else {
result.push_str(&val.as_str()); result.push_str(&val.as_str());
@ -1195,7 +1195,7 @@ fn null_id() -> rls_data::Id {
fn lower_attributes(attrs: Vec<Attribute>, scx: &SaveContext<'_, '_>) -> Vec<rls_data::Attribute> { fn lower_attributes(attrs: Vec<Attribute>, scx: &SaveContext<'_, '_>) -> Vec<rls_data::Attribute> {
attrs.into_iter() attrs.into_iter()
// Only retain real attributes. Doc comments are lowered separately. // Only retain real attributes. Doc comments are lowered separately.
.filter(|attr| attr.item.path != sym::doc) .filter(|attr| !attr.has_name(sym::doc))
.map(|mut attr| { .map(|mut attr| {
// Remove the surrounding '#[..]' or '#![..]' of the pretty printed // Remove the surrounding '#[..]' or '#![..]' of the pretty printed
// attribute. First normalize all inner attribute (#![..]) to outer // attribute. First normalize all inner attribute (#![..]) to outer

View file

@ -2706,7 +2706,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
} }
codegen_fn_attrs.inline = attrs.iter().fold(InlineAttr::None, |ia, attr| { codegen_fn_attrs.inline = attrs.iter().fold(InlineAttr::None, |ia, attr| {
if attr.item.path != sym::inline { if !attr.has_name(sym::inline) {
return ia; return ia;
} }
match attr.meta().map(|i| i.kind) { match attr.meta().map(|i| i.kind) {
@ -2746,7 +2746,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
}); });
codegen_fn_attrs.optimize = attrs.iter().fold(OptimizeAttr::None, |ia, attr| { codegen_fn_attrs.optimize = attrs.iter().fold(OptimizeAttr::None, |ia, attr| {
if attr.item.path != sym::optimize { if !attr.has_name(sym::optimize) {
return ia; return ia;
} }
let err = |sp, s| span_err!(tcx.sess.diagnostic(), sp, E0722, "{}", s); let err = |sp, s| span_err!(tcx.sess.diagnostic(), sp, E0722, "{}", s);

View file

@ -26,7 +26,7 @@ use rustc::ty::{self, DefIdTree, TyCtxt, Region, RegionVid, Ty, AdtKind};
use rustc::ty::fold::TypeFolder; use rustc::ty::fold::TypeFolder;
use rustc::ty::layout::VariantIdx; use rustc::ty::layout::VariantIdx;
use rustc::util::nodemap::{FxHashMap, FxHashSet}; use rustc::util::nodemap::{FxHashMap, FxHashSet};
use syntax::ast::{self, Attribute, AttrStyle, AttrItem, Ident}; use syntax::ast::{self, Attribute, AttrStyle, AttrKind, Ident};
use syntax::attr; use syntax::attr;
use syntax::parse::lexer::comments; use syntax::parse::lexer::comments;
use syntax::source_map::DUMMY_SP; use syntax::source_map::DUMMY_SP;
@ -859,31 +859,32 @@ impl Attributes {
let mut cfg = Cfg::True; let mut cfg = Cfg::True;
let mut doc_line = 0; let mut doc_line = 0;
/// Converts `attr` to a normal `#[doc="foo"]` comment, if it is a /// If `attr` is a doc comment, strips the leading and (if present)
/// comment like `///` or `/** */`. (Returns `attr` unchanged for /// trailing comments symbols, e.g. `///`, `/**`, and `*/`. Otherwise,
/// non-sugared doc attributes.) /// returns `attr` unchanged.
pub fn with_desugared_doc<T>(attr: &Attribute, f: impl FnOnce(&Attribute) -> T) -> T { pub fn with_doc_comment_markers_stripped<T>(
if attr.is_sugared_doc { attr: &Attribute,
let comment = attr.value_str().unwrap(); f: impl FnOnce(&Attribute) -> T
let meta = attr::mk_name_value_item_str( ) -> T {
Ident::with_dummy_span(sym::doc), match attr.kind {
Symbol::intern(&comments::strip_doc_comment_decoration(&comment.as_str())), AttrKind::Normal(_) => {
DUMMY_SP, f(attr)
); }
AttrKind::DocComment(comment) => {
let comment =
Symbol::intern(&comments::strip_doc_comment_decoration(&comment.as_str()));
f(&Attribute { f(&Attribute {
item: AttrItem { path: meta.path, tokens: meta.kind.tokens(meta.span) }, kind: AttrKind::DocComment(comment),
id: attr.id, id: attr.id,
style: attr.style, style: attr.style,
is_sugared_doc: true,
span: attr.span, span: attr.span,
}) })
} else { }
f(attr)
} }
} }
let other_attrs = attrs.iter().filter_map(|attr| { let other_attrs = attrs.iter().filter_map(|attr| {
with_desugared_doc(attr, |attr| { with_doc_comment_markers_stripped(attr, |attr| {
if attr.check_name(sym::doc) { if attr.check_name(sym::doc) {
if let Some(mi) = attr.meta() { if let Some(mi) = attr.meta() {
if let Some(value) = mi.value_str() { if let Some(value) = mi.value_str() {
@ -892,7 +893,7 @@ impl Attributes {
let line = doc_line; let line = doc_line;
doc_line += value.lines().count(); doc_line += value.lines().count();
if attr.is_sugared_doc { if attr.is_doc_comment() {
doc_strings.push(DocFragment::SugaredDoc(line, attr.span, value)); doc_strings.push(DocFragment::SugaredDoc(line, attr.span, value));
} else { } else {
doc_strings.push(DocFragment::RawDoc(line, attr.span, value)); doc_strings.push(DocFragment::RawDoc(line, attr.span, value));

View file

@ -2190,18 +2190,31 @@ pub struct AttrItem {
} }
/// Metadata associated with an item. /// Metadata associated with an item.
/// Doc-comments are promoted to attributes that have `is_sugared_doc = true`.
#[derive(Clone, RustcEncodable, RustcDecodable, Debug)] #[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
pub struct Attribute { pub struct Attribute {
pub item: AttrItem, pub kind: AttrKind,
pub id: AttrId, pub id: AttrId,
/// Denotes if the attribute decorates the following construct (outer) /// Denotes if the attribute decorates the following construct (outer)
/// or the construct this attribute is contained within (inner). /// or the construct this attribute is contained within (inner).
pub style: AttrStyle, pub style: AttrStyle,
pub is_sugared_doc: bool,
pub span: Span, pub span: Span,
} }
#[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
pub enum AttrKind {
/// A normal attribute.
Normal(AttrItem),
/// A doc comment (e.g. `/// ...`, `//! ...`, `/** ... */`, `/*! ... */`).
/// Doc attributes (e.g. `#[doc="..."]`) are represented with the `Normal`
/// variant (which is much less compact and thus more expensive).
///
/// Note: `self.has_name(sym::doc)` and `self.check_name(sym::doc)` succeed
/// for this variant, but this may change in the future.
/// ```
DocComment(Symbol),
}
/// `TraitRef`s appear in impls. /// `TraitRef`s appear in impls.
/// ///
/// Resolution maps each `TraitRef`'s `ref_id` to its defining trait; that's all /// Resolution maps each `TraitRef`'s `ref_id` to its defining trait; that's all

View file

@ -228,7 +228,7 @@ fn find_stability_generic<'a, I>(sess: &ParseSess,
sym::stable, sym::stable,
sym::rustc_promotable, sym::rustc_promotable,
sym::rustc_allow_const_fn_ptr, sym::rustc_allow_const_fn_ptr,
].iter().any(|&s| attr.item.path == s) { ].iter().any(|&s| attr.has_name(s)) {
continue // not a stability level continue // not a stability level
} }
@ -236,10 +236,10 @@ fn find_stability_generic<'a, I>(sess: &ParseSess,
let meta = attr.meta(); let meta = attr.meta();
if attr.item.path == sym::rustc_promotable { if attr.has_name(sym::rustc_promotable) {
promotable = true; promotable = true;
} }
if attr.item.path == sym::rustc_allow_const_fn_ptr { if attr.has_name(sym::rustc_allow_const_fn_ptr) {
allow_const_fn_ptr = true; allow_const_fn_ptr = true;
} }
// attributes with data // attributes with data
@ -778,7 +778,7 @@ pub fn find_repr_attrs(sess: &ParseSess, attr: &Attribute) -> Vec<ReprAttr> {
let mut acc = Vec::new(); let mut acc = Vec::new();
let diagnostic = &sess.span_diagnostic; let diagnostic = &sess.span_diagnostic;
if attr.item.path == sym::repr { if attr.has_name(sym::repr) {
if let Some(items) = attr.meta_item_list() { if let Some(items) = attr.meta_item_list() {
mark_used(attr); mark_used(attr);
for item in items { for item in items {

View file

@ -9,7 +9,7 @@ pub use StabilityLevel::*;
pub use crate::ast::Attribute; pub use crate::ast::Attribute;
use crate::ast; use crate::ast;
use crate::ast::{AttrItem, AttrId, AttrStyle, Name, Ident, Path, PathSegment}; use crate::ast::{AttrItem, AttrId, AttrKind, AttrStyle, Name, Ident, Path, PathSegment};
use crate::ast::{MetaItem, MetaItemKind, NestedMetaItem}; use crate::ast::{MetaItem, MetaItemKind, NestedMetaItem};
use crate::ast::{Lit, LitKind, Expr, Item, Local, Stmt, StmtKind, GenericParam}; use crate::ast::{Lit, LitKind, Expr, Item, Local, Stmt, StmtKind, GenericParam};
use crate::mut_visit::visit_clobber; use crate::mut_visit::visit_clobber;
@ -145,12 +145,17 @@ impl NestedMetaItem {
} }
impl Attribute { impl Attribute {
pub fn has_name(&self, name: Symbol) -> bool {
match self.kind {
AttrKind::Normal(ref item) => item.path == name,
AttrKind::DocComment(_) => name == sym::doc,
}
}
/// Returns `true` if the attribute's path matches the argument. If it matches, then the /// Returns `true` if the attribute's path matches the argument. If it matches, then the
/// attribute is marked as used. /// attribute is marked as used.
///
/// To check the attribute name without marking it used, use the `path` field directly.
pub fn check_name(&self, name: Symbol) -> bool { pub fn check_name(&self, name: Symbol) -> bool {
let matches = self.item.path == name; let matches = self.has_name(name);
if matches { if matches {
mark_used(self); mark_used(self);
} }
@ -159,29 +164,48 @@ impl Attribute {
/// For a single-segment attribute, returns its name; otherwise, returns `None`. /// For a single-segment attribute, returns its name; otherwise, returns `None`.
pub fn ident(&self) -> Option<Ident> { pub fn ident(&self) -> Option<Ident> {
if self.item.path.segments.len() == 1 { match self.kind {
Some(self.item.path.segments[0].ident) AttrKind::Normal(ref item) => {
if item.path.segments.len() == 1 {
Some(item.path.segments[0].ident)
} else { } else {
None None
} }
} }
AttrKind::DocComment(_) => Some(Ident::new(sym::doc, self.span)),
}
}
pub fn name_or_empty(&self) -> Symbol { pub fn name_or_empty(&self) -> Symbol {
self.ident().unwrap_or(Ident::invalid()).name self.ident().unwrap_or(Ident::invalid()).name
} }
pub fn value_str(&self) -> Option<Symbol> { pub fn value_str(&self) -> Option<Symbol> {
self.meta().and_then(|meta| meta.value_str()) match self.kind {
AttrKind::Normal(ref item) => {
item.meta(self.span).and_then(|meta| meta.value_str())
}
AttrKind::DocComment(comment) => Some(comment),
}
} }
pub fn meta_item_list(&self) -> Option<Vec<NestedMetaItem>> { pub fn meta_item_list(&self) -> Option<Vec<NestedMetaItem>> {
match self.meta() { match self.kind {
AttrKind::Normal(ref item) => {
match item.meta(self.span) {
Some(MetaItem { kind: MetaItemKind::List(list), .. }) => Some(list), Some(MetaItem { kind: MetaItemKind::List(list), .. }) => Some(list),
_ => None _ => None
} }
} }
AttrKind::DocComment(_) => None,
}
}
pub fn is_word(&self) -> bool { pub fn is_word(&self) -> bool {
self.item.tokens.is_empty() if let AttrKind::Normal(item) = &self.kind {
item.tokens.is_empty()
} else {
false
}
} }
pub fn is_meta_item_list(&self) -> bool { pub fn is_meta_item_list(&self) -> bool {
@ -275,18 +299,50 @@ impl AttrItem {
} }
impl Attribute { impl Attribute {
pub fn is_doc_comment(&self) -> bool {
match self.kind {
AttrKind::Normal(_) => false,
AttrKind::DocComment(_) => true,
}
}
pub fn get_normal_item(&self) -> &AttrItem {
match self.kind {
AttrKind::Normal(ref item) => item,
AttrKind::DocComment(_) => panic!("unexpected sugared doc"),
}
}
pub fn unwrap_normal_item(self) -> AttrItem {
match self.kind {
AttrKind::Normal(item) => item,
AttrKind::DocComment(_) => panic!("unexpected sugared doc"),
}
}
/// Extracts the MetaItem from inside this Attribute. /// Extracts the MetaItem from inside this Attribute.
pub fn meta(&self) -> Option<MetaItem> { pub fn meta(&self) -> Option<MetaItem> {
self.item.meta(self.span) match self.kind {
AttrKind::Normal(ref item) => item.meta(self.span),
AttrKind::DocComment(comment) =>
Some(mk_name_value_item_str(Ident::new(sym::doc, self.span), comment, self.span)),
}
} }
pub fn parse_meta<'a>(&self, sess: &'a ParseSess) -> PResult<'a, MetaItem> { pub fn parse_meta<'a>(&self, sess: &'a ParseSess) -> PResult<'a, MetaItem> {
match self.kind {
AttrKind::Normal(ref item) => {
Ok(MetaItem { Ok(MetaItem {
path: self.item.path.clone(), path: item.path.clone(),
kind: parse::parse_in_attr(sess, self, |p| p.parse_meta_item_kind())?, kind: parse::parse_in_attr(sess, self, |parser| parser.parse_meta_item_kind())?,
span: self.span, span: self.span,
}) })
} }
AttrKind::DocComment(comment) => {
Ok(mk_name_value_item_str(Ident::new(sym::doc, self.span), comment, self.span))
}
}
}
} }
/* Constructors */ /* Constructors */
@ -327,10 +383,9 @@ crate fn mk_attr_id() -> AttrId {
pub fn mk_attr(style: AttrStyle, path: Path, tokens: TokenStream, span: Span) -> Attribute { pub fn mk_attr(style: AttrStyle, path: Path, tokens: TokenStream, span: Span) -> Attribute {
Attribute { Attribute {
item: AttrItem { path, tokens }, kind: AttrKind::Normal(AttrItem { path, tokens }),
id: mk_attr_id(), id: mk_attr_id(),
style, style,
is_sugared_doc: false,
span, span,
} }
} }
@ -345,18 +400,11 @@ pub fn mk_attr_outer(item: MetaItem) -> Attribute {
mk_attr(AttrStyle::Outer, item.path, item.kind.tokens(item.span), item.span) mk_attr(AttrStyle::Outer, item.path, item.kind.tokens(item.span), item.span)
} }
pub fn mk_sugared_doc_attr(text: Symbol, span: Span) -> Attribute { pub fn mk_doc_comment(comment: Symbol, span: Span) -> Attribute {
let style = doc_comment_style(&text.as_str());
let lit_kind = LitKind::Str(text, ast::StrStyle::Cooked);
let lit = Lit::from_lit_kind(lit_kind, span);
Attribute { Attribute {
item: AttrItem { kind: AttrKind::DocComment(comment),
path: Path::from_ident(Ident::with_dummy_span(sym::doc).with_span_pos(span)),
tokens: MetaItemKind::NameValue(lit).tokens(span),
},
id: mk_attr_id(), id: mk_attr_id(),
style, style: doc_comment_style(&comment.as_str()),
is_sugared_doc: true,
span, span,
} }
} }

View file

@ -93,10 +93,10 @@ impl<'a> StripUnconfigured<'a> {
/// is in the original source file. Gives a compiler error if the syntax of /// is in the original source file. Gives a compiler error if the syntax of
/// the attribute is incorrect. /// the attribute is incorrect.
fn process_cfg_attr(&mut self, attr: ast::Attribute) -> Vec<ast::Attribute> { fn process_cfg_attr(&mut self, attr: ast::Attribute) -> Vec<ast::Attribute> {
if attr.item.path != sym::cfg_attr { if !attr.has_name(sym::cfg_attr) {
return vec![attr]; return vec![attr];
} }
if attr.item.tokens.is_empty() { if attr.get_normal_item().tokens.is_empty() {
self.sess.span_diagnostic self.sess.span_diagnostic
.struct_span_err( .struct_span_err(
attr.span, attr.span,
@ -136,10 +136,9 @@ impl<'a> StripUnconfigured<'a> {
// `#[cfg_attr(false, cfg_attr(true, some_attr))]`. // `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
expanded_attrs.into_iter() expanded_attrs.into_iter()
.flat_map(|(item, span)| self.process_cfg_attr(ast::Attribute { .flat_map(|(item, span)| self.process_cfg_attr(ast::Attribute {
item, kind: ast::AttrKind::Normal(item),
id: attr::mk_attr_id(), id: attr::mk_attr_id(),
style: attr.style, style: attr.style,
is_sugared_doc: false,
span, span,
})) }))
.collect() .collect()
@ -212,7 +211,7 @@ impl<'a> StripUnconfigured<'a> {
GateIssue::Language, GateIssue::Language,
EXPLAIN_STMT_ATTR_SYNTAX); EXPLAIN_STMT_ATTR_SYNTAX);
if attr.is_sugared_doc { if attr.is_doc_comment() {
err.help("`///` is for documentation comments. For a plain comment, use `//`."); err.help("`///` is for documentation comments. For a plain comment, use `//`.");
} }

View file

@ -329,7 +329,8 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
// `rustc_dummy` doesn't have any restrictions specific to built-in attributes. // `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
Some((name, _, template, _)) if name != sym::rustc_dummy => Some((name, _, template, _)) if name != sym::rustc_dummy =>
check_builtin_attribute(self.parse_sess, attr, name, template), check_builtin_attribute(self.parse_sess, attr, name, template),
_ => if let Some(TokenTree::Token(token)) = attr.item.tokens.trees().next() { _ => if let Some(TokenTree::Token(token)) =
attr.get_normal_item().tokens.trees().next() {
if token == token::Eq { if token == token::Eq {
// All key-value attributes are restricted to meta-item syntax. // All key-value attributes are restricted to meta-item syntax.
attr.parse_meta(self.parse_sess).map_err(|mut err| err.emit()).ok(); attr.parse_meta(self.parse_sess).map_err(|mut err| err.emit()).ok();

View file

@ -550,10 +550,14 @@ pub fn noop_visit_local<T: MutVisitor>(local: &mut P<Local>, vis: &mut T) {
} }
pub fn noop_visit_attribute<T: MutVisitor>(attr: &mut Attribute, vis: &mut T) { pub fn noop_visit_attribute<T: MutVisitor>(attr: &mut Attribute, vis: &mut T) {
let Attribute { item: AttrItem { path, tokens }, id: _, style: _, is_sugared_doc: _, span } let Attribute { kind, id: _, style: _, span } = attr;
= attr; match kind {
AttrKind::Normal(AttrItem { path, tokens }) => {
vis.visit_path(path); vis.visit_path(path);
vis.visit_tts(tokens); vis.visit_tts(tokens);
}
AttrKind::DocComment(_) => {}
}
vis.visit_span(span); vis.visit_span(span);
} }

View file

@ -287,7 +287,7 @@ pub fn parse_in_attr<'a, T>(
) -> PResult<'a, T> { ) -> PResult<'a, T> {
let mut parser = Parser::new( let mut parser = Parser::new(
sess, sess,
attr.item.tokens.clone(), attr.get_normal_item().tokens.clone(),
None, None,
false, false,
false, false,
@ -393,18 +393,22 @@ fn prepend_attrs(
let source = pprust::attribute_to_string(attr); let source = pprust::attribute_to_string(attr);
let macro_filename = FileName::macro_expansion_source_code(&source); let macro_filename = FileName::macro_expansion_source_code(&source);
if attr.is_sugared_doc {
let item = match attr.kind {
ast::AttrKind::Normal(ref item) => item,
ast::AttrKind::DocComment(_) => {
let stream = parse_stream_from_source_str(macro_filename, source, sess, Some(span)); let stream = parse_stream_from_source_str(macro_filename, source, sess, Some(span));
builder.push(stream); builder.push(stream);
continue continue
} }
};
// synthesize # [ $path $tokens ] manually here // synthesize # [ $path $tokens ] manually here
let mut brackets = tokenstream::TokenStreamBuilder::new(); let mut brackets = tokenstream::TokenStreamBuilder::new();
// For simple paths, push the identifier directly // For simple paths, push the identifier directly
if attr.item.path.segments.len() == 1 && attr.item.path.segments[0].args.is_none() { if item.path.segments.len() == 1 && item.path.segments[0].args.is_none() {
let ident = attr.item.path.segments[0].ident; let ident = item.path.segments[0].ident;
let token = token::Ident(ident.name, ident.as_str().starts_with("r#")); let token = token::Ident(ident.name, ident.as_str().starts_with("r#"));
brackets.push(tokenstream::TokenTree::token(token, ident.span)); brackets.push(tokenstream::TokenTree::token(token, ident.span));
@ -415,7 +419,7 @@ fn prepend_attrs(
brackets.push(stream); brackets.push(stream);
} }
brackets.push(attr.item.tokens.clone()); brackets.push(item.tokens.clone());
// The span we list here for `#` and for `[ ... ]` are both wrong in // The span we list here for `#` and for `[ ... ]` are both wrong in
// that it encompasses more than each token, but it hopefully is "good // that it encompasses more than each token, but it hopefully is "good

View file

@ -43,7 +43,7 @@ impl<'a> Parser<'a> {
just_parsed_doc_comment = false; just_parsed_doc_comment = false;
} }
token::DocComment(s) => { token::DocComment(s) => {
let attr = attr::mk_sugared_doc_attr(s, self.token.span); let attr = attr::mk_doc_comment(s, self.token.span);
if attr.style != ast::AttrStyle::Outer { if attr.style != ast::AttrStyle::Outer {
let mut err = self.fatal("expected outer doc comment"); let mut err = self.fatal("expected outer doc comment");
err.note("inner doc comments like this (starting with \ err.note("inner doc comments like this (starting with \
@ -150,10 +150,9 @@ impl<'a> Parser<'a> {
}; };
Ok(ast::Attribute { Ok(ast::Attribute {
item, kind: ast::AttrKind::Normal(item),
id: attr::mk_attr_id(), id: attr::mk_attr_id(),
style, style,
is_sugared_doc: false,
span, span,
}) })
} }
@ -229,7 +228,7 @@ impl<'a> Parser<'a> {
} }
token::DocComment(s) => { token::DocComment(s) => {
// We need to get the position of this token before we bump. // We need to get the position of this token before we bump.
let attr = attr::mk_sugared_doc_attr(s, self.token.span); let attr = attr::mk_doc_comment(s, self.token.span);
if attr.style == ast::AttrStyle::Inner { if attr.style == ast::AttrStyle::Inner {
attrs.push(attr); attrs.push(attr);
self.bump(); self.bump();

View file

@ -3,8 +3,8 @@ use super::diagnostics::{Error, dummy_arg, ConsumeClosingDelim};
use crate::maybe_whole; use crate::maybe_whole;
use crate::ptr::P; use crate::ptr::P;
use crate::ast::{self, DUMMY_NODE_ID, Ident, Attribute, AttrStyle, AnonConst, Item, ItemKind}; use crate::ast::{self, DUMMY_NODE_ID, Ident, Attribute, AttrKind, AttrStyle, AnonConst, Item};
use crate::ast::{ImplItem, ImplItemKind, TraitItem, TraitItemKind, UseTree, UseTreeKind}; use crate::ast::{ItemKind, ImplItem, ImplItemKind, TraitItem, TraitItemKind, UseTree, UseTreeKind};
use crate::ast::{PathSegment, IsAuto, Constness, IsAsync, Unsafety, Defaultness}; use crate::ast::{PathSegment, IsAuto, Constness, IsAsync, Unsafety, Defaultness};
use crate::ast::{Visibility, VisibilityKind, Mutability, FnHeader, ForeignItem, ForeignItemKind}; use crate::ast::{Visibility, VisibilityKind, Mutability, FnHeader, ForeignItem, ForeignItemKind};
use crate::ast::{Ty, TyKind, Generics, GenericBounds, TraitRef, EnumDef, VariantData, StructField}; use crate::ast::{Ty, TyKind, Generics, GenericBounds, TraitRef, EnumDef, VariantData, StructField};
@ -483,12 +483,14 @@ impl<'a> Parser<'a> {
/// Emits an expected-item-after-attributes error. /// Emits an expected-item-after-attributes error.
fn expected_item_err(&mut self, attrs: &[Attribute]) -> PResult<'a, ()> { fn expected_item_err(&mut self, attrs: &[Attribute]) -> PResult<'a, ()> {
let message = match attrs.last() { let message = match attrs.last() {
Some(&Attribute { is_sugared_doc: true, .. }) => "expected item after doc comment", Some(&Attribute { kind: AttrKind::DocComment(_), .. }) =>
_ => "expected item after attributes", "expected item after doc comment",
_ =>
"expected item after attributes",
}; };
let mut err = self.diagnostic().struct_span_err(self.prev_span, message); let mut err = self.diagnostic().struct_span_err(self.prev_span, message);
if attrs.last().unwrap().is_sugared_doc { if attrs.last().unwrap().is_doc_comment() {
err.span_label(self.prev_span, "this doc comment doesn't document anything"); err.span_label(self.prev_span, "this doc comment doesn't document anything");
} }
Err(err) Err(err)

View file

@ -246,7 +246,7 @@ let mut fflags: c_int = wb();
let source = "/// doc comment\r\n/// line 2\r\nfn foo() {}".to_string(); let source = "/// doc comment\r\n/// line 2\r\nfn foo() {}".to_string();
let item = parse_item_from_source_str(name_2, source, &sess) let item = parse_item_from_source_str(name_2, source, &sess)
.unwrap().unwrap(); .unwrap().unwrap();
let docs = item.attrs.iter().filter(|a| a.path == sym::doc) let docs = item.attrs.iter().filter(|a| a.has_name(sym::doc))
.map(|a| a.value_str().unwrap().to_string()).collect::<Vec<_>>(); .map(|a| a.value_str().unwrap().to_string()).collect::<Vec<_>>();
let b: &[_] = &["/// doc comment".to_string(), "/// line 2".to_string()]; let b: &[_] = &["/// doc comment".to_string(), "/// line 2".to_string()];
assert_eq!(&docs[..], b); assert_eq!(&docs[..], b);

View file

@ -622,17 +622,20 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
self.hardbreak_if_not_bol(); self.hardbreak_if_not_bol();
} }
self.maybe_print_comment(attr.span.lo()); self.maybe_print_comment(attr.span.lo());
if attr.is_sugared_doc { match attr.kind {
self.word(attr.value_str().unwrap().to_string()); ast::AttrKind::Normal(ref item) => {
self.hardbreak()
} else {
match attr.style { match attr.style {
ast::AttrStyle::Inner => self.word("#!["), ast::AttrStyle::Inner => self.word("#!["),
ast::AttrStyle::Outer => self.word("#["), ast::AttrStyle::Outer => self.word("#["),
} }
self.print_attr_item(&attr.item, attr.span); self.print_attr_item(&item, attr.span);
self.word("]"); self.word("]");
} }
ast::AttrKind::DocComment(comment) => {
self.word(comment.to_string());
self.hardbreak()
}
}
} }
fn print_attr_item(&mut self, item: &ast::AttrItem, span: Span) { fn print_attr_item(&mut self, item: &ast::AttrItem, span: Span) {

View file

@ -846,7 +846,10 @@ pub fn walk_vis<'a, V: Visitor<'a>>(visitor: &mut V, vis: &'a Visibility) {
} }
pub fn walk_attribute<'a, V: Visitor<'a>>(visitor: &mut V, attr: &'a Attribute) { pub fn walk_attribute<'a, V: Visitor<'a>>(visitor: &mut V, attr: &'a Attribute) {
visitor.visit_tts(attr.item.tokens.clone()); match attr.kind {
AttrKind::Normal(ref item) => visitor.visit_tts(item.tokens.clone()),
AttrKind::DocComment(_) => {}
}
} }
pub fn walk_tt<'a, V: Visitor<'a>>(visitor: &mut V, tt: TokenTree) { pub fn walk_tt<'a, V: Visitor<'a>>(visitor: &mut V, tt: TokenTree) {

View file

@ -419,7 +419,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
} }
let mut item = self.fully_configure(item); let mut item = self.fully_configure(item);
item.visit_attrs(|attrs| attrs.retain(|a| a.item.path != sym::derive)); item.visit_attrs(|attrs| attrs.retain(|a| !a.has_name(sym::derive)));
let mut helper_attrs = Vec::new(); let mut helper_attrs = Vec::new();
let mut has_copy = false; let mut has_copy = false;
for ext in exts { for ext in exts {
@ -634,9 +634,10 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
| Annotatable::Variant(..) | Annotatable::Variant(..)
=> panic!("unexpected annotatable"), => panic!("unexpected annotatable"),
})), DUMMY_SP).into(); })), DUMMY_SP).into();
let input = self.extract_proc_macro_attr_input(attr.item.tokens, span); let item = attr.unwrap_normal_item();
let input = self.extract_proc_macro_attr_input(item.tokens, span);
let tok_result = expander.expand(self.cx, span, input, item_tok); let tok_result = expander.expand(self.cx, span, input, item_tok);
self.parse_ast_fragment(tok_result, fragment_kind, &attr.item.path, span) self.parse_ast_fragment(tok_result, fragment_kind, &item.path, span)
} }
SyntaxExtensionKind::LegacyAttr(expander) => { SyntaxExtensionKind::LegacyAttr(expander) => {
match attr.parse_meta(self.cx.parse_sess) { match attr.parse_meta(self.cx.parse_sess) {
@ -974,7 +975,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
-> Option<ast::Attribute> { -> Option<ast::Attribute> {
let attr = attrs.iter() let attr = attrs.iter()
.position(|a| { .position(|a| {
if a.item.path == sym::derive { if a.has_name(sym::derive) {
*after_derive = true; *after_derive = true;
} }
!attr::is_known(a) && !is_builtin_attr(a) !attr::is_known(a) && !is_builtin_attr(a)
@ -982,7 +983,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
.map(|i| attrs.remove(i)); .map(|i| attrs.remove(i));
if let Some(attr) = &attr { if let Some(attr) = &attr {
if !self.cx.ecfg.custom_inner_attributes() && if !self.cx.ecfg.custom_inner_attributes() &&
attr.style == ast::AttrStyle::Inner && attr.item.path != sym::test { attr.style == ast::AttrStyle::Inner && !attr.has_name(sym::test) {
emit_feature_err(&self.cx.parse_sess, sym::custom_inner_attributes, emit_feature_err(&self.cx.parse_sess, sym::custom_inner_attributes,
attr.span, GateIssue::Language, attr.span, GateIssue::Language,
"non-builtin inner attributes are unstable"); "non-builtin inner attributes are unstable");
@ -1032,7 +1033,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
feature_gate::check_attribute(attr, self.cx.parse_sess, features); feature_gate::check_attribute(attr, self.cx.parse_sess, features);
// macros are expanded before any lint passes so this warning has to be hardcoded // macros are expanded before any lint passes so this warning has to be hardcoded
if attr.item.path == sym::derive { if attr.has_name(sym::derive) {
self.cx.struct_span_warn(attr.span, "`#[derive]` does nothing on macro invocations") self.cx.struct_span_warn(attr.span, "`#[derive]` does nothing on macro invocations")
.note("this may become a hard error in a future release") .note("this may become a hard error in a future release")
.emit(); .emit();
@ -1547,11 +1548,12 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
let meta = attr::mk_list_item(Ident::with_dummy_span(sym::doc), items); let meta = attr::mk_list_item(Ident::with_dummy_span(sym::doc), items);
*at = attr::Attribute { *at = attr::Attribute {
item: AttrItem { path: meta.path, tokens: meta.kind.tokens(meta.span) }, kind: ast::AttrKind::Normal(
AttrItem { path: meta.path, tokens: meta.kind.tokens(meta.span) },
),
span: at.span, span: at.span,
id: at.id, id: at.id,
style: at.style, style: at.style,
is_sugared_doc: false,
}; };
} else { } else {
noop_visit_attribute(at, self) noop_visit_attribute(at, self)

View file

@ -181,7 +181,7 @@ impl<'a> Visitor<'a> for MarkAttrs<'a> {
crate fn collect_derives(cx: &mut ExtCtxt<'_>, attrs: &mut Vec<ast::Attribute>) -> Vec<ast::Path> { crate fn collect_derives(cx: &mut ExtCtxt<'_>, attrs: &mut Vec<ast::Attribute>) -> Vec<ast::Path> {
let mut result = Vec::new(); let mut result = Vec::new();
attrs.retain(|attr| { attrs.retain(|attr| {
if attr.item.path != sym::derive { if !attr.has_name(sym::derive) {
return true; return true;
} }
if !attr.is_meta_item_list() { if !attr.is_meta_item_list() {
@ -196,7 +196,7 @@ crate fn collect_derives(cx: &mut ExtCtxt<'_>, attrs: &mut Vec<ast::Attribute>)
} }
let parse_derive_paths = |attr: &ast::Attribute| { let parse_derive_paths = |attr: &ast::Attribute| {
if attr.item.tokens.is_empty() { if attr.get_normal_item().tokens.is_empty() {
return Ok(Vec::new()); return Ok(Vec::new());
} }
parse::parse_in_attr(cx.parse_sess, attr, |p| p.parse_derive_paths()) parse::parse_in_attr(cx.parse_sess, attr, |p| p.parse_derive_paths())

View file

@ -249,9 +249,11 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
for attr in &item.attrs { for attr in &item.attrs {
if is_proc_macro_attr(&attr) { if is_proc_macro_attr(&attr) {
if let Some(prev_attr) = found_attr { if let Some(prev_attr) = found_attr {
let path_str = pprust::path_to_string(&attr.item.path); let prev_item = prev_attr.get_normal_item();
let msg = if attr.item.path.segments[0].ident.name == let item = attr.get_normal_item();
prev_attr.item.path.segments[0].ident.name { let path_str = pprust::path_to_string(&item.path);
let msg = if item.path.segments[0].ident.name ==
prev_item.path.segments[0].ident.name {
format!( format!(
"only one `#[{}]` attribute is allowed on any given function", "only one `#[{}]` attribute is allowed on any given function",
path_str, path_str,
@ -261,7 +263,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
"`#[{}]` and `#[{}]` attributes cannot both be applied "`#[{}]` and `#[{}]` attributes cannot both be applied
to the same function", to the same function",
path_str, path_str,
pprust::path_to_string(&prev_attr.item.path), pprust::path_to_string(&prev_item.path),
) )
}; };
@ -290,7 +292,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
if !is_fn { if !is_fn {
let msg = format!( let msg = format!(
"the `#[{}]` attribute may only be used on bare functions", "the `#[{}]` attribute may only be used on bare functions",
pprust::path_to_string(&attr.item.path), pprust::path_to_string(&attr.get_normal_item().path),
); );
self.handler.span_err(attr.span, &msg); self.handler.span_err(attr.span, &msg);
@ -304,7 +306,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
if !self.is_proc_macro_crate { if !self.is_proc_macro_crate {
let msg = format!( let msg = format!(
"the `#[{}]` attribute is only usable with crates of the `proc-macro` crate type", "the `#[{}]` attribute is only usable with crates of the `proc-macro` crate type",
pprust::path_to_string(&attr.item.path), pprust::path_to_string(&attr.get_normal_item().path),
); );
self.handler.span_err(attr.span, &msg); self.handler.span_err(attr.span, &msg);