Support registering attributes and attribute tools using crate-level attributes
This commit is contained in:
parent
5a5027519a
commit
3a223a9173
14 changed files with 210 additions and 20 deletions
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -545,6 +545,8 @@ symbols! {
|
|||
recursion_limit,
|
||||
reexport_test_harness_main,
|
||||
reflect,
|
||||
register_attr,
|
||||
register_tool,
|
||||
relaxed_adts,
|
||||
repr,
|
||||
repr128,
|
||||
|
|
13
src/test/ui/attributes/register-attr-tool-fail.rs
Normal file
13
src/test/ui/attributes/register-attr-tool-fail.rs
Normal file
|
@ -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() {}
|
42
src/test/ui/attributes/register-attr-tool-fail.stderr
Normal file
42
src/test/ui/attributes/register-attr-tool-fail.stderr
Normal file
|
@ -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
|
||||
|
19
src/test/ui/attributes/register-attr-tool.rs
Normal file
19
src/test/ui/attributes/register-attr-tool.rs
Normal file
|
@ -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() {}
|
3
src/test/ui/feature-gates/feature-gate-register_attr.rs
Normal file
3
src/test/ui/feature-gates/feature-gate-register_attr.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
#![register_attr(attr)] //~ ERROR the `#[register_attr]` attribute is an experimental feature
|
||||
|
||||
fn main() {}
|
12
src/test/ui/feature-gates/feature-gate-register_attr.stderr
Normal file
12
src/test/ui/feature-gates/feature-gate-register_attr.stderr
Normal file
|
@ -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`.
|
3
src/test/ui/feature-gates/feature-gate-register_tool.rs
Normal file
3
src/test/ui/feature-gates/feature-gate-register_tool.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
#![register_tool(tool)] //~ ERROR the `#[register_tool]` attribute is an experimental feature
|
||||
|
||||
fn main() {}
|
12
src/test/ui/feature-gates/feature-gate-register_tool.stderr
Normal file
12
src/test/ui/feature-gates/feature-gate-register_tool.stderr
Normal file
|
@ -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`.
|
Loading…
Add table
Add a link
Reference in a new issue