diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index c8bb35202f518..230fbb16b87bc 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -997,14 +997,20 @@ impl<'a> LoweringContext<'a> {
         // 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
         // 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 {
-            item: AttrItem {
-                path: attr.path.clone(),
-                tokens: self.lower_token_stream(attr.tokens.clone()),
-            },
+            kind,
             id: attr.id,
             style: attr.style,
-            is_sugared_doc: attr.is_sugared_doc,
             span: attr.span,
         }
     }
diff --git a/src/librustc/ich/impls_syntax.rs b/src/librustc/ich/impls_syntax.rs
index 304735fb1c7ed..aa147462e3d3b 100644
--- a/src/librustc/ich/impls_syntax.rs
+++ b/src/librustc/ich/impls_syntax.rs
@@ -177,7 +177,7 @@ impl<'a> HashStable<StableHashingContext<'a>> for [ast::Attribute] {
         let filtered: SmallVec<[&ast::Attribute; 8]> = self
             .iter()
             .filter(|attr| {
-                !attr.is_sugared_doc &&
+                !attr.is_doc_comment() &&
                 !attr.ident().map_or(false, |ident| hcx.is_ignored_attr(ident.name))
             })
             .collect();
@@ -207,19 +207,16 @@ impl<'a> HashStable<StableHashingContext<'a>> for ast::Attribute {
     fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
         // 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.is_sugared_doc);
-
-        let ast::Attribute {
-            ref item,
-            id: _,
-            style,
-            is_sugared_doc: _,
-            span,
-        } = *self;
-
-        item.hash_stable(hcx, hasher);
-        style.hash_stable(hcx, hasher);
-        span.hash_stable(hcx, hasher);
+        debug_assert!(!self.is_doc_comment());
+
+        let ast::Attribute { kind, id: _, style, span } = self;
+        if let ast::AttrKind::Normal(item) = kind {
+            item.hash_stable(hcx, hasher);
+            style.hash_stable(hcx, hasher);
+            span.hash_stable(hcx, hasher);
+        } else {
+            unreachable!();
+        }
     }
 }
 
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index 30d68fd0bfcb1..d40af615eb181 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -706,7 +706,7 @@ impl EarlyLintPass for DeprecatedAttr {
             }
         }
         if attr.check_name(sym::no_start) || attr.check_name(sym::crate_id) {
-            let path_str = pprust::path_to_string(&attr.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);
             lint_deprecated_attr(cx, attr, &msg, None);
         }
