about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2018-05-03 11:52:03 +0000
committerbors <bors@rust-lang.org>2018-05-03 11:52:03 +0000
commitd68b0eceaaf719a4702ab13a7ca62dea5d966082 (patch)
treec7a351327d19c398532f139444d4cc652070318d
parent698b956a9fca9688632d617dd6d73cae834237a3 (diff)
parent84f450866041e0269875acb1350920308cdc109f (diff)
downloadrust-d68b0eceaaf719a4702ab13a7ca62dea5d966082.tar.gz
rust-d68b0eceaaf719a4702ab13a7ca62dea5d966082.zip
Auto merge of #50030 - flip1995:rfc2103, r=petrochenkov
Implement tool_attributes feature (RFC 2103)

cc #44690

This is currently just a rebased and compiling (hopefully) version of #47773.

Let's see if travis likes this. I will add the implementation for `tool_lints` this week.
-rw-r--r--src/doc/unstable-book/src/language-features/tool-attributes.md26
-rw-r--r--src/librustc/hir/check_attr.rs7
-rw-r--r--src/librustc/ich/impls_syntax.rs21
-rw-r--r--src/librustc/lint/levels.rs6
-rw-r--r--src/librustc/middle/stability.rs2
-rw-r--r--src/librustc/session/config.rs2
-rw-r--r--src/librustc/traits/on_unimplemented.rs2
-rw-r--r--src/librustc_driver/lib.rs2
-rw-r--r--src/librustc_incremental/assert_dep_graph.rs2
-rw-r--r--src/librustc_lint/builtin.rs3
-rw-r--r--src/librustc_lint/lib.rs1
-rw-r--r--src/librustc_lint/unused.rs3
-rw-r--r--src/librustc_resolve/build_reduced_graph.rs2
-rw-r--r--src/librustc_resolve/macros.rs4
-rw-r--r--src/librustdoc/clean/cfg.rs242
-rw-r--r--src/librustdoc/clean/mod.rs2
-rw-r--r--src/librustdoc/html/render.rs4
-rw-r--r--src/libsyntax/ast.rs4
-rw-r--r--src/libsyntax/attr.rs150
-rw-r--r--src/libsyntax/diagnostic_list.rs1
-rw-r--r--src/libsyntax/ext/expand.rs8
-rw-r--r--src/libsyntax/feature_gate.rs37
-rw-r--r--src/libsyntax/parse/attr.rs7
-rw-r--r--src/libsyntax/parse/parser.rs6
-rw-r--r--src/libsyntax/print/pprust.rs37
-rw-r--r--src/libsyntax_ext/deriving/custom.rs8
-rw-r--r--src/libsyntax_ext/deriving/generic/mod.rs2
-rw-r--r--src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs2
-rw-r--r--src/test/compile-fail/feature-gate-tool_attributes.rs15
-rw-r--r--src/test/compile-fail/unknown-tool-name.rs16
-rw-r--r--src/test/compile-fail/unknown_tool_attributes-1.rs18
-rw-r--r--src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs2
-rw-r--r--src/test/run-pass/tool_attributes.rs23
-rw-r--r--src/test/ui/feature-gate-tool_attributes.rs15
-rw-r--r--src/test/ui/feature-gate-tool_attributes.stderr11
35 files changed, 391 insertions, 302 deletions
diff --git a/src/doc/unstable-book/src/language-features/tool-attributes.md b/src/doc/unstable-book/src/language-features/tool-attributes.md
new file mode 100644
index 00000000000..15fc84a3e2a
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/tool-attributes.md
@@ -0,0 +1,26 @@
+# `tool_attributes`
+
+The tracking issue for this feature is: [#44690]
+
+[#44690]: https://github.com/rust-lang/rust/issues/44690
+
+------------------------
+
+Tool attributes let you use scoped attributes to control the behavior
+of certain tools.
+
+Currently tool names which can be appear in scoped attributes are restricted to
+`clippy` and `rustfmt`.
+
+## An example
+
+```rust
+#![feature(tool_attributes)]
+
+#[rustfmt::skip]
+fn foo() { println!("hello, world"); }
+
+fn main() {
+    foo();
+}
+```
diff --git a/src/librustc/hir/check_attr.rs b/src/librustc/hir/check_attr.rs
index cad6ff8ae9f..24a1256c9d3 100644
--- a/src/librustc/hir/check_attr.rs
+++ b/src/librustc/hir/check_attr.rs
@@ -153,10 +153,7 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
         // ```
         let hints: Vec<_> = item.attrs
             .iter()
-            .filter(|attr| match attr.name() {
-                Some(name) => name == "repr",
-                None => false,
-            })
+            .filter(|attr| attr.name() == "repr")
             .filter_map(|attr| attr.meta_item_list())
             .flat_map(|hints| hints)
             .collect();
@@ -311,7 +308,7 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
 
     fn check_used(&self, item: &hir::Item, target: Target) {
         for attr in &item.attrs {
-            if attr.name().map(|name| name == "used").unwrap_or(false) && target != Target::Static {
+            if attr.name() == "used" && target != Target::Static {
                 self.tcx.sess
                     .span_err(attr.span, "attribute must be applied to a `static` variable");
             }
diff --git a/src/librustc/ich/impls_syntax.rs b/src/librustc/ich/impls_syntax.rs
index c1e86473996..1cf9b7bf478 100644
--- a/src/librustc/ich/impls_syntax.rs
+++ b/src/librustc/ich/impls_syntax.rs
@@ -199,8 +199,7 @@ impl<'a> HashStable<StableHashingContext<'a>> for [ast::Attribute] {
         let filtered: AccumulateVec<[&ast::Attribute; 8]> = self
             .iter()
             .filter(|attr| {
-                !attr.is_sugared_doc &&
-                attr.name().map(|name| !hcx.is_ignored_attr(name)).unwrap_or(true)
+                !attr.is_sugared_doc && !hcx.is_ignored_attr(attr.name())
             })
             .collect();
 
@@ -211,12 +210,23 @@ impl<'a> HashStable<StableHashingContext<'a>> for [ast::Attribute] {
     }
 }
 
+impl<'a> HashStable<StableHashingContext<'a>> for ast::Path {
+    fn hash_stable<W: StableHasherResult>(&self,
+                                          hcx: &mut StableHashingContext<'a>,
+                                          hasher: &mut StableHasher<W>) {
+        self.segments.len().hash_stable(hcx, hasher);
+        for segment in &self.segments {
+            segment.ident.name.hash_stable(hcx, hasher);
+        }
+    }
+}
+
 impl<'a> HashStable<StableHashingContext<'a>> for ast::Attribute {
     fn hash_stable<W: StableHasherResult>(&self,
                                           hcx: &mut StableHashingContext<'a>,
                                           hasher: &mut StableHasher<W>) {
         // Make sure that these have been filtered out.
-        debug_assert!(self.name().map(|name| !hcx.is_ignored_attr(name)).unwrap_or(true));
+        debug_assert!(!hcx.is_ignored_attr(self.name()));
         debug_assert!(!self.is_sugared_doc);
 
         let ast::Attribute {
@@ -229,10 +239,7 @@ impl<'a> HashStable<StableHashingContext<'a>> for ast::Attribute {
         } = *self;
 
         style.hash_stable(hcx, hasher);
-        path.segments.len().hash_stable(hcx, hasher);
-        for segment in &path.segments {
-            segment.ident.name.hash_stable(hcx, hasher);
-        }
+        path.hash_stable(hcx, hasher);
         for tt in tokens.trees() {
             tt.hash_stable(hcx, hasher);
         }
diff --git a/src/librustc/lint/levels.rs b/src/librustc/lint/levels.rs
index e8b536d5267..d158f52c643 100644
--- a/src/librustc/lint/levels.rs
+++ b/src/librustc/lint/levels.rs
@@ -198,7 +198,7 @@ impl<'a> LintLevelsBuilder<'a> {
                       "malformed lint attribute");
         };
         for attr in attrs {
-            let level = match attr.name().and_then(|name| Level::from_str(&name.as_str())) {
+            let level = match Level::from_str(&attr.name().as_str()) {
                 None => continue,
                 Some(lvl) => lvl,
             };
@@ -221,7 +221,7 @@ impl<'a> LintLevelsBuilder<'a> {
                         continue
                     }
                 };
-                let name = word.ident.name;
+                let name = word.name();
                 match store.check_lint_name(&name.as_str()) {
                     CheckLintNameResult::Ok(ids) => {
                         let src = LintSource::Node(name, li.span);
@@ -260,7 +260,7 @@ impl<'a> LintLevelsBuilder<'a> {
                                                 Some(li.span.into()),
                                                 &msg);
                         if name.as_str().chars().any(|c| c.is_uppercase()) {
-                            let name_lower = name.as_str().to_lowercase();
+                            let name_lower = name.as_str().to_lowercase().to_string();
                             if let CheckLintNameResult::NoLint =
                                     store.check_lint_name(&name_lower) {
                                 db.emit();
diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs
index 328b2db2b58..279908d2b67 100644
--- a/src/librustc/middle/stability.rs
+++ b/src/librustc/middle/stability.rs
@@ -205,7 +205,7 @@ impl<'a, 'tcx: 'a> Annotator<'a, 'tcx> {
         } else {
             // Emit errors for non-staged-api crates.
             for attr in attrs {
-                let tag = unwrap_or!(attr.name(), continue);
+                let tag = attr.name();
                 if tag == "unstable" || tag == "stable" || tag == "rustc_deprecated" {
                     attr::mark_used(attr);
                     self.tcx.sess.span_err(attr.span(), "stability attributes may not be used \
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 59b40e9e2dc..dc97c941567 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -1683,7 +1683,7 @@ pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> ast::CrateConfig {
                 early_error(ErrorOutputType::default(), &msg)
             }
 
-            (meta_item.ident.name, meta_item.value_str())
+            (meta_item.name(), meta_item.value_str())
         })
         .collect::<ast::CrateConfig>()
 }
diff --git a/src/librustc/traits/on_unimplemented.rs b/src/librustc/traits/on_unimplemented.rs
index d1fd70ae02d..3cf7af30b3d 100644
--- a/src/librustc/traits/on_unimplemented.rs
+++ b/src/librustc/traits/on_unimplemented.rs
@@ -190,7 +190,7 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedDirective {
         for command in self.subcommands.iter().chain(Some(self)).rev() {
             if let Some(ref condition) = command.condition {
                 if !attr::eval_condition(condition, &tcx.sess.parse_sess, &mut |c| {
-                    options.contains(&(c.ident.name.as_str().to_string(),
+                    options.contains(&(c.name().as_str().to_string(),
                                       match c.value_str().map(|s| s.as_str().to_string()) {
                                           Some(s) => Some(s),
                                           None => None
diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs
index 3f166daac71..a1052ca6c3c 100644
--- a/src/librustc_driver/lib.rs
+++ b/src/librustc_driver/lib.rs
@@ -1060,7 +1060,7 @@ impl RustcDefaultCalls {
                     let mut cfgs = Vec::new();
                     for &(name, ref value) in sess.parse_sess.config.iter() {
                         let gated_cfg = GatedCfg::gate(&ast::MetaItem {
-                            ident: ast::Ident::with_empty_ctxt(name),
+                            ident: ast::Path::from_ident(name.to_ident()),
                             node: ast::MetaItemKind::Word,
                             span: DUMMY_SP,
                         });
diff --git a/src/librustc_incremental/assert_dep_graph.rs b/src/librustc_incremental/assert_dep_graph.rs
index 57311a7b588..38e891008f7 100644
--- a/src/librustc_incremental/assert_dep_graph.rs
+++ b/src/librustc_incremental/assert_dep_graph.rs
@@ -110,7 +110,7 @@ impl<'a, 'tcx> IfThisChanged<'a, 'tcx> {
         for list_item in attr.meta_item_list().unwrap_or_default() {
             match list_item.word() {
                 Some(word) if value.is_none() =>
-                    value = Some(word.ident.name),
+                    value = Some(word.name()),
                 _ =>
                     // FIXME better-encapsulate meta_item (don't directly access `node`)
                     span_bug!(list_item.span(), "unexpected meta-item {:?}", list_item.node),
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index 91ce6f3854a..f06062fa4ac 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -675,9 +675,8 @@ impl LintPass for DeprecatedAttr {
 
 impl EarlyLintPass for DeprecatedAttr {
     fn check_attribute(&mut self, cx: &EarlyContext, attr: &ast::Attribute) {
-        let name = unwrap_or!(attr.name(), return);
         for &&(n, _, ref g) in &self.depr_attrs {
-            if name == n {
+            if attr.name() == n {
                 if let &AttributeGate::Gated(Stability::Deprecated(link),
                                              ref name,
                                              ref reason,
diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs
index 65b340d6568..4f6d23dce6d 100644
--- a/src/librustc_lint/lib.rs
+++ b/src/librustc_lint/lib.rs
@@ -30,7 +30,6 @@
 #![feature(quote)]
 #![feature(rustc_diagnostic_macros)]
 
-#[macro_use]
 extern crate syntax;
 #[macro_use]
 extern crate rustc;
diff --git a/src/librustc_lint/unused.rs b/src/librustc_lint/unused.rs
index 9e1b75ba336..8df40b62ddd 100644
--- a/src/librustc_lint/unused.rs
+++ b/src/librustc_lint/unused.rs
@@ -192,8 +192,6 @@ impl LintPass for UnusedAttributes {
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedAttributes {
     fn check_attribute(&mut self, cx: &LateContext, attr: &ast::Attribute) {
         debug!("checking attribute: {:?}", attr);
-        let name = unwrap_or!(attr.name(), return);
-
         // Note that check_name() marks the attribute as used if it matches.
         for &(ref name, ty, _) in BUILTIN_ATTRIBUTES {
             match ty {
@@ -213,6 +211,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedAttributes {
             }
         }
 
+        let name = attr.name();
         if !attr::is_used(attr) {
             debug!("Emitting warning for: {:?}", attr);
             cx.span_lint(UNUSED_ATTRIBUTES, attr.span, "unused attribute");
diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs
index 45fd82b33bd..ef5cc958283 100644
--- a/src/librustc_resolve/build_reduced_graph.rs
+++ b/src/librustc_resolve/build_reduced_graph.rs
@@ -706,7 +706,7 @@ impl<'a> Resolver<'a> {
                 match attr.meta_item_list() {
                     Some(names) => for attr in names {
                         if let Some(word) = attr.word() {
-                            imports.imports.push((word.ident.name, attr.span()));
+                            imports.imports.push((word.name(), attr.span()));
                         } else {
                             span_err!(self.session, attr.span(), E0466, "bad macro import");
                         }
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index 3d20f922ac0..4afc621ad8b 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -209,7 +209,7 @@ impl<'a> base::Resolver for Resolver<'a> {
     fn find_legacy_attr_invoc(&mut self, attrs: &mut Vec<ast::Attribute>, allow_derive: bool)
                               -> Option<ast::Attribute> {
         for i in 0..attrs.len() {
-            let name = unwrap_or!(attrs[i].name(), continue);
+            let name = attrs[i].name();
 
             if self.session.plugin_attributes.borrow().iter()
                     .any(|&(ref attr_nm, _)| name == &**attr_nm) {
@@ -231,7 +231,7 @@ impl<'a> base::Resolver for Resolver<'a> {
 
         // Check for legacy derives
         for i in 0..attrs.len() {
-            let name = unwrap_or!(attrs[i].name(), continue);
+            let name = attrs[i].name();
 
             if name == "derive" {
                 let result = attrs[i].parse_list(&self.session.parse_sess, |parser| {
diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs
index 7f89b3e6b3a..d5e0f95ddf4 100644
--- a/src/librustdoc/clean/cfg.rs
+++ b/src/librustdoc/clean/cfg.rs
@@ -67,7 +67,7 @@ impl Cfg {
     /// If the content is not properly formatted, it will return an error indicating what and where
     /// the error is.
     pub fn parse(cfg: &MetaItem) -> Result<Cfg, InvalidCfgError> {
-        let name = cfg.ident.name;
+        let name = cfg.name();
         match cfg.node {
             MetaItemKind::Word => Ok(Cfg::Cfg(name, None)),
             MetaItemKind::NameValue(ref lit) => match lit.node {
@@ -436,6 +436,42 @@ mod test {
         Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value)))
     }
 
+    fn dummy_meta_item_word(name: &str) -> MetaItem {
+        MetaItem {
+            ident: Path::from_ident(Ident::from_str(name)),
+            node: MetaItemKind::Word,
+            span: DUMMY_SP,
+        }
+    }
+
+    macro_rules! dummy_meta_item_list {
+        ($name:ident, [$($list:ident),* $(,)*]) => {
+            MetaItem {
+                ident: Path::from_ident(Ident::from_str(stringify!($name))),
+                node: MetaItemKind::List(vec![
+                    $(
+                        dummy_spanned(NestedMetaItemKind::MetaItem(
+                            dummy_meta_item_word(stringify!($list)),
+                        )),
+                    )*
+                ]),
+                span: DUMMY_SP,
+            }
+        };
+
+        ($name:ident, [$($list:expr),* $(,)*]) => {
+            MetaItem {
+                ident: Path::from_ident(Ident::from_str(stringify!($name))),
+                node: MetaItemKind::List(vec![
+                    $(
+                        dummy_spanned(NestedMetaItemKind::MetaItem($list)),
+                    )*
+                ]),
+                span: DUMMY_SP,
+            }
+        };
+    }
+
     #[test]
     fn test_cfg_not() {
         with_globals(|| {
@@ -561,15 +597,11 @@ mod test {
     #[test]
     fn test_parse_ok() {
         with_globals(|| {
-            let mi = MetaItem {
-                ident: Ident::from_str("all"),
-                node: MetaItemKind::Word,
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_word("all");
             assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
 
             let mi = MetaItem {
-                ident: Ident::from_str("all"),
+                ident: Path::from_ident(Ident::from_str("all")),
                 node: MetaItemKind::NameValue(dummy_spanned(LitKind::Str(
                     Symbol::intern("done"),
                     StrStyle::Cooked,
@@ -578,111 +610,24 @@ mod test {
             };
             assert_eq!(Cfg::parse(&mi), Ok(name_value_cfg("all", "done")));
 
-            let mi = MetaItem {
-                ident: Ident::from_str("all"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("a"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("b"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(all, [a, b]);
             assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b")));
 
-            let mi = MetaItem {
-                ident: Ident::from_str("any"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("a"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("b"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(any, [a, b]);
             assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b")));
 
-            let mi = MetaItem {
-                ident: Ident::from_str("not"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("a"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(not, [a]);
             assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a")));
 
-            let mi = MetaItem {
-                ident: Ident::from_str("not"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("any"),
-                        node: MetaItemKind::List(vec![
-                            dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                                ident: Ident::from_str("a"),
-                                node: MetaItemKind::Word,
-                                span: DUMMY_SP,
-                            })),
-                            dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                                ident: Ident::from_str("all"),
-                                node: MetaItemKind::List(vec![
-                                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                                        ident: Ident::from_str("b"),
-                                        node: MetaItemKind::Word,
-                                        span: DUMMY_SP,
-                                    })),
-                                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                                        ident: Ident::from_str("c"),
-                                        node: MetaItemKind::Word,
-                                        span: DUMMY_SP,
-                                    })),
-                                ]),
-                                span: DUMMY_SP,
-                            })),
-                        ]),
-                        span: DUMMY_SP,
-                    })),
+            let mi = dummy_meta_item_list!(not, [
+                dummy_meta_item_list!(any, [
+                    dummy_meta_item_word("a"),
+                    dummy_meta_item_list!(all, [b, c]),
                 ]),
-                span: DUMMY_SP,
-            };
+            ]);
             assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c")))));
 
-            let mi = MetaItem {
-                ident: Ident::from_str("all"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("a"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("b"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("c"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(all, [a, b, c]);
             assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c")));
         })
     }
@@ -691,97 +636,36 @@ mod test {
     fn test_parse_err() {
         with_globals(|| {
             let mi = MetaItem {
-                ident: Ident::from_str("foo"),
+                ident: Path::from_ident(Ident::from_str("foo")),
                 node: MetaItemKind::NameValue(dummy_spanned(LitKind::Bool(false))),
                 span: DUMMY_SP,
             };
             assert!(Cfg::parse(&mi).is_err());
 
-            let mi = MetaItem {
-                ident: Ident::from_str("not"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("a"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("b"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(not, [a, b]);
             assert!(Cfg::parse(&mi).is_err());
 
-            let mi = MetaItem {
-                ident: Ident::from_str("not"),
-                node: MetaItemKind::List(vec![]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(not, []);
             assert!(Cfg::parse(&mi).is_err());
 
-            let mi = MetaItem {
-                ident: Ident::from_str("foo"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("a"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(foo, []);
             assert!(Cfg::parse(&mi).is_err());
 
-            let mi = MetaItem {
-                ident: Ident::from_str("all"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("foo"),
-                        node: MetaItemKind::List(vec![]),
-                        span: DUMMY_SP,
-                    })),
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("b"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(all, [
+                dummy_meta_item_list!(foo, []),
+                dummy_meta_item_word("b"),
+            ]);
             assert!(Cfg::parse(&mi).is_err());
 
-            let mi = MetaItem {
-                ident: Ident::from_str("any"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("a"),
-                        node: MetaItemKind::Word,
-                        span: DUMMY_SP,
-                    })),
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("foo"),
-                        node: MetaItemKind::List(vec![]),
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(any, [
+                dummy_meta_item_word("a"),
+                dummy_meta_item_list!(foo, []),
+            ]);
             assert!(Cfg::parse(&mi).is_err());
 
-            let mi = MetaItem {
-                ident: Ident::from_str("not"),
-                node: MetaItemKind::List(vec![
-                    dummy_spanned(NestedMetaItemKind::MetaItem(MetaItem {
-                        ident: Ident::from_str("foo"),
-                        node: MetaItemKind::List(vec![]),
-                        span: DUMMY_SP,
-                    })),
-                ]),
-                span: DUMMY_SP,
-            };
+            let mi = dummy_meta_item_list!(not, [
+                dummy_meta_item_list!(foo, []),
+            ]);
             assert!(Cfg::parse(&mi).is_err());
         })
     }
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index bd64ac67ac9..d124a17b421 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -3661,7 +3661,7 @@ impl Clean<Vec<Item>> for doctree::Import {
         // #[doc(no_inline)] attribute is present.
         // Don't inline doc(hidden) imports so they can be stripped at a later stage.
         let denied = self.vis != hir::Public || self.attrs.iter().any(|a| {
-            a.name().unwrap() == "doc" && match a.meta_item_list() {
+            a.name() == "doc" && match a.meta_item_list() {
                 Some(l) => attr::list_contains_name(&l, "no_inline") ||
                            attr::list_contains_name(&l, "hidden"),
                 None => false,
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 4e9781cc560..e9520573f8b 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -3284,7 +3284,7 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
 }
 
 fn render_attribute(attr: &ast::MetaItem) -> Option<String> {
-    let name = attr.ident.name;
+    let name = attr.name();
 
     if attr.is_word() {
         Some(format!("{}", name))
@@ -3319,7 +3319,7 @@ fn render_attributes(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result {
     let mut attrs = String::new();
 
     for attr in &it.attrs.other_attrs {
-        let name = attr.name().unwrap();
+        let name = attr.name();
         if !ATTRIBUTE_WHITELIST.contains(&&*name.as_str()) {
             continue;
         }
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index b590a4a6286..f8cd6103bdf 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -474,10 +474,10 @@ pub enum NestedMetaItemKind {
 
 /// A spanned compile-time attribute item.
 ///
-/// E.g. `#[test]`, `#[derive(..)]` or `#[feature = "foo"]`
+/// E.g. `#[test]`, `#[derive(..)]`, `#[rustfmt::skip]` or `#[feature = "foo"]`
 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug)]
 pub struct MetaItem {
-    pub ident: Ident,
+    pub ident: Path,
     pub node: MetaItemKind,
     pub span: Span,
 }
diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs
index b5461d61dd4..ace9904e0c0 100644
--- a/src/libsyntax/attr.rs
+++ b/src/libsyntax/attr.rs
@@ -15,10 +15,10 @@ pub use self::ReprAttr::*;
 pub use self::IntType::*;
 
 use ast;
-use ast::{AttrId, Attribute, Name, Ident};
+use ast::{AttrId, Attribute, Name, Ident, Path, PathSegment};
 use ast::{MetaItem, MetaItemKind, NestedMetaItem, NestedMetaItemKind};
 use ast::{Lit, LitKind, Expr, ExprKind, Item, Local, Stmt, StmtKind};
-use codemap::{Spanned, respan, dummy_spanned};
+use codemap::{BytePos, Spanned, respan, dummy_spanned};
 use syntax_pos::Span;
 use errors::Handler;
 use feature_gate::{Features, GatedCfg};
@@ -107,6 +107,14 @@ pub fn is_known(attr: &Attribute) -> bool {
     })
 }
 
+const RUST_KNOWN_TOOL: &[&str] = &["clippy", "rustfmt"];
+
+pub fn is_known_tool(attr: &Attribute) -> bool {
+    let tool_name =
+        attr.path.segments.iter().next().expect("empty path in attribute").ident.name;
+    RUST_KNOWN_TOOL.contains(&tool_name.as_str().as_ref())
+}
+
 impl NestedMetaItem {
     /// Returns the MetaItem if self is a NestedMetaItemKind::MetaItem.
     pub fn meta_item(&self) -> Option<&MetaItem> {
@@ -137,7 +145,7 @@ impl NestedMetaItem {
     /// Returns the name of the meta item, e.g. `foo` in `#[foo]`,
     /// `#[foo="bar"]` and `#[foo(bar)]`, if self is a MetaItem
     pub fn name(&self) -> Option<Name> {
-        self.meta_item().and_then(|meta_item| Some(meta_item.ident.name))
+        self.meta_item().and_then(|meta_item| Some(meta_item.name()))
     }
 
     /// Gets the string value if self is a MetaItem and the MetaItem is a
@@ -154,7 +162,7 @@ impl NestedMetaItem {
                     if meta_item_list.len() == 1 {
                         let nested_item = &meta_item_list[0];
                         if nested_item.is_literal() {
-                            Some((meta_item.ident.name, nested_item.literal().unwrap()))
+                            Some((meta_item.name(), nested_item.literal().unwrap()))
                         } else {
                             None
                         }
@@ -204,6 +212,10 @@ impl NestedMetaItem {
     }
 }
 
+fn name_from_path(path: &Path) -> Name {
+    path.segments.last().expect("empty path in attribute").ident.name
+}
+
 impl Attribute {
     pub fn check_name(&self, name: &str) -> bool {
         let matches = self.path == name;
@@ -213,11 +225,10 @@ impl Attribute {
         matches
     }
 
-    pub fn name(&self) -> Option<Name> {
-        match self.path.segments.len() {
-            1 => Some(self.path.segments[0].ident.name),
-            _ => None,
-        }
+    /// Returns the **last** segment of the name of this attribute.
+    /// E.g. `foo` for `#[foo]`, `skip` for `#[rustfmt::skip]`.
+    pub fn name(&self) -> Name {
+        name_from_path(&self.path)
     }
 
     pub fn value_str(&self) -> Option<Symbol> {
@@ -247,9 +258,17 @@ impl Attribute {
     pub fn is_value_str(&self) -> bool {
         self.value_str().is_some()
     }
+
+    pub fn is_scoped(&self) -> bool {
+        self.path.segments.len() > 1
+    }
 }
 
 impl MetaItem {
+    pub fn name(&self) -> Name {
+        name_from_path(&self.ident)
+    }
+
     pub fn value_str(&self) -> Option<Symbol> {
         match self.node {
             MetaItemKind::NameValue(ref v) => {
@@ -279,7 +298,7 @@ impl MetaItem {
     pub fn span(&self) -> Span { self.span }
 
     pub fn check_name(&self, name: &str) -> bool {
-        self.ident.name == name
+        self.name() == name
     }
 
     pub fn is_value_str(&self) -> bool {
@@ -296,10 +315,7 @@ impl Attribute {
     pub fn meta(&self) -> Option<MetaItem> {
         let mut tokens = self.tokens.trees().peekable();
         Some(MetaItem {
-            ident: match self.path.segments.len() {
-                1 => self.path.segments[0].ident,
-                _ => return None,
-            },
+            ident: self.path.clone(),
             node: if let Some(node) = MetaItemKind::from_tokens(&mut tokens) {
                 if tokens.peek().is_some() {
                     return None;
@@ -344,12 +360,8 @@ impl Attribute {
     }
 
     pub fn parse_meta<'a>(&self, sess: &'a ParseSess) -> PResult<'a, MetaItem> {
-        if self.path.segments.len() > 1 {
-            sess.span_diagnostic.span_err(self.path.span, "expected ident, found path");
-        }
-
         Ok(MetaItem {
-            ident: self.path.segments.last().unwrap().ident,
+            ident: self.path.clone(),
             node: self.parse(sess, |parser| parser.parse_meta_item_kind())?,
             span: self.span,
         })
@@ -387,16 +399,17 @@ pub fn mk_name_value_item_str(ident: Ident, value: Spanned<Symbol>) -> MetaItem
 }
 
 pub fn mk_name_value_item(span: Span, ident: Ident, value: ast::Lit) -> MetaItem {
-    MetaItem { ident, span, node: MetaItemKind::NameValue(value) }
+    MetaItem { ident: Path::from_ident(ident), span, node: MetaItemKind::NameValue(value) }
 }
 
 pub fn mk_list_item(span: Span, ident: Ident, items: Vec<NestedMetaItem>) -> MetaItem {
-    MetaItem { ident, span, node: MetaItemKind::List(items) }
+    MetaItem { ident: Path::from_ident(ident), span, node: MetaItemKind::List(items) }
 }
 
 pub fn mk_word_item(ident: Ident) -> MetaItem {
-    MetaItem { ident, span: ident.span, node: MetaItemKind::Word }
+    MetaItem { ident: Path::from_ident(ident), span: ident.span, node: MetaItemKind::Word }
 }
+
 pub fn mk_nested_word_item(ident: Ident) -> NestedMetaItem {
     respan(ident.span, NestedMetaItemKind::MetaItem(mk_word_item(ident)))
 }
@@ -422,7 +435,7 @@ pub fn mk_spanned_attr_inner(sp: Span, id: AttrId, item: MetaItem) -> Attribute
     Attribute {
         id,
         style: ast::AttrStyle::Inner,
-        path: ast::Path::from_ident(item.ident),
+        path: item.ident,
         tokens: item.node.tokens(item.span),
         is_sugared_doc: false,
         span: sp,
@@ -440,7 +453,7 @@ pub fn mk_spanned_attr_outer(sp: Span, id: AttrId, item: MetaItem) -> Attribute
     Attribute {
         id,
         style: ast::AttrStyle::Outer,
-        path: ast::Path::from_ident(item.ident),
+        path: item.ident,
         tokens: item.node.tokens(item.span),
         is_sugared_doc: false,
         span: sp,
@@ -453,7 +466,7 @@ pub fn mk_sugared_doc_attr(id: AttrId, text: Symbol, span: Span) -> Attribute {
     Attribute {
         id,
         style,
-        path: ast::Path::from_ident(Ident::from_str("doc").with_span_pos(span)),
+        path: Path::from_ident(Ident::from_str("doc").with_span_pos(span)),
         tokens: MetaItemKind::NameValue(lit).tokens(span),
         is_sugared_doc: true,
         span,
@@ -489,7 +502,7 @@ pub fn contains_feature_attr(attrs: &[Attribute], feature_name: &str) -> bool {
         item.check_name("feature") &&
         item.meta_item_list().map(|list| {
             list.iter().any(|mi| {
-                mi.word().map(|w| w.ident.name == feature_name)
+                mi.word().map(|w| w.name() == feature_name)
                          .unwrap_or(false)
             })
         }).unwrap_or(false)
@@ -562,7 +575,7 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
         if let (Some(feats), Some(gated_cfg)) = (features, GatedCfg::gate(cfg)) {
             gated_cfg.check_and_emit(sess, feats);
         }
-        sess.config.contains(&(cfg.ident.name, cfg.value_str()))
+        sess.config.contains(&(cfg.name(), cfg.value_str()))
     })
 }
 
@@ -583,7 +596,7 @@ pub fn eval_condition<F>(cfg: &ast::MetaItem, sess: &ParseSess, eval: &mut F)
 
             // The unwraps below may look dangerous, but we've already asserted
             // that they won't fail with the loop above.
-            match &*cfg.ident.name.as_str() {
+            match &*cfg.name().as_str() {
                 "any" => mis.iter().any(|mi| {
                     eval_condition(mi.meta_item().unwrap(), sess, eval)
                 }),
@@ -676,7 +689,7 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler,
             let meta = meta.as_ref().unwrap();
             let get = |meta: &MetaItem, item: &mut Option<Symbol>| {
                 if item.is_some() {
-                    handle_errors(diagnostic, meta.span, AttrError::MultipleItem(meta.ident.name));
+                    handle_errors(diagnostic, meta.span, AttrError::MultipleItem(meta.name()));
                     return false
                 }
                 if let Some(v) = meta.value_str() {
@@ -695,14 +708,14 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler,
                     )+
                     for meta in metas {
                         if let Some(mi) = meta.meta_item() {
-                            match &*mi.ident.name.as_str() {
+                            match &*mi.name().as_str() {
                                 $(
                                     stringify!($name)
                                         => if !get(mi, &mut $name) { continue 'outer },
                                 )+
                                 _ => {
                                     handle_errors(diagnostic, mi.span,
-                                                  AttrError::UnknownMetaItem(mi.ident.name));
+                                                  AttrError::UnknownMetaItem(mi.name()));
                                     continue 'outer
                                 }
                             }
@@ -714,7 +727,7 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler,
                 }
             }
 
-            match &*meta.ident.name.as_str() {
+            match &*meta.name().as_str() {
                 "rustc_deprecated" => {
                     if rustc_depr.is_some() {
                         span_err!(diagnostic, item_sp, E0540,
@@ -769,13 +782,13 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler,
                     let mut issue = None;
                     for meta in metas {
                         if let Some(mi) = meta.meta_item() {
-                            match &*mi.ident.name.as_str() {
+                            match &*mi.name().as_str() {
                                 "feature" => if !get(mi, &mut feature) { continue 'outer },
                                 "reason" => if !get(mi, &mut reason) { continue 'outer },
                                 "issue" => if !get(mi, &mut issue) { continue 'outer },
                                 _ => {
                                     handle_errors(diagnostic, meta.span,
-                                                  AttrError::UnknownMetaItem(mi.ident.name));
+                                                  AttrError::UnknownMetaItem(mi.name()));
                                     continue 'outer
                                 }
                             }
@@ -825,12 +838,12 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler,
                     let mut since = None;
                     for meta in metas {
                         if let NestedMetaItemKind::MetaItem(ref mi) = meta.node {
-                            match &*mi.ident.name.as_str() {
+                            match &*mi.name().as_str() {
                                 "feature" => if !get(mi, &mut feature) { continue 'outer },
                                 "since" => if !get(mi, &mut since) { continue 'outer },
                                 _ => {
                                     handle_errors(diagnostic, meta.span,
-                                                  AttrError::UnknownMetaItem(mi.ident.name));
+                                                  AttrError::UnknownMetaItem(mi.name()));
                                     continue 'outer
                                 }
                             }
@@ -917,7 +930,7 @@ fn find_deprecation_generic<'a, I>(diagnostic: &Handler,
         depr = if let Some(metas) = attr.meta_item_list() {
             let get = |meta: &MetaItem, item: &mut Option<Symbol>| {
                 if item.is_some() {
-                    handle_errors(diagnostic, meta.span, AttrError::MultipleItem(meta.ident.name));
+                    handle_errors(diagnostic, meta.span, AttrError::MultipleItem(meta.name()));
                     return false
                 }
                 if let Some(v) = meta.value_str() {
@@ -933,12 +946,12 @@ fn find_deprecation_generic<'a, I>(diagnostic: &Handler,
             let mut note = None;
             for meta in metas {
                 if let NestedMetaItemKind::MetaItem(ref mi) = meta.node {
-                    match &*mi.ident.name.as_str() {
+                    match &*mi.name().as_str() {
                         "since" => if !get(mi, &mut since) { continue 'outer },
                         "note" => if !get(mi, &mut note) { continue 'outer },
                         _ => {
                             handle_errors(diagnostic, meta.span,
-                                          AttrError::UnknownMetaItem(mi.ident.name));
+                                          AttrError::UnknownMetaItem(mi.name()));
                             continue 'outer
                         }
                     }
@@ -990,7 +1003,7 @@ pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec<ReprAttr>
 
                 let mut recognised = false;
                 if let Some(mi) = item.word() {
-                    let word = &*mi.ident.name.as_str();
+                    let word = &*mi.name().as_str();
                     let hint = match word {
                         "C" => Some(ReprC),
                         "packed" => Some(ReprPacked(1)),
@@ -1047,7 +1060,7 @@ pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec<ReprAttr>
                     }
                 } else {
                     if let Some(meta_item) = item.meta_item() {
-                        if meta_item.ident.name == "align" {
+                        if meta_item.name() == "align" {
                             if let MetaItemKind::NameValue(ref value) = meta_item.node {
                                 recognised = true;
                                 let mut err = struct_span_err!(diagnostic, item.span, E0693,
@@ -1127,18 +1140,56 @@ impl IntType {
 
 impl MetaItem {
     fn tokens(&self) -> TokenStream {
-        let ident = TokenTree::Token(self.span, Token::from_ast_ident(self.ident));
-        TokenStream::concat(vec![ident.into(), self.node.tokens(self.span)])
+        let mut idents = vec![];
+        let mut last_pos = BytePos(0 as u32);
+        for (i, segment) in self.ident.segments.iter().enumerate() {
+            let is_first = i == 0;
+            if !is_first {
+                let mod_sep_span = Span::new(last_pos,
+                                             segment.ident.span.lo(),
+                                             segment.ident.span.ctxt());
+                idents.push(TokenTree::Token(mod_sep_span, Token::ModSep).into());
+            }
+            idents.push(TokenTree::Token(segment.ident.span,
+                                         Token::from_ast_ident(segment.ident)).into());
+            last_pos = segment.ident.span.hi();
+        }
+        idents.push(self.node.tokens(self.span));
+        TokenStream::concat(idents)
     }
 
     fn from_tokens<I>(tokens: &mut iter::Peekable<I>) -> Option<MetaItem>
         where I: Iterator<Item = TokenTree>,
     {
-        let (span, ident) = match tokens.next() {
-            Some(TokenTree::Token(span, Token::Ident(ident, _))) => (span, ident),
+        // FIXME: Share code with `parse_path`.
+        let ident = match tokens.next() {
+            Some(TokenTree::Token(span, Token::Ident(ident, _))) => {
+                if let Some(TokenTree::Token(_, Token::ModSep)) = tokens.peek() {
+                    let mut segments = vec![PathSegment::from_ident(ident.with_span_pos(span))];
+                    tokens.next();
+                    loop {
+                        if let Some(TokenTree::Token(span,
+                                                     Token::Ident(ident, _))) = tokens.next() {
+                            segments.push(PathSegment::from_ident(ident.with_span_pos(span)));
+                        } else {
+                            return None;
+                        }
+                        if let Some(TokenTree::Token(_, Token::ModSep)) = tokens.peek() {
+                            tokens.next();
+                        } else {
+                            break;
+                        }
+                    }
+                    let span = span.with_hi(segments.last().unwrap().ident.span.hi());
+                    Path { span, segments }
+                } else {
+                    Path::from_ident(ident.with_span_pos(span))
+                }
+            }
             Some(TokenTree::Token(_, Token::Interpolated(ref nt))) => match nt.0 {
-                token::Nonterminal::NtIdent(ident, _) => (ident.span, ident),
+                token::Nonterminal::NtIdent(ident, _) => Path::from_ident(ident),
                 token::Nonterminal::NtMeta(ref meta) => return Some(meta.clone()),
+                token::Nonterminal::NtPath(ref path) => path.clone(),
                 _ => return None,
             },
             _ => return None,
@@ -1147,10 +1198,11 @@ impl MetaItem {
         let node = MetaItemKind::from_tokens(tokens)?;
         let hi = match node {
             MetaItemKind::NameValue(ref lit) => lit.span.hi(),
-            MetaItemKind::List(..) => list_closing_paren_pos.unwrap_or(span.hi()),
-            _ => span.hi(),
+            MetaItemKind::List(..) => list_closing_paren_pos.unwrap_or(ident.span.hi()),
+            _ => ident.span.hi(),
         };
-        Some(MetaItem { ident, node, span: span.with_hi(hi) })
+        let span = ident.span.with_hi(hi);
+        Some(MetaItem { ident, node, span })
     }
 }
 
diff --git a/src/libsyntax/diagnostic_list.rs b/src/libsyntax/diagnostic_list.rs
index d1f123f6f6c..5f940437ab3 100644
--- a/src/libsyntax/diagnostic_list.rs
+++ b/src/libsyntax/diagnostic_list.rs
@@ -336,4 +336,5 @@ register_diagnostics! {
     E0629, // missing 'feature' (rustc_const_unstable)
     E0630, // rustc_const_unstable attribute must be paired with stable/unstable attribute
     E0693, // incorrect `repr(align)` attribute format
+    E0694, // an unknown tool name found in scoped attributes
 }
diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs
index 2f3b35c33f3..584b9455a93 100644
--- a/src/libsyntax/ext/expand.rs
+++ b/src/libsyntax/ext/expand.rs
@@ -810,7 +810,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 invoc.expansion_data.mark.set_expn_info(expn_info);
                 let span = span.with_ctxt(self.cx.backtrace());
                 let dummy = ast::MetaItem { // FIXME(jseyfried) avoid this
-                    ident: keywords::Invalid.ident(),
+                    ident: Path::from_ident(keywords::Invalid.ident()),
                     span: DUMMY_SP,
                     node: ast::MetaItemKind::Word,
                 };
@@ -1017,7 +1017,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
     fn check_attributes(&mut self, attrs: &[ast::Attribute]) {
         let features = self.cx.ecfg.features.unwrap();
         for attr in attrs.iter() {
-            feature_gate::check_attribute(attr, self.cx.parse_sess, features);
+            self.check_attribute_inner(attr, features);
 
             // macros are expanded before any lint passes so this warning has to be hardcoded
             if attr.path == "derive" {
@@ -1030,6 +1030,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
 
     fn check_attribute(&mut self, at: &ast::Attribute) {
         let features = self.cx.ecfg.features.unwrap();
+        self.check_attribute_inner(at, features);
+    }
+
+    fn check_attribute_inner(&mut self, at: &ast::Attribute, features: &Features) {
         feature_gate::check_attribute(at, self.cx.parse_sess, features);
     }
 }
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index b1f3c74d9f7..d8db76a95ff 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -460,6 +460,9 @@ declare_features! (
 
     // Access to crate names passed via `--extern` through prelude
     (active, extern_prelude, "1.27.0", Some(44660), Some(Edition::Edition2018)),
+
+    // Scoped attributes
+    (active, tool_attributes, "1.25.0", Some(44690), None),
 );
 
 declare_features! (
@@ -1079,7 +1082,7 @@ pub struct GatedCfg {
 
 impl GatedCfg {
     pub fn gate(cfg: &ast::MetaItem) -> Option<GatedCfg> {
-        let name = cfg.ident.name.as_str();
+        let name = cfg.name().as_str();
         GATED_CFGS.iter()
                   .position(|info| info.0 == name)
                   .map(|idx| {
@@ -1132,7 +1135,7 @@ macro_rules! gate_feature {
 impl<'a> Context<'a> {
     fn check_attribute(&self, attr: &ast::Attribute, is_macro: bool) {
         debug!("check_attribute(attr = {:?})", attr);
-        let name = unwrap_or!(attr.name(), return).as_str();
+        let name = attr.name().as_str();
         for &(n, ty, ref gateage) in BUILTIN_ATTRIBUTES {
             if name == n {
                 if let Gated(_, name, desc, ref has_feature) = *gateage {
@@ -1172,12 +1175,28 @@ impl<'a> Context<'a> {
             // before the plugin attributes are registered
             // so we skip this then
             if !is_macro {
-                gate_feature!(self, custom_attribute, attr.span,
-                              &format!("The attribute `{}` is currently \
-                                        unknown to the compiler and \
-                                        may have meaning \
-                                        added to it in the future",
-                                       attr.path));
+                if attr.is_scoped() {
+                    gate_feature!(self, tool_attributes, attr.span,
+                                  &format!("scoped attribute `{}` is experimental", attr.path));
+                    if attr::is_known_tool(attr) {
+                        attr::mark_used(attr);
+                    } else {
+                        span_err!(
+                            self.parse_sess.span_diagnostic,
+                            attr.span,
+                            E0694,
+                            "an unknown tool name found in scoped attribute: `{}`.",
+                            attr.path
+                        );
+                    }
+                } else {
+                    gate_feature!(self, custom_attribute, attr.span,
+                                  &format!("The attribute `{}` is currently \
+                                            unknown to the compiler and \
+                                            may have meaning \
+                                            added to it in the future",
+                                           attr.path));
+                }
             }
         }
     }
@@ -1843,7 +1862,7 @@ pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute],
                 for mi in list {
 
                     let name = if let Some(word) = mi.word() {
-                        word.ident.name
+                        word.name()
                     } else {
                         span_err!(span_handler, mi.span, E0556,
                                   "malformed feature, expected just one word");
diff --git a/src/libsyntax/parse/attr.rs b/src/libsyntax/parse/attr.rs
index 90f08ab1468..cceed589212 100644
--- a/src/libsyntax/parse/attr.rs
+++ b/src/libsyntax/parse/attr.rs
@@ -149,7 +149,7 @@ impl<'a> Parser<'a> {
         };
         Ok(if let Some(meta) = meta {
             self.bump();
-            (ast::Path::from_ident(meta.ident), meta.node.tokens(meta.span))
+            (meta.ident, meta.node.tokens(meta.span))
         } else {
             (self.parse_path(PathStyle::Mod)?, self.parse_tokens())
         })
@@ -225,9 +225,10 @@ impl<'a> Parser<'a> {
         }
 
         let lo = self.span;
-        let ident = self.parse_ident()?;
+        let ident = self.parse_path(PathStyle::Mod)?;
         let node = self.parse_meta_item_kind()?;
-        Ok(ast::MetaItem { ident, node: node, span: lo.to(self.prev_span) })
+        let span = lo.to(self.prev_span);
+        Ok(ast::MetaItem { ident, node, span })
     }
 
     pub fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> {
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index 324cadc84e8..0e3bced3222 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -1958,16 +1958,16 @@ impl<'a> Parser<'a> {
         let meta_ident = match self.token {
             token::Interpolated(ref nt) => match nt.0 {
                 token::NtMeta(ref meta) => match meta.node {
-                    ast::MetaItemKind::Word => Some(meta.ident),
+                    ast::MetaItemKind::Word => Some(meta.ident.clone()),
                     _ => None,
                 },
                 _ => None,
             },
             _ => None,
         };
-        if let Some(ident) = meta_ident {
+        if let Some(path) = meta_ident {
             self.bump();
-            return Ok(ast::Path::from_ident(ident));
+            return Ok(path);
         }
         self.parse_path(style)
     }
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index 27c5a14ff0e..d8228e2b28b 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -714,6 +714,22 @@ pub trait PrintState<'a> {
         Ok(())
     }
 
+    fn print_attribute_path(&mut self, path: &ast::Path) -> io::Result<()> {
+        for (i, segment) in path.segments.iter().enumerate() {
+            if i > 0 {
+                self.writer().word("::")?
+            }
+            if segment.ident.name != keywords::CrateRoot.name() &&
+               segment.ident.name != keywords::DollarCrate.name()
+            {
+                self.writer().word(&segment.ident.name.as_str())?;
+            } else if segment.ident.name == keywords::DollarCrate.name() {
+                self.print_dollar_crate(segment.ident.span.ctxt())?;
+            }
+        }
+        Ok(())
+    }
+
     fn print_attribute(&mut self, attr: &ast::Attribute) -> io::Result<()> {
         self.print_attribute_inline(attr, false)
     }
@@ -735,17 +751,7 @@ pub trait PrintState<'a> {
             if let Some(mi) = attr.meta() {
                 self.print_meta_item(&mi)?
             } else {
-                for (i, segment) in attr.path.segments.iter().enumerate() {
-                    if i > 0 {
-                        self.writer().word("::")?
-                    }
-                    if segment.ident.name != keywords::CrateRoot.name() &&
-                       segment.ident.name != keywords::DollarCrate.name() {
-                        self.writer().word(&segment.ident.name.as_str())?;
-                    } else if segment.ident.name == keywords::DollarCrate.name() {
-                        self.print_dollar_crate(segment.ident.span.ctxt())?;
-                    }
-                }
+                self.print_attribute_path(&attr.path)?;
                 self.writer().space()?;
                 self.print_tts(attr.tokens.clone())?;
             }
@@ -767,16 +773,15 @@ pub trait PrintState<'a> {
     fn print_meta_item(&mut self, item: &ast::MetaItem) -> io::Result<()> {
         self.ibox(INDENT_UNIT)?;
         match item.node {
-            ast::MetaItemKind::Word => {
-                self.writer().word(&item.ident.name.as_str())?;
-            }
+            ast::MetaItemKind::Word => self.print_attribute_path(&item.ident)?,
             ast::MetaItemKind::NameValue(ref value) => {
-                self.word_space(&item.ident.name.as_str())?;
+                self.print_attribute_path(&item.ident)?;
+                self.writer().space()?;
                 self.word_space("=")?;
                 self.print_literal(value)?;
             }
             ast::MetaItemKind::List(ref items) => {
-                self.writer().word(&item.ident.name.as_str())?;
+                self.print_attribute_path(&item.ident)?;
                 self.popen()?;
                 self.commasep(Consistent,
                               &items[..],
diff --git a/src/libsyntax_ext/deriving/custom.rs b/src/libsyntax_ext/deriving/custom.rs
index 5fd5e299488..76da1746a03 100644
--- a/src/libsyntax_ext/deriving/custom.rs
+++ b/src/libsyntax_ext/deriving/custom.rs
@@ -22,11 +22,9 @@ struct MarkAttrs<'a>(&'a [ast::Name]);
 
 impl<'a> Visitor<'a> for MarkAttrs<'a> {
     fn visit_attribute(&mut self, attr: &Attribute) {
-        if let Some(name) = attr.name() {
-            if self.0.contains(&name) {
-                mark_used(attr);
-                mark_known(attr);
-            }
+        if self.0.contains(&attr.name()) {
+            mark_used(attr);
+            mark_known(attr);
         }
     }
 
diff --git a/src/libsyntax_ext/deriving/generic/mod.rs b/src/libsyntax_ext/deriving/generic/mod.rs
index becd70149fd..80f65957c39 100644
--- a/src/libsyntax_ext/deriving/generic/mod.rs
+++ b/src/libsyntax_ext/deriving/generic/mod.rs
@@ -472,7 +472,7 @@ impl<'a> TraitDef<'a> {
                 attrs.extend(item.attrs
                     .iter()
                     .filter(|a| {
-                        a.name().is_some() && match &*a.name().unwrap().as_str() {
+                        match &*a.name().as_str() {
                             "allow" | "warn" | "deny" | "forbid" | "stable" | "unstable" => true,
                             _ => false,
                         }
diff --git a/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs b/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs
index 040f0b661be..fb0f9105b0d 100644
--- a/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs
+++ b/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs
@@ -117,7 +117,7 @@ fn expand_duplicate(cx: &mut ExtCtxt,
     let copy_name = match mi.node {
         ast::MetaItemKind::List(ref xs) => {
             if let Some(word) = xs[0].word() {
-                word.ident
+                word.ident.segments.last().unwrap().ident
             } else {
                 cx.span_err(mi.span, "Expected word");
                 return;
diff --git a/src/test/compile-fail/feature-gate-tool_attributes.rs b/src/test/compile-fail/feature-gate-tool_attributes.rs
new file mode 100644
index 00000000000..5a7536ca330
--- /dev/null
+++ b/src/test/compile-fail/feature-gate-tool_attributes.rs
@@ -0,0 +1,15 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+fn main() {
+    #[rustfmt::skip] //~ ERROR scoped attribute `rustfmt::skip` is experimental
+    let x =
+        3;
+}
diff --git a/src/test/compile-fail/unknown-tool-name.rs b/src/test/compile-fail/unknown-tool-name.rs
new file mode 100644
index 00000000000..c2192a21d90
--- /dev/null
+++ b/src/test/compile-fail/unknown-tool-name.rs
@@ -0,0 +1,16 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(tool_attributes)]
+
+#![foo::bar] //~ ERROR an unknown tool name found in scoped attribute: `foo::bar`. [E0694]
+
+#[foo::bar] //~ ERROR an unknown tool name found in scoped attribute: `foo::bar`. [E0694]
+fn main() {}
diff --git a/src/test/compile-fail/unknown_tool_attributes-1.rs b/src/test/compile-fail/unknown_tool_attributes-1.rs
new file mode 100644
index 00000000000..ba38c297a11
--- /dev/null
+++ b/src/test/compile-fail/unknown_tool_attributes-1.rs
@@ -0,0 +1,18 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Make sure that 'custom_attributes' feature does not allow scoped attributes.
+
+#![feature(custom_attributes)]
+
+#[foo::bar]
+//~^ ERROR scoped attribute `foo::bar` is experimental (see issue #44690) [E0658]
+//~^^ ERROR an unknown tool name found in scoped attribute: `foo::bar`. [E0694]
+fn main() {}
diff --git a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs
index 2f80408ac1c..d698af50579 100644
--- a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs
+++ b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs
@@ -112,7 +112,7 @@ fn expand_duplicate(cx: &mut ExtCtxt,
     let copy_name = match mi.node {
         ast::MetaItemKind::List(ref xs) => {
             if let Some(word) = xs[0].word() {
-                word.ident
+                word.ident.segments.last().unwrap().ident
             } else {
                 cx.span_err(mi.span, "Expected word");
                 return;
diff --git a/src/test/run-pass/tool_attributes.rs b/src/test/run-pass/tool_attributes.rs
new file mode 100644
index 00000000000..eb13930de40
--- /dev/null
+++ b/src/test/run-pass/tool_attributes.rs
@@ -0,0 +1,23 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Scoped attributes should not trigger an unused attributes lint.
+
+#![feature(tool_attributes)]
+#![deny(unused_attributes)]
+
+fn main() {
+    #[rustfmt::skip]
+    foo ();
+}
+
+fn foo() {
+    assert!(true);
+}
diff --git a/src/test/ui/feature-gate-tool_attributes.rs b/src/test/ui/feature-gate-tool_attributes.rs
new file mode 100644
index 00000000000..2b7cf56d938
--- /dev/null
+++ b/src/test/ui/feature-gate-tool_attributes.rs
@@ -0,0 +1,15 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+fn main() {
+    #[rustfmt::skip] //~ ERROR scoped attribute `rustfmt::skip` is experimental
+    let x = 3
+        ;
+}
diff --git a/src/test/ui/feature-gate-tool_attributes.stderr b/src/test/ui/feature-gate-tool_attributes.stderr
new file mode 100644
index 00000000000..da89c4a5ef6
--- /dev/null
+++ b/src/test/ui/feature-gate-tool_attributes.stderr
@@ -0,0 +1,11 @@
+error[E0658]: scoped attribute `rustfmt::skip` is experimental (see issue #44690)
+  --> $DIR/feature-gate-tool_attributes.rs:12:5
+   |
+LL |     #[rustfmt::skip] //~ ERROR scoped attribute `rustfmt::skip` is experimental
+   |     ^^^^^^^^^^^^^^^^
+   |
+   = help: add #![feature(tool_attributes)] to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.