diff --git a/src/librustc/plugin/registry.rs b/src/librustc/plugin/registry.rs index 04df464129541..5faaa53e4d0b5 100644 --- a/src/librustc/plugin/registry.rs +++ b/src/librustc/plugin/registry.rs @@ -20,6 +20,7 @@ use syntax::codemap::Span; use syntax::parse::token; use syntax::ptr::P; use syntax::ast; +use syntax::feature_gate::AttributeType; use std::collections::HashMap; use std::borrow::ToOwned; @@ -54,6 +55,9 @@ pub struct Registry<'a> { #[doc(hidden)] pub llvm_passes: Vec, + + #[doc(hidden)] + pub attributes: Vec<(String, AttributeType)>, } impl<'a> Registry<'a> { @@ -67,6 +71,7 @@ impl<'a> Registry<'a> { lint_passes: vec!(), lint_groups: HashMap::new(), llvm_passes: vec!(), + attributes: vec!(), } } @@ -132,4 +137,19 @@ impl<'a> Registry<'a> { pub fn register_llvm_pass(&mut self, name: &str) { self.llvm_passes.push(name.to_owned()); } + + + /// Register an attribute with an attribute type. + /// + /// Registered attributes will bypass the `custom_attribute` feature gate. + /// `Whitelisted` attributes will additionally not trigger the `unused_attribute` + /// lint. `CrateLevel` attributes will not be allowed on anything other than a crate. + pub fn register_attribute(&mut self, name: String, ty: AttributeType) { + if let AttributeType::Gated(..) = ty { + self.sess.span_err(self.krate_span, "plugin tried to register a gated \ + attribute. Only `Normal`, `Whitelisted`, \ + and `CrateLevel` attributes are allowed"); + } + self.attributes.push((name, ty)); + } } diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 87634886009ef..6b5f58720ab15 100644 --- a/src/librustc/session/mod.rs +++ b/src/librustc/session/mod.rs @@ -23,6 +23,7 @@ use syntax::parse; use syntax::parse::token; use syntax::parse::ParseSess; use syntax::{ast, codemap}; +use syntax::feature_gate::AttributeType; use rustc_back::target::Target; @@ -54,6 +55,7 @@ pub struct Session { pub lint_store: RefCell, pub lints: RefCell>>, pub plugin_llvm_passes: RefCell>, + pub plugin_attributes: RefCell>, pub crate_types: RefCell>, pub crate_metadata: RefCell>, pub features: RefCell, @@ -425,6 +427,7 @@ pub fn build_session_(sopts: config::Options, lint_store: RefCell::new(lint::LintStore::new()), lints: RefCell::new(NodeMap()), plugin_llvm_passes: RefCell::new(Vec::new()), + plugin_attributes: RefCell::new(Vec::new()), crate_types: RefCell::new(Vec::new()), crate_metadata: RefCell::new(Vec::new()), delayed_span_bug: RefCell::new(None), diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index 9c78c5aec00b4..7c49f1e01d85e 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -444,7 +444,8 @@ pub fn phase_2_configure_and_expand(sess: &Session, } }); - let Registry { syntax_exts, lint_passes, lint_groups, llvm_passes, .. } = registry; + let Registry { syntax_exts, lint_passes, lint_groups, + llvm_passes, attributes, .. } = registry; { let mut ls = sess.lint_store.borrow_mut(); @@ -457,6 +458,7 @@ pub fn phase_2_configure_and_expand(sess: &Session, } *sess.plugin_llvm_passes.borrow_mut() = llvm_passes; + *sess.plugin_attributes.borrow_mut() = attributes.clone(); } // Lint plugins are registered; now we can process command line flags. @@ -511,7 +513,7 @@ pub fn phase_2_configure_and_expand(sess: &Session, let features = syntax::feature_gate::check_crate(sess.codemap(), &sess.parse_sess.span_diagnostic, - &krate); + &krate, &attributes); *sess.features.borrow_mut() = features; sess.abort_if_errors(); }); @@ -541,7 +543,7 @@ pub fn phase_2_configure_and_expand(sess: &Session, let features = syntax::feature_gate::check_crate(sess.codemap(), &sess.parse_sess.span_diagnostic, - &krate); + &krate, &attributes); *sess.features.borrow_mut() = features; sess.abort_if_errors(); }); diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index 6721c4a6636a9..f50abc7b3ad2a 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -641,9 +641,26 @@ impl LintPass for UnusedAttributes { } } + let plugin_attributes = cx.sess().plugin_attributes.borrow_mut(); + for &(ref name, ty) in plugin_attributes.iter() { + if ty == AttributeType::Whitelisted && attr.check_name(&*name) { + break; + } + } + if !attr::is_used(attr) { cx.span_lint(UNUSED_ATTRIBUTES, attr.span, "unused attribute"); - if KNOWN_ATTRIBUTES.contains(&(&attr.name(), AttributeType::CrateLevel)) { + // Is it a builtin attribute that must be used at the crate level? + let known_crate = KNOWN_ATTRIBUTES.contains(&(&attr.name(), + AttributeType::CrateLevel)); + // Has a plugin registered this attribute as one which must be used at + // the crate level? + let plugin_crate = plugin_attributes.iter() + .find(|&&(ref x, t)| { + &*attr.name() == &*x && + AttributeType::CrateLevel == t + }).is_some(); + if known_crate || plugin_crate { let msg = match attr.node.style { ast::AttrOuter => "crate-level attribute should be an inner \ attribute: add an exclamation mark: #![foo]", diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index d79714248c89b..bd73e6b7de70d 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -358,6 +358,7 @@ struct Context<'a> { features: Vec<&'static str>, span_handler: &'a SpanHandler, cm: &'a CodeMap, + plugin_attributes: &'a [(String, AttributeType)], } impl<'a> Context<'a> { @@ -372,7 +373,7 @@ impl<'a> Context<'a> { self.features.iter().any(|&n| n == feature) } - fn check_attribute(&self, attr: &ast::Attribute) { + fn check_attribute(&self, attr: &ast::Attribute, is_macro: bool) { debug!("check_attribute(attr = {:?})", attr); let name = &*attr.name(); for &(n, ty) in KNOWN_ATTRIBUTES { @@ -384,6 +385,15 @@ impl<'a> Context<'a> { return; } } + for &(ref n, ref ty) in self.plugin_attributes.iter() { + if &*n == name { + // Plugins can't gate attributes, so we don't check for it + // unlike the code above; we only use this loop to + // short-circuit to avoid the checks below + debug!("check_attribute: {:?} is registered by a plugin, {:?}", name, ty); + return; + } + } if name.starts_with("rustc_") { self.gate_feature("rustc_attrs", attr.span, "unless otherwise specified, attributes \ @@ -394,12 +404,18 @@ impl<'a> Context<'a> { "attributes of the form `#[derive_*]` are reserved \ for the compiler"); } else { - self.gate_feature("custom_attribute", attr.span, - &format!("The attribute `{}` is currently \ - unknown to the compiler and \ - may have meaning \ - added to it in the future", - name)); + // Only run the custom attribute lint during regular + // feature gate checking. Macro gating runs + // before the plugin attributes are registered + // so we skip this then + if !is_macro { + self.gate_feature("custom_attribute", attr.span, + &format!("The attribute `{}` is currently \ + unknown to the compiler and \ + may have meaning \ + added to it in the future", + name)); + } } } } @@ -478,7 +494,7 @@ impl<'a, 'v> Visitor<'v> for MacroVisitor<'a> { } fn visit_attribute(&mut self, attr: &'v ast::Attribute) { - self.context.check_attribute(attr); + self.context.check_attribute(attr, true); } } @@ -497,7 +513,7 @@ impl<'a> PostExpansionVisitor<'a> { impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { fn visit_attribute(&mut self, attr: &ast::Attribute) { if !self.context.cm.span_allows_unstable(attr.span) { - self.context.check_attribute(attr); + self.context.check_attribute(attr, false); } } @@ -684,6 +700,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> { fn check_crate_inner(cm: &CodeMap, span_handler: &SpanHandler, krate: &ast::Crate, + plugin_attributes: &[(String, AttributeType)], check: F) -> Features where F: FnOnce(&mut Context, &ast::Crate) @@ -692,6 +709,7 @@ fn check_crate_inner(cm: &CodeMap, span_handler: &SpanHandler, features: Vec::new(), span_handler: span_handler, cm: cm, + plugin_attributes: plugin_attributes, }; let mut accepted_features = Vec::new(); @@ -764,14 +782,14 @@ fn check_crate_inner(cm: &CodeMap, span_handler: &SpanHandler, pub fn check_crate_macros(cm: &CodeMap, span_handler: &SpanHandler, krate: &ast::Crate) -> Features { - check_crate_inner(cm, span_handler, krate, + check_crate_inner(cm, span_handler, krate, &[] as &'static [_], |ctx, krate| visit::walk_crate(&mut MacroVisitor { context: ctx }, krate)) } -pub fn check_crate(cm: &CodeMap, span_handler: &SpanHandler, krate: &ast::Crate) - -> Features +pub fn check_crate(cm: &CodeMap, span_handler: &SpanHandler, krate: &ast::Crate, + plugin_attributes: &[(String, AttributeType)]) -> Features { - check_crate_inner(cm, span_handler, krate, + check_crate_inner(cm, span_handler, krate, plugin_attributes, |ctx, krate| visit::walk_crate(&mut PostExpansionVisitor { context: ctx }, krate)) } diff --git a/src/test/auxiliary/attr_plugin_test.rs b/src/test/auxiliary/attr_plugin_test.rs new file mode 100644 index 0000000000000..a6cae743cebce --- /dev/null +++ b/src/test/auxiliary/attr_plugin_test.rs @@ -0,0 +1,30 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// force-host + +#![feature(plugin_registrar)] +#![feature(rustc_private)] + +extern crate syntax; + +extern crate rustc; + +use syntax::feature_gate::AttributeType; +use rustc::plugin::Registry; + + + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_attribute("foo".to_owned(), AttributeType::Normal); + reg.register_attribute("bar".to_owned(), AttributeType::CrateLevel); + reg.register_attribute("baz".to_owned(), AttributeType::Whitelisted); +} diff --git a/src/test/compile-fail-fulldeps/plugin-attr-register-deny.rs b/src/test/compile-fail-fulldeps/plugin-attr-register-deny.rs new file mode 100644 index 0000000000000..0d2a5a30c106b --- /dev/null +++ b/src/test/compile-fail-fulldeps/plugin-attr-register-deny.rs @@ -0,0 +1,30 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:attr_plugin_test.rs +// ignore-stage1 + +#![feature(plugin)] +#![plugin(attr_plugin_test)] +#![deny(unused_attributes)] + +#[baz] +fn baz() { } // no error + +#[foo] +pub fn main() { + //~^^ ERROR unused + #[bar] + fn inner() {} + //~^^ ERROR crate + //~^^^ ERROR unused + baz(); + inner(); +}