about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVadim Petrochenkov <vadim.petrochenkov@gmail.com>2019-11-03 20:28:20 +0300
committerVadim Petrochenkov <vadim.petrochenkov@gmail.com>2019-11-09 17:50:51 +0300
commit3a223a917378f439fd9107e26ee7355f5f92c62d (patch)
tree973b335a5debf2e4be2791df7af0f85d666bd133
parent5a5027519a4a634baa6cde5b698b907d27fbe6b3 (diff)
downloadrust-3a223a917378f439fd9107e26ee7355f5f92c62d.tar.gz
rust-3a223a917378f439fd9107e26ee7355f5f92c62d.zip
Support registering attributes and attribute tools using crate-level attributes
-rw-r--r--src/librustc/hir/def.rs3
-rw-r--r--src/librustc_resolve/diagnostics.rs14
-rw-r--r--src/librustc_resolve/lib.rs24
-rw-r--r--src/librustc_resolve/macros.rs69
-rw-r--r--src/libsyntax/feature_gate/active.rs6
-rw-r--r--src/libsyntax/feature_gate/builtin_attrs.rs8
-rw-r--r--src/libsyntax_pos/symbol.rs2
-rw-r--r--src/test/ui/attributes/register-attr-tool-fail.rs13
-rw-r--r--src/test/ui/attributes/register-attr-tool-fail.stderr42
-rw-r--r--src/test/ui/attributes/register-attr-tool.rs19
-rw-r--r--src/test/ui/feature-gates/feature-gate-register_attr.rs3
-rw-r--r--src/test/ui/feature-gates/feature-gate-register_attr.stderr12
-rw-r--r--src/test/ui/feature-gates/feature-gate-register_tool.rs3
-rw-r--r--src/test/ui/feature-gates/feature-gate-register_tool.stderr12
14 files changed, 210 insertions, 20 deletions
diff --git a/src/librustc/hir/def.rs b/src/librustc/hir/def.rs
index d4d7af92fe3..486d6dfb095 100644
--- a/src/librustc/hir/def.rs
+++ b/src/librustc/hir/def.rs
@@ -40,6 +40,8 @@ pub enum NonMacroAttrKind {
     Tool,
     /// Single-segment custom attribute registered by a derive macro (`#[serde(default)]`).
     DeriveHelper,
+    /// Single-segment custom attribute registered with `#[register_attr]`.
+    Registered,
     /// Single-segment custom attribute registered by a legacy plugin (`register_attribute`).
     LegacyPluginHelper,
     /// Single-segment custom attribute not registered in any way (`#[my_attr]`).
@@ -329,6 +331,7 @@ impl NonMacroAttrKind {
             NonMacroAttrKind::Builtin => "built-in attribute",
             NonMacroAttrKind::Tool => "tool attribute",
             NonMacroAttrKind::DeriveHelper => "derive helper attribute",
+            NonMacroAttrKind::Registered => "explicitly registered attribute",
             NonMacroAttrKind::LegacyPluginHelper => "legacy plugin helper attribute",
             NonMacroAttrKind::Custom => "custom attribute",
         }
diff --git a/src/librustc_resolve/diagnostics.rs b/src/librustc_resolve/diagnostics.rs
index 3d68b72a655..771519b86c1 100644
--- a/src/librustc_resolve/diagnostics.rs
+++ b/src/librustc_resolve/diagnostics.rs
@@ -19,7 +19,7 @@ use syntax_pos::hygiene::MacroKind;
 use syntax_pos::{BytePos, Span, MultiSpan};
 
 use crate::resolve_imports::{ImportDirective, ImportDirectiveSubclass, ImportResolver};
-use crate::{path_names_to_string, KNOWN_TOOLS};
+use crate::path_names_to_string;
 use crate::{BindingError, CrateLint, HasGenericParams, LegacyScope, Module, ModuleOrUniformRoot};
 use crate::{PathResult, ParentScope, ResolutionError, Resolver, Scope, ScopeSet, Segment};
 
@@ -400,6 +400,14 @@ impl<'a> Resolver<'a> {
                 Scope::Module(module) => {
                     this.add_module_candidates(module, &mut suggestions, filter_fn);
                 }
+                Scope::RegisteredAttrs => {
+                    let res = Res::NonMacroAttr(NonMacroAttrKind::Registered);
+                    if filter_fn(res) {
+                        suggestions.extend(this.registered_attrs.iter().map(|ident| {
+                            TypoSuggestion::from_res(ident.name, res)
+                        }));
+                    }
+                }
                 Scope::MacroUsePrelude => {
                     suggestions.extend(this.macro_use_prelude.iter().filter_map(|(name, binding)| {
                         let res = binding.res();
@@ -439,8 +447,8 @@ impl<'a> Resolver<'a> {
                 }
                 Scope::ToolPrelude => {
                     let res = Res::NonMacroAttr(NonMacroAttrKind::Tool);
-                    suggestions.extend(KNOWN_TOOLS.iter().map(|name| {
-                        TypoSuggestion::from_res(*name, res)
+                    suggestions.extend(this.registered_tools.iter().map(|ident| {
+                        TypoSuggestion::from_res(ident.name, res)
                     }));
                 }
                 Scope::StdLibPrelude => {
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index 5c996bffb9a..6aefacef92c 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -74,8 +74,6 @@ mod check_unused;
 mod build_reduced_graph;
 mod resolve_imports;
 
-const KNOWN_TOOLS: &[Name] = &[sym::clippy, sym::rustfmt];
-
 enum Weak {
     Yes,
     No,
@@ -102,6 +100,7 @@ enum Scope<'a> {
     MacroRules(LegacyScope<'a>),
     CrateRoot,
     Module(Module<'a>),
+    RegisteredAttrs,
     MacroUsePrelude,
     BuiltinAttrs,
     LegacyPluginHelpers,
@@ -916,6 +915,8 @@ pub struct Resolver<'a> {
     crate_loader: CrateLoader<'a>,
     macro_names: FxHashSet<Ident>,
     builtin_macros: FxHashMap<Name, SyntaxExtension>,
+    registered_attrs: FxHashSet<Ident>,
+    registered_tools: FxHashSet<Ident>,
     macro_use_prelude: FxHashMap<Name, &'a NameBinding<'a>>,
     all_macros: FxHashMap<Name, Res>,
     macro_map: FxHashMap<DefId, Lrc<SyntaxExtension>>,
@@ -1132,6 +1133,9 @@ impl<'a> Resolver<'a> {
             }
         }
 
+        let (registered_attrs, registered_tools) =
+            macros::registered_attrs_and_tools(session, &krate.attrs);
+
         let mut invocation_parent_scopes = FxHashMap::default();
         invocation_parent_scopes.insert(ExpnId::root(), ParentScope::module(graph_root));
 
@@ -1201,6 +1205,8 @@ impl<'a> Resolver<'a> {
             crate_loader: CrateLoader::new(session, metadata_loader, crate_name),
             macro_names: FxHashSet::default(),
             builtin_macros: Default::default(),
+            registered_attrs,
+            registered_tools,
             macro_use_prelude: FxHashMap::default(),
             all_macros: FxHashMap::default(),
             macro_map: FxHashMap::default(),
@@ -1469,6 +1475,7 @@ impl<'a> Resolver<'a> {
                 Scope::MacroRules(..) => true,
                 Scope::CrateRoot => true,
                 Scope::Module(..) => true,
+                Scope::RegisteredAttrs => true,
                 Scope::MacroUsePrelude => use_prelude || rust_2015,
                 Scope::BuiltinAttrs => true,
                 Scope::LegacyPluginHelpers => use_prelude || rust_2015,
@@ -1513,11 +1520,12 @@ impl<'a> Resolver<'a> {
                             match ns {
                                 TypeNS => Scope::ExternPrelude,
                                 ValueNS => Scope::StdLibPrelude,
-                                MacroNS => Scope::MacroUsePrelude,
+                                MacroNS => Scope::RegisteredAttrs,
                             }
                         }
                     }
                 }
+                Scope::RegisteredAttrs => Scope::MacroUsePrelude,
                 Scope::MacroUsePrelude => Scope::StdLibPrelude,
                 Scope::BuiltinAttrs => Scope::LegacyPluginHelpers,
                 Scope::LegacyPluginHelpers => break, // nowhere else to search
@@ -1673,11 +1681,11 @@ impl<'a> Resolver<'a> {
                 if let Some(binding) = self.extern_prelude_get(ident, !record_used) {
                     return Some(LexicalScopeBinding::Item(binding));
                 }
-            }
-            if ns == TypeNS && KNOWN_TOOLS.contains(&ident.name) {
-                let binding = (Res::ToolMod, ty::Visibility::Public,
-                               DUMMY_SP, ExpnId::root()).to_name_binding(self.arenas);
-                return Some(LexicalScopeBinding::Item(binding));
+                if let Some(ident) = self.registered_tools.get(&ident) {
+                    let binding = (Res::ToolMod, ty::Visibility::Public,
+                                   ident.span, ExpnId::root()).to_name_binding(self.arenas);
+                    return Some(LexicalScopeBinding::Item(binding));
+                }
             }
             if let Some(prelude) = self.prelude {
                 if let Ok(binding) = self.resolve_ident_in_module_unadjusted(
diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs
index 3d5a7f26eda..9d8d035ab70 100644
--- a/src/librustc_resolve/macros.rs
+++ b/src/librustc_resolve/macros.rs
@@ -3,16 +3,17 @@
 
 use crate::{AmbiguityError, AmbiguityKind, AmbiguityErrorMisc, Determinacy};
 use crate::{CrateLint, Resolver, ResolutionError, Scope, ScopeSet, ParentScope, Weak};
-use crate::{ModuleKind, NameBinding, PathResult, Segment, ToNameBinding};
-use crate::{ModuleOrUniformRoot, KNOWN_TOOLS};
+use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment, ToNameBinding};
 use crate::Namespace::*;
 use crate::resolve_imports::ImportResolver;
 use rustc::hir::def::{self, DefKind, NonMacroAttrKind};
 use rustc::hir::def_id;
 use rustc::middle::stability;
+use rustc::session::Session;
+use rustc::util::nodemap::FxHashSet;
 use rustc::{ty, lint, span_bug};
 use syntax::ast::{self, NodeId, Ident};
-use syntax::attr::StabilityLevel;
+use syntax::attr::{self, StabilityLevel};
 use syntax::edition::Edition;
 use syntax::feature_gate::{emit_feature_err, is_builtin_attr_name};
 use syntax::feature_gate::GateIssue;
@@ -93,6 +94,45 @@ fn fast_print_path(path: &ast::Path) -> Symbol {
     }
 }
 
+fn registered_idents(
+    sess: &Session,
+    attrs: &[ast::Attribute],
+    attr_name: Symbol,
+    descr: &str,
+) -> FxHashSet<Ident> {
+    let mut registered = FxHashSet::default();
+    for attr in attr::filter_by_name(attrs, attr_name) {
+        for nested_meta in attr.meta_item_list().unwrap_or_default() {
+            match nested_meta.ident() {
+                Some(ident) => if let Some(old_ident) = registered.replace(ident) {
+                    let msg = format!("{} `{}` was already registered", descr, ident);
+                    sess.struct_span_err(ident.span, &msg)
+                        .span_label(old_ident.span, "already registered here").emit();
+                }
+                None => {
+                    let msg = format!("`{}` only accepts identifiers", attr_name);
+                    let span = nested_meta.span();
+                    sess.struct_span_err(span, &msg).span_label(span, "not an identifier").emit();
+                }
+            }
+        }
+    }
+    registered
+}
+
+crate fn registered_attrs_and_tools(
+    sess: &Session,
+    attrs: &[ast::Attribute],
+) -> (FxHashSet<Ident>, FxHashSet<Ident>) {
+    let registered_attrs = registered_idents(sess, attrs, sym::register_attr, "attribute");
+    let mut registered_tools = registered_idents(sess, attrs, sym::register_tool, "tool");
+    // We implicitly add `rustfmt` and `clippy` to known tools,
+    // but it's not an error to register them explicitly.
+    let predefined_tools = [sym::clippy, sym::rustfmt];
+    registered_tools.extend(predefined_tools.iter().cloned().map(Ident::with_dummy_span));
+    (registered_attrs, registered_tools)
+}
+
 impl<'a> base::Resolver for Resolver<'a> {
     fn next_node_id(&mut self) -> NodeId {
         self.session.next_node_id()
@@ -531,6 +571,15 @@ impl<'a> Resolver<'a> {
                         Err((Determinacy::Determined, _)) => Err(Determinacy::Determined),
                     }
                 }
+                Scope::RegisteredAttrs => match this.registered_attrs.get(&ident).cloned() {
+                    Some(ident) => {
+                        let binding = (Res::NonMacroAttr(NonMacroAttrKind::Registered),
+                                       ty::Visibility::Public, ident.span, ExpnId::root())
+                                       .to_name_binding(this.arenas);
+                        Ok((binding, Flags::PRELUDE))
+                    }
+                    None => Err(Determinacy::Determined)
+                }
                 Scope::MacroUsePrelude => match this.macro_use_prelude.get(&ident.name).cloned() {
                     Some(binding) => Ok((binding, Flags::PRELUDE | Flags::MISC_FROM_PRELUDE)),
                     None => Err(Determinacy::determined(
@@ -560,12 +609,14 @@ impl<'a> Resolver<'a> {
                         this.graph_root.unexpanded_invocations.borrow().is_empty()
                     )),
                 }
-                Scope::ToolPrelude => if KNOWN_TOOLS.contains(&ident.name) {
-                    let binding = (Res::ToolMod, ty::Visibility::Public, DUMMY_SP, ExpnId::root())
-                                   .to_name_binding(this.arenas);
-                    Ok((binding, Flags::PRELUDE))
-                } else {
-                    Err(Determinacy::Determined)
+                Scope::ToolPrelude => match this.registered_tools.get(&ident).cloned() {
+                    Some(ident) => {
+                        let binding = (Res::ToolMod,
+                                       ty::Visibility::Public, ident.span, ExpnId::root())
+                                       .to_name_binding(this.arenas);
+                        Ok((binding, Flags::PRELUDE))
+                    }
+                    None => Err(Determinacy::Determined)
                 }
                 Scope::StdLibPrelude => {
                     let mut result = Err(Determinacy::Determined);
diff --git a/src/libsyntax/feature_gate/active.rs b/src/libsyntax/feature_gate/active.rs
index 1d198fca56b..087a754b27e 100644
--- a/src/libsyntax/feature_gate/active.rs
+++ b/src/libsyntax/feature_gate/active.rs
@@ -526,6 +526,12 @@ declare_features! (
     /// Allows using the `efiapi` ABI.
     (active, abi_efiapi, "1.40.0", Some(65815), None),
 
+    /// Allows using the `#[register_attr]` attribute.
+    (active, register_attr, "1.41.0", Some(29642), None),
+
+    /// Allows using the `#[register_attr]` attribute.
+    (active, register_tool, "1.41.0", Some(44690), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
diff --git a/src/libsyntax/feature_gate/builtin_attrs.rs b/src/libsyntax/feature_gate/builtin_attrs.rs
index b32a887c6b2..3e77b4cf495 100644
--- a/src/libsyntax/feature_gate/builtin_attrs.rs
+++ b/src/libsyntax/feature_gate/builtin_attrs.rs
@@ -329,6 +329,14 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
 
     gated!(ffi_returns_twice, Whitelisted, template!(Word), experimental!(ffi_returns_twice)),
     gated!(track_caller, Whitelisted, template!(Word), experimental!(track_caller)),
+    gated!(
+        register_attr, Whitelisted, template!(List: "attr1, attr2, ..."),
+        experimental!(register_attr),
+    ),
+    gated!(
+        register_tool, Whitelisted, template!(List: "tool1, tool2, ..."),
+        experimental!(register_tool),
+    ),
 
     // ==========================================================================
     // Internal attributes: Stability, deprecation, and unsafe:
diff --git a/src/libsyntax_pos/symbol.rs b/src/libsyntax_pos/symbol.rs
index ae82ffd6383..4df9530ac79 100644
--- a/src/libsyntax_pos/symbol.rs
+++ b/src/libsyntax_pos/symbol.rs
@@ -545,6 +545,8 @@ symbols! {
         recursion_limit,
         reexport_test_harness_main,
         reflect,
+        register_attr,
+        register_tool,
         relaxed_adts,
         repr,
         repr128,
diff --git a/src/test/ui/attributes/register-attr-tool-fail.rs b/src/test/ui/attributes/register-attr-tool-fail.rs
new file mode 100644
index 00000000000..84736be844b
--- /dev/null
+++ b/src/test/ui/attributes/register-attr-tool-fail.rs
@@ -0,0 +1,13 @@
+#![feature(register_attr)]
+#![feature(register_tool)]
+
+#![register_attr] //~ ERROR malformed `register_attr` attribute input
+#![register_tool] //~ ERROR malformed `register_tool` attribute input
+
+#![register_attr(a::b)] //~ ERROR `register_attr` only accepts identifiers
+#![register_tool(a::b)] //~ ERROR `register_tool` only accepts identifiers
+
+#![register_attr(attr, attr)] //~ ERROR attribute `attr` was already registered
+#![register_tool(tool, tool)] //~ ERROR tool `tool` was already registered
+
+fn main() {}
diff --git a/src/test/ui/attributes/register-attr-tool-fail.stderr b/src/test/ui/attributes/register-attr-tool-fail.stderr
new file mode 100644
index 00000000000..77acfcd87cf
--- /dev/null
+++ b/src/test/ui/attributes/register-attr-tool-fail.stderr
@@ -0,0 +1,42 @@
+error: `register_attr` only accepts identifiers
+  --> $DIR/register-attr-tool-fail.rs:7:18
+   |
+LL | #![register_attr(a::b)]
+   |                  ^^^^ not an identifier
+
+error: attribute `attr` was already registered
+  --> $DIR/register-attr-tool-fail.rs:10:24
+   |
+LL | #![register_attr(attr, attr)]
+   |                  ----  ^^^^
+   |                  |
+   |                  already registered here
+
+error: `register_tool` only accepts identifiers
+  --> $DIR/register-attr-tool-fail.rs:8:18
+   |
+LL | #![register_tool(a::b)]
+   |                  ^^^^ not an identifier
+
+error: tool `tool` was already registered
+  --> $DIR/register-attr-tool-fail.rs:11:24
+   |
+LL | #![register_tool(tool, tool)]
+   |                  ----  ^^^^
+   |                  |
+   |                  already registered here
+
+error: malformed `register_attr` attribute input
+  --> $DIR/register-attr-tool-fail.rs:4:1
+   |
+LL | #![register_attr]
+   | ^^^^^^^^^^^^^^^^^ help: must be of the form: `#[register_attr(attr1, attr2, ...)]`
+
+error: malformed `register_tool` attribute input
+  --> $DIR/register-attr-tool-fail.rs:5:1
+   |
+LL | #![register_tool]
+   | ^^^^^^^^^^^^^^^^^ help: must be of the form: `#[register_tool(tool1, tool2, ...)]`
+
+error: aborting due to 6 previous errors
+
diff --git a/src/test/ui/attributes/register-attr-tool.rs b/src/test/ui/attributes/register-attr-tool.rs
new file mode 100644
index 00000000000..ee9da74d4fb
--- /dev/null
+++ b/src/test/ui/attributes/register-attr-tool.rs
@@ -0,0 +1,19 @@
+// check-pass
+// compile-flags: --cfg foo
+
+#![feature(register_attr)]
+#![feature(register_tool)]
+
+#![register_attr(attr)]
+#![register_tool(tool)]
+#![register_tool(rustfmt, clippy)] // OK
+#![cfg_attr(foo, register_attr(conditional_attr))]
+#![cfg_attr(foo, register_tool(conditional_tool))]
+
+#[attr]
+#[tool::attr]
+#[rustfmt::attr]
+#[clippy::attr]
+#[conditional_attr]
+#[conditional_tool::attr]
+fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-register_attr.rs b/src/test/ui/feature-gates/feature-gate-register_attr.rs
new file mode 100644
index 00000000000..36dce2aa7b9
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-register_attr.rs
@@ -0,0 +1,3 @@
+#![register_attr(attr)] //~ ERROR the `#[register_attr]` attribute is an experimental feature
+
+fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-register_attr.stderr b/src/test/ui/feature-gates/feature-gate-register_attr.stderr
new file mode 100644
index 00000000000..b097f578bf2
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-register_attr.stderr
@@ -0,0 +1,12 @@
+error[E0658]: the `#[register_attr]` attribute is an experimental feature
+  --> $DIR/feature-gate-register_attr.rs:1:1
+   |
+LL | #![register_attr(attr)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: for more information, see https://github.com/rust-lang/rust/issues/29642
+   = help: add `#![feature(register_attr)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/feature-gates/feature-gate-register_tool.rs b/src/test/ui/feature-gates/feature-gate-register_tool.rs
new file mode 100644
index 00000000000..e599593283b
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-register_tool.rs
@@ -0,0 +1,3 @@
+#![register_tool(tool)] //~ ERROR the `#[register_tool]` attribute is an experimental feature
+
+fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-register_tool.stderr b/src/test/ui/feature-gates/feature-gate-register_tool.stderr
new file mode 100644
index 00000000000..85a86d2daf8
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-register_tool.stderr
@@ -0,0 +1,12 @@
+error[E0658]: the `#[register_tool]` attribute is an experimental feature
+  --> $DIR/feature-gate-register_tool.rs:1:1
+   |
+LL | #![register_tool(tool)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: for more information, see https://github.com/rust-lang/rust/issues/44690
+   = help: add `#![feature(register_tool)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.