@@ -736,7 +736,7 @@ impl UnusedDocComment {
         let mut sugared_span: Option<Span> = None;
 
         while let Some(attr) = attrs.next() {
-            if attr.is_sugared_doc {
+            if attr.is_doc_comment() {
                 sugared_span = Some(
                     sugared_span.map_or_else(
                         || 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;
             }
 
diff --git a/src/librustc_metadata/link_args.rs b/src/librustc_metadata/link_args.rs
index 4291f3a4ae34e..b40d58a681976 100644
--- a/src/librustc_metadata/link_args.rs
+++ b/src/librustc_metadata/link_args.rs
@@ -11,7 +11,7 @@ crate fn collect(tcx: TyCtxt<'_>) -> Vec<String> {
     tcx.hir().krate().visit_all_item_likes(&mut collector);
 
     for attr in tcx.hir().krate().attrs.iter() {
-        if attr.path == sym::link_args {
+        if attr.has_name(sym::link_args) {
             if let Some(linkarg) = attr.value_str() {
                 collector.add_link_args(&linkarg.as_str());
             }
diff --git a/src/librustc_passes/ast_validation.rs b/src/librustc_passes/ast_validation.rs
index e625334040e0a..d1a801b3006d7 100644
--- a/src/librustc_passes/ast_validation.rs
+++ b/src/librustc_passes/ast_validation.rs
@@ -328,7 +328,7 @@ impl<'a> AstValidator<'a> {
                 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)
             })
-            .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(
                     attr.span,
                     "documentation comments cannot be applied to function parameters"
diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs
index 648c5104b1af7..55f054d0be323 100644
--- a/src/librustc_resolve/build_reduced_graph.rs
+++ b/src/librustc_resolve/build_reduced_graph.rs
@@ -1229,8 +1229,10 @@ impl<'a, 'b> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b> {
     }
 
     fn visit_attribute(&mut self, attr: &'b ast::Attribute) {
-        if !attr.is_sugared_doc && is_builtin_attr(attr) {
-            self.r.builtin_attrs.push((attr.path.segments[0].ident, self.parent_scope));
+        if !attr.is_doc_comment() && is_builtin_attr(attr) {
+            self.r.builtin_attrs.push(
+                (attr.get_normal_item().path.segments[0].ident, self.parent_scope)
+            );
         }
         visit::walk_attribute(self, attr);
     }
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index 0fbd6b0e5d306..3d5a7f26eda55 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -179,7 +179,10 @@ impl<'a> base::Resolver for Resolver<'a> {
 
         let (path, kind, derives, after_derive) = match invoc.kind {
             InvocationKind::Attr { ref attr, ref derives, after_derive, .. } =>
-                (&attr.path, MacroKind::Attr, self.arenas.alloc_ast_paths(derives), after_derive),
+                (&attr.get_normal_item().path,
+                 MacroKind::Attr,
+                 self.arenas.alloc_ast_paths(derives),
+                 after_derive),
             InvocationKind::Bang { ref mac, .. } =>
                 (&mac.path, MacroKind::Bang, &[][..], false),
             InvocationKind::Derive { ref path, .. } =>
diff --git a/src/librustc_save_analysis/lib.rs b/src/librustc_save_analysis/lib.rs
index 1cfb84bb511e4..9408bbe557a21 100644
--- a/src/librustc_save_analysis/lib.rs
+++ b/src/librustc_save_analysis/lib.rs
@@ -885,7 +885,7 @@ impl<'l, 'tcx> SaveContext<'l, 'tcx> {
         for attr in attrs {
             if attr.check_name(sym::doc) {
                 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()));
                     } else {
                         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> {
     attrs.into_iter()
     // Only retain real attributes. Doc comments are lowered separately.
-    .filter(|attr| attr.path != sym::doc)
+    .filter(|attr| !attr.has_name(sym::doc))
     .map(|mut attr| {
         // Remove the surrounding '#[..]' or '#![..]' of the pretty printed
         // attribute. First normalize all inner attribute (#![..]) to outer
diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs
index ffe034759a80c..71a7b52a27ba9 100644
--- a/src/librustc_typeck/collect.rs
+++ b/src/librustc_typeck/collect.rs
@@ -2706,7 +2706,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
     }
 
     codegen_fn_attrs.inline = attrs.iter().fold(InlineAttr::None, |ia, attr| {
-        if attr.path != sym::inline {
+        if !attr.has_name(sym::inline) {
             return ia;
         }
         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| {
-        if attr.path != sym::optimize {
+        if !attr.has_name(sym::optimize) {
             return ia;
         }
         let err = |sp, s| span_err!(tcx.sess.diagnostic(), sp, E0722, "{}", s);
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index e7f7615525292..32c8ca234a0c3 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -26,7 +26,7 @@ use rustc::ty::{self, DefIdTree, TyCtxt, Region, RegionVid, Ty, AdtKind};
 use rustc::ty::fold::TypeFolder;
 use rustc::ty::layout::VariantIdx;
 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::parse::lexer::comments;
 use syntax::source_map::DUMMY_SP;
@@ -859,31 +859,32 @@ impl Attributes {
         let mut cfg = Cfg::True;
         let mut doc_line = 0;
 
-        /// Converts `attr` to a normal `#[doc="foo"]` comment, if it is a
-        /// comment like `///` or `/** */`. (Returns `attr` unchanged for
-        /// non-sugared doc attributes.)
-        pub fn with_desugared_doc<T>(attr: &Attribute, f: impl FnOnce(&Attribute) -> T) -> T {
-            if attr.is_sugared_doc {
-                let comment = attr.value_str().unwrap();
-                let meta = attr::mk_name_value_item_str(
-                    Ident::with_dummy_span(sym::doc),
-                    Symbol::intern(&comments::strip_doc_comment_decoration(&comment.as_str())),
-                    DUMMY_SP,
-                );
-                f(&Attribute {
-                    item: AttrItem { path: meta.path, tokens: meta.kind.tokens(meta.span) },
-                    id: attr.id,
-                    style: attr.style,
-                    is_sugared_doc: true,
-                    span: attr.span,
-                })
-            } else {
-                f(attr)
+        /// If `attr` is a doc comment, strips the leading and (if present)
+        /// trailing comments symbols, e.g. `///`, `/**`, and `*/`. Otherwise,
+        /// returns `attr` unchanged.
+        pub fn with_doc_comment_markers_stripped<T>(
+            attr: &Attribute,
+            f: impl FnOnce(&Attribute) -> T
+        ) -> T {
+            match attr.kind {
+                AttrKind::Normal(_) => {
+                    f(attr)
+                }
+                AttrKind::DocComment(comment) => {
+                    let comment =
+                        Symbol::intern(&comments::strip_doc_comment_decoration(&comment.as_str()));
+                    f(&Attribute {
+                        kind: AttrKind::DocComment(comment),
+                        id: attr.id,
+                        style: attr.style,
+                        span: attr.span,
+                    })
+                }
             }
         }
 
         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 let Some(mi) = attr.meta() {
                         if let Some(value) = mi.value_str() {
@@ -892,7 +893,7 @@ impl Attributes {
                             let line = doc_line;
                             doc_line += value.lines().count();
 
-                            if attr.is_sugared_doc {
+                            if attr.is_doc_comment() {
                                 doc_strings.push(DocFragment::SugaredDoc(line, attr.span, value));
                             } else {
                                 doc_strings.push(DocFragment::RawDoc(line, attr.span, value));
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index 8af38507b48d1..2392b809150d4 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -2190,22 +2190,29 @@ pub struct AttrItem {
 }
 
 /// Metadata associated with an item.
-/// Doc-comments are promoted to attributes that have `is_sugared_doc = true`.
 #[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
 pub struct Attribute {
-    pub item: AttrItem,
+    pub kind: AttrKind,
     pub id: AttrId,
     /// Denotes if the attribute decorates the following construct (outer)
     /// or the construct this attribute is contained within (inner).
     pub style: AttrStyle,
-    pub is_sugared_doc: bool,
     pub span: Span,
 }
 
-// Compatibility impl to avoid churn, consider removing.
-impl std::ops::Deref for Attribute {
-    type Target = AttrItem;
-    fn deref(&self) -> &Self::Target { &self.item }
+#[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.
diff --git a/src/libsyntax/attr/builtin.rs b/src/libsyntax/attr/builtin.rs
index 84c86c9651fcf..787d69f5e9964 100644
--- a/src/libsyntax/attr/builtin.rs
+++ b/src/libsyntax/attr/builtin.rs
@@ -228,7 +228,7 @@ fn find_stability_generic<'a, I>(sess: &ParseSess,
             sym::stable,
             sym::rustc_promotable,
             sym::rustc_allow_const_fn_ptr,
-        ].iter().any(|&s| attr.path == s) {
+        ].iter().any(|&s| attr.has_name(s)) {
             continue // not a stability level
         }
 
@@ -236,10 +236,10 @@ fn find_stability_generic<'a, I>(sess: &ParseSess,
 
         let meta = attr.meta();
 
-        if attr.path == sym::rustc_promotable {
+        if attr.has_name(sym::rustc_promotable) {
             promotable = true;
         }
-        if attr.path == sym::rustc_allow_const_fn_ptr {
+        if attr.has_name(sym::rustc_allow_const_fn_ptr) {
             allow_const_fn_ptr = true;
         }
         // attributes with data
@@ -778,7 +778,7 @@ pub fn find_repr_attrs(sess: &ParseSess, attr: &Attribute) -> Vec<ReprAttr> {
 
     let mut acc = Vec::new();
     let diagnostic = &sess.span_diagnostic;
-    if attr.path == sym::repr {
+    if attr.has_name(sym::repr) {
         if let Some(items) = attr.meta_item_list() {
             mark_used(attr);
             for item in items {
diff --git a/src/libsyntax/attr/mod.rs b/src/libsyntax/attr/mod.rs
index 3e240a855e280..c663995eb8fcb 100644
--- a/src/libsyntax/attr/mod.rs
+++ b/src/libsyntax/attr/mod.rs
@@ -9,7 +9,7 @@ pub use StabilityLevel::*;
 pub use crate::ast::Attribute;
 
 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::{Lit, LitKind, Expr, Item, Local, Stmt, StmtKind, GenericParam};
 use crate::mut_visit::visit_clobber;
@@ -145,12 +145,17 @@ impl NestedMetaItem {
 }
 
 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
     /// 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 {
-        let matches = self.path == name;
+        let matches = self.has_name(name);
         if matches {
             mark_used(self);
         }
@@ -159,10 +164,15 @@ impl Attribute {
 
     /// For a single-segment attribute, returns its name; otherwise, returns `None`.
     pub fn ident(&self) -> Option<Ident> {
-        if self.path.segments.len() == 1 {
-            Some(self.path.segments[0].ident)
-        } else {
-            None
+        match self.kind {
+            AttrKind::Normal(ref item) => {
+                if item.path.segments.len() == 1 {
+                    Some(item.path.segments[0].ident)
+                } else {
+                    None
+                }
+            }
+            AttrKind::DocComment(_) => Some(Ident::new(sym::doc, self.span)),
         }
     }
     pub fn name_or_empty(&self) -> Symbol {
@@ -170,18 +180,32 @@ impl Attribute {
     }
 
     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>> {
-        match self.meta() {
-            Some(MetaItem { kind: MetaItemKind::List(list), .. }) => Some(list),
-            _ => None
+        match self.kind {
+            AttrKind::Normal(ref item) => {
+                match item.meta(self.span) {
+                    Some(MetaItem { kind: MetaItemKind::List(list), .. }) => Some(list),
+                    _ => None
+                }
+            }
+            AttrKind::DocComment(_) => None,
         }
     }
 
     pub fn is_word(&self) -> bool {
-        self.tokens.is_empty()
+        if let AttrKind::Normal(item) = &self.kind {
+            item.tokens.is_empty()
+        } else {
+            false
+        }
     }
 
     pub fn is_meta_item_list(&self) -> bool {
@@ -275,17 +299,49 @@ impl AttrItem {
 }
 
 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.
     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> {
-        Ok(MetaItem {
-            path: self.path.clone(),
-            kind: parse::parse_in_attr(sess, self, |p| p.parse_meta_item_kind())?,
-            span: self.span,
-        })
+        match self.kind {
+            AttrKind::Normal(ref item) => {
+                Ok(MetaItem {
+                    path: item.path.clone(),
+                    kind: parse::parse_in_attr(sess, self, |parser| parser.parse_meta_item_kind())?,
+                    span: self.span,
+                })
+            }
+            AttrKind::DocComment(comment) => {
+                Ok(mk_name_value_item_str(Ident::new(sym::doc, self.span), comment, self.span))
+            }
+        }
     }
 }
 
@@ -327,10 +383,9 @@ crate fn mk_attr_id() -> AttrId {
 
 pub fn mk_attr(style: AttrStyle, path: Path, tokens: TokenStream, span: Span) -> Attribute {
     Attribute {
-        item: AttrItem { path, tokens },
+        kind: AttrKind::Normal(AttrItem { path, tokens }),
         id: mk_attr_id(),
         style,
-        is_sugared_doc: false,
         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)
 }
 
-pub fn mk_sugared_doc_attr(text: 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);
+pub fn mk_doc_comment(comment: Symbol, span: Span) -> Attribute {
     Attribute {
-        item: AttrItem {
-            path: Path::from_ident(Ident::with_dummy_span(sym::doc).with_span_pos(span)),
-            tokens: MetaItemKind::NameValue(lit).tokens(span),
-        },
+        kind: AttrKind::DocComment(comment),
         id: mk_attr_id(),
-        style,
-        is_sugared_doc: true,
+        style: doc_comment_style(&comment.as_str()),
         span,
     }
 }
diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs
index 6003fd1d2861a..5f89ed36e2a7d 100644
--- a/src/libsyntax/config.rs
+++ b/src/libsyntax/config.rs
@@ -93,10 +93,10 @@ impl<'a> StripUnconfigured<'a> {
     /// is in the original source file. Gives a compiler error if the syntax of
     /// the attribute is incorrect.
     fn process_cfg_attr(&mut self, attr: ast::Attribute) -> Vec<ast::Attribute> {
-        if attr.path != sym::cfg_attr {
+        if !attr.has_name(sym::cfg_attr) {
             return vec![attr];
         }
-        if attr.tokens.is_empty() {
+        if attr.get_normal_item().tokens.is_empty() {
             self.sess.span_diagnostic
                 .struct_span_err(
                     attr.span,
@@ -136,10 +136,9 @@ impl<'a> StripUnconfigured<'a> {
             //  `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
             expanded_attrs.into_iter()
             .flat_map(|(item, span)| self.process_cfg_attr(ast::Attribute {
-                item,
+                kind: ast::AttrKind::Normal(item),
                 id: attr::mk_attr_id(),
                 style: attr.style,
-                is_sugared_doc: false,
                 span,
             }))
             .collect()
@@ -212,7 +211,7 @@ impl<'a> StripUnconfigured<'a> {
                                       GateIssue::Language,
                                       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 `//`.");
             }
 
diff --git a/src/libsyntax/feature_gate/check.rs b/src/libsyntax/feature_gate/check.rs
index d9cc5f6c169b6..b7e75ff3a7e38 100644
--- a/src/libsyntax/feature_gate/check.rs
+++ b/src/libsyntax/feature_gate/check.rs
@@ -329,7 +329,8 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
             // `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
             Some((name, _, template, _)) if name != sym::rustc_dummy =>
                 check_builtin_attribute(self.parse_sess, attr, name, template),
-            _ => if let Some(TokenTree::Token(token)) = attr.tokens.trees().next() {
+            _ => if let Some(TokenTree::Token(token)) =
+                    attr.get_normal_item().tokens.trees().next() {
                 if token == token::Eq {
                     // All key-value attributes are restricted to meta-item syntax.
                     attr.parse_meta(self.parse_sess).map_err(|mut err| err.emit()).ok();
diff --git a/src/libsyntax/mut_visit.rs b/src/libsyntax/mut_visit.rs
index 60ee17d09b755..7261601e14491 100644
--- a/src/libsyntax/mut_visit.rs
+++ b/src/libsyntax/mut_visit.rs
@@ -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) {
-    let Attribute { item: AttrItem { path, tokens }, id: _, style: _, is_sugared_doc: _, span }
-        = attr;
-    vis.visit_path(path);
-    vis.visit_tts(tokens);
+    let Attribute { kind, id: _, style: _, span } = attr;
+    match kind {
+        AttrKind::Normal(AttrItem { path, tokens }) => {
+            vis.visit_path(path);
+            vis.visit_tts(tokens);
+        }
+        AttrKind::DocComment(_) => {}
+    }
     vis.visit_span(span);
 }
 
diff --git a/src/libsyntax/parse/mod.rs b/src/libsyntax/parse/mod.rs
index 6d8ecdf805b01..b688dce87c138 100644
--- a/src/libsyntax/parse/mod.rs
+++ b/src/libsyntax/parse/mod.rs
@@ -287,7 +287,7 @@ pub fn parse_in_attr<'a, T>(
 ) -> PResult<'a, T> {
     let mut parser = Parser::new(
         sess,
-        attr.tokens.clone(),
+        attr.get_normal_item().tokens.clone(),
         None,
         false,
         false,
@@ -393,18 +393,22 @@ fn prepend_attrs(
 
         let source = pprust::attribute_to_string(attr);
         let macro_filename = FileName::macro_expansion_source_code(&source);
-        if attr.is_sugared_doc {
-            let stream = parse_stream_from_source_str(macro_filename, source, sess, Some(span));
-            builder.push(stream);
-            continue
-        }
+
+        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));
+                builder.push(stream);
+                continue
+            }
+        };
 
         // synthesize # [ $path $tokens ] manually here
         let mut brackets = tokenstream::TokenStreamBuilder::new();
 
         // For simple paths, push the identifier directly
-        if attr.path.segments.len() == 1 && attr.path.segments[0].args.is_none() {
-            let ident = attr.path.segments[0].ident;
+        if item.path.segments.len() == 1 && item.path.segments[0].args.is_none() {
+            let ident = item.path.segments[0].ident;
             let token = token::Ident(ident.name, ident.as_str().starts_with("r#"));
             brackets.push(tokenstream::TokenTree::token(token, ident.span));
 
@@ -415,7 +419,7 @@ fn prepend_attrs(
             brackets.push(stream);
         }
 
-        brackets.push(attr.tokens.clone());
+        brackets.push(item.tokens.clone());
 
         // The span we list here for `#` and for `[ ... ]` are both wrong in
         // that it encompasses more than each token, but it hopefully is "good
diff --git a/src/libsyntax/parse/parser/attr.rs b/src/libsyntax/parse/parser/attr.rs
index 188a144cac9de..1c292661f2440 100644
--- a/src/libsyntax/parse/parser/attr.rs
+++ b/src/libsyntax/parse/parser/attr.rs
@@ -43,7 +43,7 @@ impl<'a> Parser<'a> {
                     just_parsed_doc_comment = false;
                 }
                 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 {
                         let mut err = self.fatal("expected outer doc comment");
                         err.note("inner doc comments like this (starting with \
@@ -150,10 +150,9 @@ impl<'a> Parser<'a> {
         };
 
         Ok(ast::Attribute {
-            item,
+            kind: ast::AttrKind::Normal(item),
             id: attr::mk_attr_id(),
             style,
-            is_sugared_doc: false,
             span,
         })
     }
@@ -229,7 +228,7 @@ impl<'a> Parser<'a> {
                 }
                 token::DocComment(s) => {
                     // 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 {
                         attrs.push(attr);
                         self.bump();
diff --git a/src/libsyntax/parse/parser/item.rs b/src/libsyntax/parse/parser/item.rs
index 5b60e7e6dba05..cc6235c6fc772 100644
--- a/src/libsyntax/parse/parser/item.rs
+++ b/src/libsyntax/parse/parser/item.rs
@@ -3,8 +3,8 @@ use super::diagnostics::{Error, dummy_arg, ConsumeClosingDelim};
 
 use crate::maybe_whole;
 use crate::ptr::P;
-use crate::ast::{self, DUMMY_NODE_ID, Ident, Attribute, AttrStyle, AnonConst, Item, ItemKind};
-use crate::ast::{ImplItem, ImplItemKind, TraitItem, TraitItemKind, UseTree, UseTreeKind};
+use crate::ast::{self, DUMMY_NODE_ID, Ident, Attribute, AttrKind, AttrStyle, AnonConst, Item};
+use crate::ast::{ItemKind, ImplItem, ImplItemKind, TraitItem, TraitItemKind, UseTree, UseTreeKind};
 use crate::ast::{PathSegment, IsAuto, Constness, IsAsync, Unsafety, Defaultness};
 use crate::ast::{Visibility, VisibilityKind, Mutability, FnHeader, ForeignItem, ForeignItemKind};
 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.
     fn expected_item_err(&mut self, attrs: &[Attribute]) -> PResult<'a,  ()> {
         let message = match attrs.last() {
-            Some(&Attribute { is_sugared_doc: true, .. }) => "expected item after doc comment",
-            _ => "expected item after attributes",
+            Some(&Attribute { kind: AttrKind::DocComment(_), .. }) =>
+                "expected item after doc comment",
+            _ =>
+                "expected item after attributes",
         };
 
         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(err)
diff --git a/src/libsyntax/parse/tests.rs b/src/libsyntax/parse/tests.rs
index 3bdb9227b4edd..169eb954efadf 100644
--- a/src/libsyntax/parse/tests.rs
+++ b/src/libsyntax/parse/tests.rs
@@ -246,7 +246,7 @@ let mut fflags: c_int = wb();
         let source = "/// doc comment\r\n/// line 2\r\nfn foo() {}".to_string();
         let item = parse_item_from_source_str(name_2, source, &sess)
             .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<_>>();
         let b: &[_] = &["/// doc comment".to_string(), "/// line 2".to_string()];
         assert_eq!(&docs[..], b);
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index 74ab5c790193d..c8afe8a1ff459 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -622,16 +622,19 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
             self.hardbreak_if_not_bol();
         }
         self.maybe_print_comment(attr.span.lo());
-        if attr.is_sugared_doc {
-            self.word(attr.value_str().unwrap().to_string());
-            self.hardbreak()
-        } else {
-            match attr.style {
-                ast::AttrStyle::Inner => self.word("#!["),
-                ast::AttrStyle::Outer => self.word("#["),
+        match attr.kind {
+            ast::AttrKind::Normal(ref item) => {
+                match attr.style {
+                    ast::AttrStyle::Inner => self.word("#!["),
+                    ast::AttrStyle::Outer => self.word("#["),
+                }
+                self.print_attr_item(&item, attr.span);
+                self.word("]");
+            }
+            ast::AttrKind::DocComment(comment) => {
+                self.word(comment.to_string());
+                self.hardbreak()
             }
-            self.print_attr_item(&attr.item, attr.span);
-            self.word("]");
         }
     }
 
diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs
index a36783e2b642d..117787d08c750 100644
--- a/src/libsyntax/visit.rs
+++ b/src/libsyntax/visit.rs
@@ -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) {
-    visitor.visit_tts(attr.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) {
diff --git a/src/libsyntax_expand/expand.rs b/src/libsyntax_expand/expand.rs
index da70fdbb0f308..7dbc778701006 100644
--- a/src/libsyntax_expand/expand.rs
+++ b/src/libsyntax_expand/expand.rs
@@ -419,7 +419,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                     }
 
                     let mut item = self.fully_configure(item);
-                    item.visit_attrs(|attrs| attrs.retain(|a| a.path != sym::derive));
+                    item.visit_attrs(|attrs| attrs.retain(|a| !a.has_name(sym::derive)));
                     let mut helper_attrs = Vec::new();
                     let mut has_copy = false;
                     for ext in exts {
@@ -634,9 +634,10 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                         | Annotatable::Variant(..)
                             => panic!("unexpected annotatable"),
                     })), 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);
-                    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) => {
                     match attr.parse_meta(self.cx.parse_sess) {
@@ -974,7 +975,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                        -> Option<ast::Attribute> {
         let attr = attrs.iter()
                         .position(|a| {
-                            if a.path == sym::derive {
+                            if a.has_name(sym::derive) {
                                 *after_derive = true;
                             }
                             !attr::is_known(a) && !is_builtin_attr(a)
@@ -982,7 +983,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                         .map(|i| attrs.remove(i));
         if let Some(attr) = &attr {
             if !self.cx.ecfg.custom_inner_attributes() &&
-               attr.style == ast::AttrStyle::Inner && attr.path != sym::test {
+               attr.style == ast::AttrStyle::Inner && !attr.has_name(sym::test) {
                 emit_feature_err(&self.cx.parse_sess, sym::custom_inner_attributes,
                                  attr.span, GateIssue::Language,
                                  "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);
 
             // macros are expanded before any lint passes so this warning has to be hardcoded
-            if attr.path == sym::derive {
+            if attr.has_name(sym::derive) {
                 self.cx.struct_span_warn(attr.span, "`#[derive]` does nothing on macro invocations")
                     .note("this may become a hard error in a future release")
                     .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);
             *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,
                 id: at.id,
                 style: at.style,
-                is_sugared_doc: false,
             };
         } else {
             noop_visit_attribute(at, self)
diff --git a/src/libsyntax_expand/proc_macro.rs b/src/libsyntax_expand/proc_macro.rs
index bda9478ce96a9..1f4c481e3ea04 100644
--- a/src/libsyntax_expand/proc_macro.rs
+++ b/src/libsyntax_expand/proc_macro.rs
@@ -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> {
     let mut result = Vec::new();
     attrs.retain(|attr| {
-        if attr.path != sym::derive {
+        if !attr.has_name(sym::derive) {
             return true;
         }
         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| {
-            if attr.tokens.is_empty() {
+            if attr.get_normal_item().tokens.is_empty() {
                 return Ok(Vec::new());
             }
             parse::parse_in_attr(cx.parse_sess, attr, |p| p.parse_derive_paths())
diff --git a/src/libsyntax_ext/proc_macro_harness.rs b/src/libsyntax_ext/proc_macro_harness.rs
index fc4a7a0a0fe47..792c97d8508fe 100644
--- a/src/libsyntax_ext/proc_macro_harness.rs
+++ b/src/libsyntax_ext/proc_macro_harness.rs
@@ -249,9 +249,11 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
         for attr in &item.attrs {
             if is_proc_macro_attr(&attr) {
                 if let Some(prev_attr) = found_attr {
-                    let path_str = pprust::path_to_string(&attr.path);
-                    let msg = if attr.path.segments[0].ident.name ==
-                                 prev_attr.path.segments[0].ident.name {
+                    let prev_item = prev_attr.get_normal_item();
+                    let item = attr.get_normal_item();
+                    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!(
                             "only one `#[{}]` attribute is allowed on any given function",
                             path_str,
@@ -261,7 +263,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
                             "`#[{}]` and `#[{}]` attributes cannot both be applied
                             to the same function",
                             path_str,
-                            pprust::path_to_string(&prev_attr.path),
+                            pprust::path_to_string(&prev_item.path),
                         )
                     };
 
@@ -290,7 +292,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
         if !is_fn {
             let msg = format!(
                 "the `#[{}]` attribute may only be used on bare functions",
-                pprust::path_to_string(&attr.path),
+                pprust::path_to_string(&attr.get_normal_item().path),
             );
 
             self.handler.span_err(attr.span, &msg);
@@ -304,7 +306,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
         if !self.is_proc_macro_crate {
             let msg = format!(
                 "the `#[{}]` attribute is only usable with crates of the `proc-macro` crate type",
-                pprust::path_to_string(&attr.path),
+                pprust::path_to_string(&attr.get_normal_item().path),
             );
 
             self.handler.span_err(attr.span, &msg);