1
Fork 0

Support registering attributes and attribute tools using crate-level attributes

This commit is contained in:
Vadim Petrochenkov 2019-11-03 20:28:20 +03:00
parent 5a5027519a
commit 3a223a9173
14 changed files with 210 additions and 20 deletions

View file

@ -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",
}

View file

@ -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 => {

View file

@ -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(

View file

@ -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);

View file

@ -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
// -------------------------------------------------------------------------

View file

@ -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:

View file

@ -545,6 +545,8 @@ symbols! {
recursion_limit,
reexport_test_harness_main,
reflect,
register_attr,
register_tool,
relaxed_adts,
repr,
repr128,

View 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() {}

View 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

View 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() {}

View file

@ -0,0 +1,3 @@
#![register_attr(attr)] //~ ERROR the `#[register_attr]` attribute is an experimental feature
fn main() {}

View 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`.

View file

@ -0,0 +1,3 @@
#![register_tool(tool)] //~ ERROR the `#[register_tool]` attribute is an experimental feature
fn main() {}

View 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`.