auto merge of #14373 : sfackler/rust/unused-attr, r=huonw
The compiler now tracks which attributes were actually looked at during the compilation process and warns for those that were unused. Some things of note: * The tracking is done via thread locals, as it made the implementation more straightforward. Note that this shouldn't hamper any future parallelization as each task can have its own thread local state which can be merged for the lint pass. If there are serious objections to this, I can restructure things to explicitly pass the state around. * There are a number of attributes that have to be special-cased and globally whitelisted. This happens for four reasons: * The `doc` and `automatically_derived` attributes are used by rustdoc, but not by the compiler. * The crate-level attributes `license`, `desc` and `comment` aren't currently used by anything. * Stability attributes as well as `must_use` are checked only when the tagged item is used, so we can't guarantee that the compiler's looked at them. * 12 attributes are used only in trans, which happens after the lint pass. #14300 is adding infrastructure to track lint state through trans, which this lint should also be able to use to handle the last case. For the other attributes, the right solution would probably involve a specific pass to mark uses that occur in the correct context. For example, a `doc` attribute attached to a match arm should generate a warning, but will not currently. RFC: 0002-attribute-usage
This commit is contained in:
commit
07563be6eb
@ -661,6 +661,7 @@ Attributes on the anonymous crate module define important metadata that influenc
|
||||
the behavior of the compiler.
|
||||
|
||||
~~~~ {.rust}
|
||||
# #![allow(unused_attribute)]
|
||||
// Crate ID
|
||||
#![crate_id = "projx#2.5"]
|
||||
|
||||
|
@ -11,6 +11,7 @@ Documenting Rust APIs is quite simple. To document a given item, we have "doc
|
||||
comments":
|
||||
|
||||
~~~
|
||||
# #![allow(unused_attribute)]
|
||||
// the "link" crate attribute is currently required for rustdoc, but normally
|
||||
// isn't needed.
|
||||
#![crate_id = "universe"]
|
||||
|
@ -3166,6 +3166,7 @@ without conflict.
|
||||
Therefore, if you plan to compile your crate as a library, you should annotate it with that information:
|
||||
|
||||
~~~~
|
||||
# #![allow(unused_attribute)]
|
||||
// `lib.rs`
|
||||
|
||||
# #![crate_type = "lib"]
|
||||
@ -3189,6 +3190,7 @@ Other crate settings and metadata include things like enabling/disabling certain
|
||||
or setting the crate type (library or executable) explicitly:
|
||||
|
||||
~~~~
|
||||
# #![allow(unused_attribute)]
|
||||
// `lib.rs`
|
||||
// ...
|
||||
|
||||
@ -3208,6 +3210,7 @@ Now for something that you can actually compile yourself.
|
||||
We define two crates, and use one of them as a library in the other.
|
||||
|
||||
~~~~
|
||||
# #![allow(unused_attribute)]
|
||||
// `world.rs`
|
||||
#![crate_id = "world#0.42"]
|
||||
|
||||
@ -3282,11 +3285,13 @@ fn main() {
|
||||
Both auto-insertions can be disabled with an attribute if necessary:
|
||||
|
||||
~~~
|
||||
# #![allow(unused_attribute)]
|
||||
// In the crate root:
|
||||
#![no_std]
|
||||
~~~
|
||||
|
||||
~~~
|
||||
# #![allow(unused_attribute)]
|
||||
// In any module:
|
||||
#![no_implicit_prelude]
|
||||
~~~
|
||||
|
@ -91,7 +91,12 @@ pub fn calculate(krate: &ast::Crate) -> Svh {
|
||||
// types and then use hash_content. But, since all crate
|
||||
// attributes should appear near beginning of the file, it is
|
||||
// not such a big deal to be sensitive to their spans for now.
|
||||
krate.attrs.hash(&mut state);
|
||||
//
|
||||
// We hash only the MetaItems instead of the entire Attribute
|
||||
// to avoid hashing the AttrId
|
||||
for attr in krate.attrs.iter() {
|
||||
attr.node.value.hash(&mut state);
|
||||
}
|
||||
|
||||
let hash = state.result();
|
||||
return Svh {
|
||||
|
@ -716,6 +716,45 @@ fn expand_err_details(r: io::IoResult<()>) -> io::IoResult<()> {
|
||||
|
||||
pub fn collect_crate_types(session: &Session,
|
||||
attrs: &[ast::Attribute]) -> Vec<config::CrateType> {
|
||||
// Unconditionally collect crate types from attributes to make them used
|
||||
let attr_types: Vec<config::CrateType> = attrs.iter().filter_map(|a| {
|
||||
if a.check_name("crate_type") {
|
||||
match a.value_str() {
|
||||
Some(ref n) if n.equiv(&("rlib")) => {
|
||||
Some(config::CrateTypeRlib)
|
||||
}
|
||||
Some(ref n) if n.equiv(&("dylib")) => {
|
||||
Some(config::CrateTypeDylib)
|
||||
}
|
||||
Some(ref n) if n.equiv(&("lib")) => {
|
||||
Some(config::default_lib_output())
|
||||
}
|
||||
Some(ref n) if n.equiv(&("staticlib")) => {
|
||||
Some(config::CrateTypeStaticlib)
|
||||
}
|
||||
Some(ref n) if n.equiv(&("bin")) => Some(config::CrateTypeExecutable),
|
||||
Some(_) => {
|
||||
session.add_lint(lint::UnknownCrateType,
|
||||
ast::CRATE_NODE_ID,
|
||||
a.span,
|
||||
"invalid `crate_type` \
|
||||
value".to_strbuf());
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
session.add_lint(lint::UnknownCrateType,
|
||||
ast::CRATE_NODE_ID,
|
||||
a.span,
|
||||
"`crate_type` requires a \
|
||||
value".to_strbuf());
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).collect();
|
||||
|
||||
// If we're generating a test executable, then ignore all other output
|
||||
// styles at all other locations
|
||||
if session.opts.test {
|
||||
@ -729,44 +768,7 @@ pub fn collect_crate_types(session: &Session,
|
||||
if base.len() > 0 {
|
||||
return base
|
||||
} else {
|
||||
let iter = attrs.iter().filter_map(|a| {
|
||||
if a.name().equiv(&("crate_type")) {
|
||||
match a.value_str() {
|
||||
Some(ref n) if n.equiv(&("rlib")) => {
|
||||
Some(config::CrateTypeRlib)
|
||||
}
|
||||
Some(ref n) if n.equiv(&("dylib")) => {
|
||||
Some(config::CrateTypeDylib)
|
||||
}
|
||||
Some(ref n) if n.equiv(&("lib")) => {
|
||||
Some(config::default_lib_output())
|
||||
}
|
||||
Some(ref n) if n.equiv(&("staticlib")) => {
|
||||
Some(config::CrateTypeStaticlib)
|
||||
}
|
||||
Some(ref n) if n.equiv(&("bin")) => Some(config::CrateTypeExecutable),
|
||||
Some(_) => {
|
||||
session.add_lint(lint::UnknownCrateType,
|
||||
ast::CRATE_NODE_ID,
|
||||
a.span,
|
||||
"invalid `crate_type` \
|
||||
value".to_strbuf());
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
session.add_lint(lint::UnknownCrateType,
|
||||
ast::CRATE_NODE_ID,
|
||||
a.span,
|
||||
"`crate_type` requires a \
|
||||
value".to_strbuf());
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
base.extend(iter);
|
||||
base.extend(attr_types.move_iter());
|
||||
if base.len() == 0 {
|
||||
base.push(config::CrateTypeExecutable);
|
||||
}
|
||||
|
@ -327,7 +327,7 @@ pub fn check_crate(sess: &Session, krate: &ast::Crate) {
|
||||
};
|
||||
|
||||
for attr in krate.attrs.iter() {
|
||||
if !attr.name().equiv(&("feature")) {
|
||||
if !attr.check_name("feature") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ fn fold_crate(&mut self, mut krate: ast::Crate) -> ast::Crate {
|
||||
with_version("std"),
|
||||
ast::DUMMY_NODE_ID),
|
||||
attrs: vec!(
|
||||
attr::mk_attr_outer(attr::mk_list_item(
|
||||
attr::mk_attr_outer(attr::mk_attr_id(), attr::mk_list_item(
|
||||
InternedString::new("phase"),
|
||||
vec!(
|
||||
attr::mk_word_item(InternedString::new("syntax")),
|
||||
@ -110,10 +110,13 @@ fn fold_crate(&mut self, mut krate: ast::Crate) -> ast::Crate {
|
||||
// Add it during the prelude injection instead.
|
||||
|
||||
// Add #![feature(phase)] here, because we use #[phase] on extern crate std.
|
||||
let feat_phase_attr = attr::mk_attr_inner(attr::mk_list_item(
|
||||
let feat_phase_attr = attr::mk_attr_inner(attr::mk_attr_id(),
|
||||
attr::mk_list_item(
|
||||
InternedString::new("feature"),
|
||||
vec![attr::mk_word_item(InternedString::new("phase"))],
|
||||
));
|
||||
// std_inject runs after feature checking so manually mark this attr
|
||||
attr::mark_used(&feat_phase_attr);
|
||||
krate.attrs.push(feat_phase_attr);
|
||||
|
||||
krate
|
||||
@ -138,7 +141,10 @@ fn fold_crate(&mut self, mut krate: ast::Crate) -> ast::Crate {
|
||||
// This must happen here and not in StandardLibraryInjector because this
|
||||
// fold happens second.
|
||||
|
||||
let no_std_attr = attr::mk_attr_inner(attr::mk_word_item(InternedString::new("no_std")));
|
||||
let no_std_attr = attr::mk_attr_inner(attr::mk_attr_id(),
|
||||
attr::mk_word_item(InternedString::new("no_std")));
|
||||
// std_inject runs after feature checking so manually mark this attr
|
||||
attr::mark_used(&no_std_attr);
|
||||
krate.attrs.push(no_std_attr);
|
||||
|
||||
if !no_prelude(krate.attrs.as_slice()) {
|
||||
@ -146,11 +152,14 @@ fn fold_crate(&mut self, mut krate: ast::Crate) -> ast::Crate {
|
||||
// `#![no_implicit_prelude]` at the crate level.
|
||||
|
||||
// fold_mod() will insert glob path.
|
||||
let globs_attr = attr::mk_attr_inner(attr::mk_list_item(
|
||||
let globs_attr = attr::mk_attr_inner(attr::mk_attr_id(),
|
||||
attr::mk_list_item(
|
||||
InternedString::new("feature"),
|
||||
vec!(
|
||||
attr::mk_word_item(InternedString::new("globs")),
|
||||
)));
|
||||
// std_inject runs after feature checking so manually mark this attr
|
||||
attr::mark_used(&globs_attr);
|
||||
krate.attrs.push(globs_attr);
|
||||
|
||||
krate.module = self.fold_mod(&krate.module);
|
||||
|
@ -253,7 +253,7 @@ fn has_test_signature(i: @ast::Item) -> bool {
|
||||
fn is_ignored(cx: &TestCtxt, i: @ast::Item) -> bool {
|
||||
i.attrs.iter().any(|attr| {
|
||||
// check ignore(cfg(foo, bar))
|
||||
attr.name().equiv(&("ignore")) && match attr.meta_item_list() {
|
||||
attr.check_name("ignore") && match attr.meta_item_list() {
|
||||
Some(ref cfgs) => {
|
||||
attr::test_cfg(cx.config.as_slice(), cfgs.iter().map(|x| *x))
|
||||
}
|
||||
@ -341,7 +341,8 @@ pub fn main() {
|
||||
// This attribute tells resolve to let us call unexported functions
|
||||
let resolve_unexported_str = InternedString::new("!resolve_unexported");
|
||||
let resolve_unexported_attr =
|
||||
attr::mk_attr_inner(attr::mk_word_item(resolve_unexported_str));
|
||||
attr::mk_attr_inner(attr::mk_attr_id(),
|
||||
attr::mk_word_item(resolve_unexported_str));
|
||||
|
||||
let item = ast::Item {
|
||||
ident: token::str_to_ident("__test"),
|
||||
|
@ -176,7 +176,7 @@ pub fn get_static_methods_if_impl(cstore: &cstore::CStore,
|
||||
|
||||
pub fn get_item_attrs(cstore: &cstore::CStore,
|
||||
def_id: ast::DefId,
|
||||
f: |Vec<@ast::MetaItem> |) {
|
||||
f: |Vec<ast::Attribute> |) {
|
||||
let cdata = cstore.get_crate_data(def_id.krate);
|
||||
decoder::get_item_attrs(&*cdata, def_id.node, f)
|
||||
}
|
||||
|
@ -953,20 +953,14 @@ pub fn get_tuple_struct_definition_if_ctor(cdata: Cmd,
|
||||
|
||||
pub fn get_item_attrs(cdata: Cmd,
|
||||
orig_node_id: ast::NodeId,
|
||||
f: |Vec<@ast::MetaItem> |) {
|
||||
f: |Vec<ast::Attribute>|) {
|
||||
// The attributes for a tuple struct are attached to the definition, not the ctor;
|
||||
// we assume that someone passing in a tuple struct ctor is actually wanting to
|
||||
// look at the definition
|
||||
let node_id = get_tuple_struct_definition_if_ctor(cdata, orig_node_id);
|
||||
let node_id = node_id.map(|x| x.node).unwrap_or(orig_node_id);
|
||||
let item = lookup_item(node_id, cdata.data());
|
||||
reader::tagged_docs(item, tag_attributes, |attributes| {
|
||||
reader::tagged_docs(attributes, tag_attribute, |attribute| {
|
||||
f(get_meta_items(attribute));
|
||||
true
|
||||
});
|
||||
true
|
||||
});
|
||||
f(get_attributes(item));
|
||||
}
|
||||
|
||||
fn struct_field_family_to_visibility(family: Family) -> ast::Visibility {
|
||||
@ -1056,6 +1050,7 @@ fn get_attributes(md: ebml::Doc) -> Vec<ast::Attribute> {
|
||||
attrs.push(
|
||||
codemap::Spanned {
|
||||
node: ast::Attribute_ {
|
||||
id: attr::mk_attr_id(),
|
||||
style: ast::AttrOuter,
|
||||
value: meta_item,
|
||||
is_sugared_doc: false,
|
||||
|
@ -1436,7 +1436,7 @@ fn synthesize_crate_attrs(ecx: &EncodeContext,
|
||||
fn synthesize_crateid_attr(ecx: &EncodeContext) -> Attribute {
|
||||
assert!(!ecx.link_meta.crateid.name.is_empty());
|
||||
|
||||
attr::mk_attr_inner(
|
||||
attr::mk_attr_inner(attr::mk_attr_id(),
|
||||
attr::mk_name_value_item_str(
|
||||
InternedString::new("crate_id"),
|
||||
token::intern_and_get_ident(ecx.link_meta
|
||||
@ -1447,7 +1447,7 @@ fn synthesize_crateid_attr(ecx: &EncodeContext) -> Attribute {
|
||||
|
||||
let mut attrs = Vec::new();
|
||||
for attr in krate.attrs.iter() {
|
||||
if !attr.name().equiv(&("crate_id")) {
|
||||
if !attr.check_name("crate_id") {
|
||||
attrs.push(*attr);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@
|
||||
use syntax::abi;
|
||||
use syntax::ast_map;
|
||||
use syntax::ast_util::IdVisitingOperation;
|
||||
use syntax::attr::{AttrMetaMethods, AttributeMethods};
|
||||
use syntax::attr::AttrMetaMethods;
|
||||
use syntax::attr;
|
||||
use syntax::codemap::Span;
|
||||
use syntax::parse::token::InternedString;
|
||||
@ -90,6 +90,7 @@ pub enum Lint {
|
||||
UnusedUnsafe,
|
||||
UnsafeBlock,
|
||||
AttributeUsage,
|
||||
UnusedAttribute,
|
||||
UnknownFeatures,
|
||||
UnknownCrateType,
|
||||
UnsignedNegate,
|
||||
@ -288,6 +289,13 @@ enum LintSource {
|
||||
default: Warn
|
||||
}),
|
||||
|
||||
("unused_attribute",
|
||||
LintSpec {
|
||||
lint: UnusedAttribute,
|
||||
desc: "detects attributes that were not used by the compiler",
|
||||
default: Warn
|
||||
}),
|
||||
|
||||
("unused_variable",
|
||||
LintSpec {
|
||||
lint: UnusedVariable,
|
||||
@ -619,7 +627,7 @@ pub fn each_lint(sess: &session::Session,
|
||||
let xs = [Allow, Warn, Deny, Forbid];
|
||||
for &level in xs.iter() {
|
||||
let level_name = level_to_str(level);
|
||||
for attr in attrs.iter().filter(|m| m.name().equiv(&level_name)) {
|
||||
for attr in attrs.iter().filter(|m| m.check_name(level_name)) {
|
||||
let meta = attr.node.value;
|
||||
let metas = match meta.node {
|
||||
ast::MetaList(_, ref metas) => metas,
|
||||
@ -1137,6 +1145,54 @@ fn check_attrs_usage(cx: &Context, attrs: &[ast::Attribute]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_unused_attribute(cx: &Context, attrs: &[ast::Attribute]) {
|
||||
static ATTRIBUTE_WHITELIST: &'static [&'static str] = &'static [
|
||||
// FIXME: #14408 whitelist docs since rustdoc looks at them
|
||||
"doc",
|
||||
|
||||
// FIXME: #14406 these are processed in trans, which happens after the
|
||||
// lint pass
|
||||
"address_insignificant",
|
||||
"cold",
|
||||
"inline",
|
||||
"link",
|
||||
"link_name",
|
||||
"link_section",
|
||||
"no_builtins",
|
||||
"no_mangle",
|
||||
"no_split_stack",
|
||||
"packed",
|
||||
"static_assert",
|
||||
"thread_local",
|
||||
|
||||
// not used anywhere (!?) but apparently we want to keep them around
|
||||
"comment",
|
||||
"desc",
|
||||
"license",
|
||||
|
||||
// FIXME: #14407 these are only looked at on-demand so we can't
|
||||
// guarantee they'll have already been checked
|
||||
"deprecated",
|
||||
"experimental",
|
||||
"frozen",
|
||||
"locked",
|
||||
"must_use",
|
||||
"stable",
|
||||
"unstable",
|
||||
];
|
||||
for attr in attrs.iter() {
|
||||
for &name in ATTRIBUTE_WHITELIST.iter() {
|
||||
if attr.check_name(name) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !attr::is_used(attr) {
|
||||
cx.span_lint(UnusedAttribute, attr.span, "unused attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_heap_expr(cx: &Context, e: &ast::Expr) {
|
||||
let ty = ty::expr_ty(cx.tcx, e);
|
||||
check_heap_type(cx, e.span, ty);
|
||||
@ -1637,9 +1693,7 @@ fn check_stability(cx: &Context, e: &ast::Expr) {
|
||||
let stability = if ast_util::is_local(id) {
|
||||
// this crate
|
||||
let s = cx.tcx.map.with_attrs(id.node, |attrs| {
|
||||
attrs.map(|a| {
|
||||
attr::find_stability(a.iter().map(|a| a.meta()))
|
||||
})
|
||||
attrs.map(|a| attr::find_stability(a.as_slice()))
|
||||
});
|
||||
match s {
|
||||
Some(s) => s,
|
||||
@ -1655,9 +1709,9 @@ fn check_stability(cx: &Context, e: &ast::Expr) {
|
||||
let mut s = None;
|
||||
// run through all the attributes and take the first
|
||||
// stability one.
|
||||
csearch::get_item_attrs(&cx.tcx.sess.cstore, id, |meta_items| {
|
||||
csearch::get_item_attrs(&cx.tcx.sess.cstore, id, |attrs| {
|
||||
if s.is_none() {
|
||||
s = attr::find_stability(meta_items.move_iter())
|
||||
s = attr::find_stability(attrs.as_slice())
|
||||
}
|
||||
});
|
||||
s
|
||||
@ -1694,6 +1748,7 @@ fn visit_item(&mut self, it: &ast::Item, _: ()) {
|
||||
check_heap_item(cx, it);
|
||||
check_missing_doc_item(cx, it);
|
||||
check_attrs_usage(cx, it.attrs.as_slice());
|
||||
check_unused_attribute(cx, it.attrs.as_slice());
|
||||
check_raw_ptr_deriving(cx, it);
|
||||
|
||||
cx.visit_ids(|v| v.visit_item(it, ()));
|
||||
@ -1900,6 +1955,7 @@ pub fn check_crate(tcx: &ty::ctxt,
|
||||
check_crate_attrs_usage(cx, krate.attrs.as_slice());
|
||||
// since the root module isn't visited as an item (because it isn't an item), warn for it
|
||||
// here.
|
||||
check_unused_attribute(cx, krate.attrs.as_slice());
|
||||
check_missing_doc_attrs(cx,
|
||||
None,
|
||||
krate.attrs.as_slice(),
|
||||
|
@ -227,9 +227,8 @@ fn get_extern_rust_fn(ccx: &CrateContext, fn_ty: ty::t, name: &str, did: ast::De
|
||||
|
||||
let f = decl_rust_fn(ccx, fn_ty, name);
|
||||
|
||||
csearch::get_item_attrs(&ccx.sess().cstore, did, |meta_items| {
|
||||
set_llvm_fn_attrs(meta_items.iter().map(|&x| attr::mk_attr_outer(x))
|
||||
.collect::<Vec<_>>().as_slice(), f)
|
||||
csearch::get_item_attrs(&ccx.sess().cstore, did, |attrs| {
|
||||
set_llvm_fn_attrs(attrs.as_slice(), f)
|
||||
});
|
||||
|
||||
ccx.externs.borrow_mut().insert(name.to_strbuf(), f);
|
||||
|
@ -3889,20 +3889,22 @@ pub fn lookup_trait_def(cx: &ctxt, did: ast::DefId) -> Rc<ty::TraitDef> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over meta_items of a definition.
|
||||
/// Iterate over attributes of a definition.
|
||||
// (This should really be an iterator, but that would require csearch and
|
||||
// decoder to use iterators instead of higher-order functions.)
|
||||
pub fn each_attr(tcx: &ctxt, did: DefId, f: |@ast::MetaItem| -> bool) -> bool {
|
||||
pub fn each_attr(tcx: &ctxt, did: DefId, f: |&ast::Attribute| -> bool) -> bool {
|
||||
if is_local(did) {
|
||||
let item = tcx.map.expect_item(did.node);
|
||||
item.attrs.iter().advance(|attr| f(attr.node.value))
|
||||
item.attrs.iter().advance(|attr| f(attr))
|
||||
} else {
|
||||
info!("getting foreign attrs");
|
||||
let mut cont = true;
|
||||
csearch::get_item_attrs(&tcx.sess.cstore, did, |meta_items| {
|
||||
csearch::get_item_attrs(&tcx.sess.cstore, did, |attrs| {
|
||||
if cont {
|
||||
cont = meta_items.iter().advance(|ptrptr| f(*ptrptr));
|
||||
cont = attrs.iter().advance(|attr| f(attr));
|
||||
}
|
||||
});
|
||||
info!("done");
|
||||
cont
|
||||
}
|
||||
}
|
||||
@ -3911,7 +3913,7 @@ pub fn each_attr(tcx: &ctxt, did: DefId, f: |@ast::MetaItem| -> bool) -> bool {
|
||||
pub fn has_attr(tcx: &ctxt, did: DefId, attr: &str) -> bool {
|
||||
let mut found = false;
|
||||
each_attr(tcx, did, |item| {
|
||||
if item.name().equiv(&attr) {
|
||||
if item.check_name(attr) {
|
||||
found = true;
|
||||
false
|
||||
} else {
|
||||
|
@ -314,9 +314,9 @@ fn clean(&self) -> Attribute {
|
||||
}
|
||||
|
||||
// This is a rough approximation that gets us what we want.
|
||||
impl<'a> attr::AttrMetaMethods for &'a Attribute {
|
||||
impl attr::AttrMetaMethods for Attribute {
|
||||
fn name(&self) -> InternedString {
|
||||
match **self {
|
||||
match *self {
|
||||
Word(ref n) | List(ref n, _) | NameValue(ref n, _) => {
|
||||
token::intern_and_get_ident(n.as_slice())
|
||||
}
|
||||
@ -324,7 +324,7 @@ fn name(&self) -> InternedString {
|
||||
}
|
||||
|
||||
fn value_str(&self) -> Option<InternedString> {
|
||||
match **self {
|
||||
match *self {
|
||||
NameValue(_, ref v) => {
|
||||
Some(token::intern_and_get_ident(v.as_slice()))
|
||||
}
|
||||
|
@ -1091,8 +1091,8 @@ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
shortty(self.item), self.item.name.get_ref().as_slice()));
|
||||
|
||||
// Write stability attributes
|
||||
match attr::find_stability(self.item.attrs.iter()) {
|
||||
Some(ref stability) => {
|
||||
match attr::find_stability_generic(self.item.attrs.iter()) {
|
||||
Some((ref stability, _)) => {
|
||||
try!(write!(fmt,
|
||||
"<a class='stability {lvl}' title='{reason}'>{lvl}</a>",
|
||||
lvl = stability.level.to_str(),
|
||||
|
@ -1024,9 +1024,13 @@ pub enum AttrStyle {
|
||||
AttrInner,
|
||||
}
|
||||
|
||||
#[deriving(Clone, Eq, TotalEq, Encodable, Decodable, Hash)]
|
||||
pub struct AttrId(pub uint);
|
||||
|
||||
// doc-comments are promoted to attributes that have is_sugared_doc = true
|
||||
#[deriving(Clone, Eq, TotalEq, Encodable, Decodable, Hash)]
|
||||
pub struct Attribute_ {
|
||||
pub id: AttrId,
|
||||
pub style: AttrStyle,
|
||||
pub value: @MetaItem,
|
||||
pub is_sugared_doc: bool,
|
||||
|
@ -11,7 +11,7 @@
|
||||
// Functions dealing with attributes and meta items
|
||||
|
||||
use ast;
|
||||
use ast::{Attribute, Attribute_, MetaItem, MetaWord, MetaNameValue, MetaList};
|
||||
use ast::{AttrId, Attribute, Attribute_, MetaItem, MetaWord, MetaNameValue, MetaList};
|
||||
use codemap::{Span, Spanned, spanned, dummy_spanned};
|
||||
use codemap::BytePos;
|
||||
use diagnostic::SpanHandler;
|
||||
@ -21,11 +21,26 @@
|
||||
use crateid::CrateId;
|
||||
|
||||
use collections::HashSet;
|
||||
use collections::bitv::BitvSet;
|
||||
|
||||
local_data_key!(used_attrs: BitvSet)
|
||||
|
||||
pub fn mark_used(attr: &Attribute) {
|
||||
let mut used = used_attrs.replace(None).unwrap_or_else(|| BitvSet::new());
|
||||
let AttrId(id) = attr.node.id;
|
||||
used.insert(id);
|
||||
used_attrs.replace(Some(used));
|
||||
}
|
||||
|
||||
pub fn is_used(attr: &Attribute) -> bool {
|
||||
let AttrId(id) = attr.node.id;
|
||||
used_attrs.get().map_or(false, |used| used.contains(&id))
|
||||
}
|
||||
|
||||
pub trait AttrMetaMethods {
|
||||
// This could be changed to `fn check_name(&self, name: InternedString) ->
|
||||
// bool` which would facilitate a side table recording which
|
||||
// attributes/meta items are used/unused.
|
||||
fn check_name(&self, name: &str) -> bool {
|
||||
name == self.name().get()
|
||||
}
|
||||
|
||||
/// Retrieve the name of the meta item, e.g. foo in #[foo],
|
||||
/// #[foo="bar"] and #[foo(bar)]
|
||||
@ -47,6 +62,13 @@ pub trait AttrMetaMethods {
|
||||
}
|
||||
|
||||
impl AttrMetaMethods for Attribute {
|
||||
fn check_name(&self, name: &str) -> bool {
|
||||
let matches = name == self.name().get();
|
||||
if matches {
|
||||
mark_used(self);
|
||||
}
|
||||
matches
|
||||
}
|
||||
fn name(&self) -> InternedString { self.meta().name() }
|
||||
fn value_str(&self) -> Option<InternedString> {
|
||||
self.meta().value_str()
|
||||
@ -127,9 +149,9 @@ fn desugar_doc(&self) -> Attribute {
|
||||
token::intern_and_get_ident(strip_doc_comment_decoration(
|
||||
comment.get()).as_slice()));
|
||||
if self.node.style == ast::AttrOuter {
|
||||
mk_attr_outer(meta)
|
||||
mk_attr_outer(self.node.id, meta)
|
||||
} else {
|
||||
mk_attr_inner(meta)
|
||||
mk_attr_inner(self.node.id, meta)
|
||||
}
|
||||
} else {
|
||||
*self
|
||||
@ -158,9 +180,18 @@ pub fn mk_word_item(name: InternedString) -> @MetaItem {
|
||||
@dummy_spanned(MetaWord(name))
|
||||
}
|
||||
|
||||
local_data_key!(next_attr_id: uint)
|
||||
|
||||
pub fn mk_attr_id() -> AttrId {
|
||||
let id = next_attr_id.replace(None).unwrap_or(0);
|
||||
next_attr_id.replace(Some(id + 1));
|
||||
AttrId(id)
|
||||
}
|
||||
|
||||
/// Returns an inner attribute with the given value.
|
||||
pub fn mk_attr_inner(item: @MetaItem) -> Attribute {
|
||||
pub fn mk_attr_inner(id: AttrId, item: @MetaItem) -> Attribute {
|
||||
dummy_spanned(Attribute_ {
|
||||
id: id,
|
||||
style: ast::AttrInner,
|
||||
value: item,
|
||||
is_sugared_doc: false,
|
||||
@ -168,19 +199,22 @@ pub fn mk_attr_inner(item: @MetaItem) -> Attribute {
|
||||
}
|
||||
|
||||
/// Returns an outer attribute with the given value.
|
||||
pub fn mk_attr_outer(item: @MetaItem) -> Attribute {
|
||||
pub fn mk_attr_outer(id: AttrId, item: @MetaItem) -> Attribute {
|
||||
dummy_spanned(Attribute_ {
|
||||
id: id,
|
||||
style: ast::AttrOuter,
|
||||
value: item,
|
||||
is_sugared_doc: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn mk_sugared_doc_attr(text: InternedString, lo: BytePos, hi: BytePos)
|
||||
pub fn mk_sugared_doc_attr(id: AttrId, text: InternedString, lo: BytePos,
|
||||
hi: BytePos)
|
||||
-> Attribute {
|
||||
let style = doc_comment_style(text.get());
|
||||
let lit = spanned(lo, hi, ast::LitStr(text, ast::CookedStr));
|
||||
let attr = Attribute_ {
|
||||
id: id,
|
||||
style: style,
|
||||
value: @spanned(lo, hi, MetaNameValue(InternedString::new("doc"),
|
||||
lit)),
|
||||
@ -206,14 +240,14 @@ pub fn contains_name<AM: AttrMetaMethods>(metas: &[AM], name: &str) -> bool {
|
||||
debug!("attr::contains_name (name={})", name);
|
||||
metas.iter().any(|item| {
|
||||
debug!(" testing: {}", item.name());
|
||||
item.name().equiv(&name)
|
||||
item.check_name(name)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn first_attr_value_str_by_name(attrs: &[Attribute], name: &str)
|
||||
-> Option<InternedString> {
|
||||
attrs.iter()
|
||||
.find(|at| at.name().equiv(&name))
|
||||
.find(|at| at.check_name(name))
|
||||
.and_then(|at| at.value_str())
|
||||
}
|
||||
|
||||
@ -221,7 +255,7 @@ pub fn last_meta_item_value_str_by_name(items: &[@MetaItem], name: &str)
|
||||
-> Option<InternedString> {
|
||||
items.iter()
|
||||
.rev()
|
||||
.find(|mi| mi.name().equiv(&name))
|
||||
.find(|mi| mi.check_name(name))
|
||||
.and_then(|i| i.value_str())
|
||||
}
|
||||
|
||||
@ -257,7 +291,7 @@ pub fn sort_meta_items(items: &[@MetaItem]) -> Vec<@MetaItem> {
|
||||
*/
|
||||
pub fn find_linkage_metas(attrs: &[Attribute]) -> Vec<@MetaItem> {
|
||||
let mut result = Vec::new();
|
||||
for attr in attrs.iter().filter(|at| at.name().equiv(&("link"))) {
|
||||
for attr in attrs.iter().filter(|at| at.check_name("link")) {
|
||||
match attr.meta().node {
|
||||
MetaList(_, ref items) => result.push_all(items.as_slice()),
|
||||
_ => ()
|
||||
@ -286,17 +320,21 @@ pub fn find_inline_attr(attrs: &[Attribute]) -> InlineAttr {
|
||||
// FIXME (#2809)---validate the usage of #[inline] and #[inline]
|
||||
attrs.iter().fold(InlineNone, |ia,attr| {
|
||||
match attr.node.value.node {
|
||||
MetaWord(ref n) if n.equiv(&("inline")) => InlineHint,
|
||||
MetaList(ref n, ref items) if n.equiv(&("inline")) => {
|
||||
if contains_name(items.as_slice(), "always") {
|
||||
InlineAlways
|
||||
} else if contains_name(items.as_slice(), "never") {
|
||||
InlineNever
|
||||
} else {
|
||||
MetaWord(ref n) if n.equiv(&("inline")) => {
|
||||
mark_used(attr);
|
||||
InlineHint
|
||||
}
|
||||
}
|
||||
_ => ia
|
||||
MetaList(ref n, ref items) if n.equiv(&("inline")) => {
|
||||
mark_used(attr);
|
||||
if contains_name(items.as_slice(), "always") {
|
||||
InlineAlways
|
||||
} else if contains_name(items.as_slice(), "never") {
|
||||
InlineNever
|
||||
} else {
|
||||
InlineHint
|
||||
}
|
||||
}
|
||||
_ => ia
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -314,9 +352,9 @@ pub fn test_cfg<AM: AttrMetaMethods, It: Iterator<AM>>
|
||||
|
||||
// this would be much nicer as a chain of iterator adaptors, but
|
||||
// this doesn't work.
|
||||
let some_cfg_matches = metas.any(|mi| {
|
||||
let some_cfg_matches = metas.fold(false, |matches, mi| {
|
||||
debug!("testing name: {}", mi.name());
|
||||
if mi.name().equiv(&("cfg")) { // it is a #[cfg()] attribute
|
||||
let this_matches = if mi.check_name("cfg") { // it is a #[cfg()] attribute
|
||||
debug!("is cfg");
|
||||
no_cfgs = false;
|
||||
// only #[cfg(...)] ones are understood.
|
||||
@ -344,7 +382,8 @@ pub fn test_cfg<AM: AttrMetaMethods, It: Iterator<AM>>
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
matches || this_matches
|
||||
});
|
||||
debug!("test_cfg (no_cfgs={}, some_cfg_matches={})", no_cfgs, some_cfg_matches);
|
||||
no_cfgs || some_cfg_matches
|
||||
@ -367,11 +406,13 @@ pub enum StabilityLevel {
|
||||
Locked
|
||||
}
|
||||
|
||||
/// Find the first stability attribute. `None` if none exists.
|
||||
pub fn find_stability<AM: AttrMetaMethods, It: Iterator<AM>>(mut metas: It)
|
||||
-> Option<Stability> {
|
||||
for m in metas {
|
||||
let level = match m.name().get() {
|
||||
pub fn find_stability_generic<'a,
|
||||
AM: AttrMetaMethods,
|
||||
I: Iterator<&'a AM>>
|
||||
(mut attrs: I)
|
||||
-> Option<(Stability, &'a AM)> {
|
||||
for attr in attrs {
|
||||
let level = match attr.name().get() {
|
||||
"deprecated" => Deprecated,
|
||||
"experimental" => Experimental,
|
||||
"unstable" => Unstable,
|
||||
@ -381,14 +422,22 @@ pub fn find_stability<AM: AttrMetaMethods, It: Iterator<AM>>(mut metas: It)
|
||||
_ => continue // not a stability level
|
||||
};
|
||||
|
||||
return Some(Stability {
|
||||
return Some((Stability {
|
||||
level: level,
|
||||
text: m.value_str()
|
||||
});
|
||||
text: attr.value_str()
|
||||
}, attr));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Find the first stability attribute. `None` if none exists.
|
||||
pub fn find_stability(attrs: &[Attribute]) -> Option<Stability> {
|
||||
find_stability_generic(attrs.iter()).map(|(s, attr)| {
|
||||
mark_used(attr);
|
||||
s
|
||||
})
|
||||
}
|
||||
|
||||
pub fn require_unique_names(diagnostic: &SpanHandler, metas: &[@MetaItem]) {
|
||||
let mut set = HashSet::new();
|
||||
for meta in metas.iter() {
|
||||
@ -415,11 +464,12 @@ pub fn require_unique_names(diagnostic: &SpanHandler, metas: &[@MetaItem]) {
|
||||
* present (before fields, if any) with that type; reprensentation
|
||||
* optimizations which would remove it will not be done.
|
||||
*/
|
||||
pub fn find_repr_attr(diagnostic: &SpanHandler, attr: @ast::MetaItem, acc: ReprAttr)
|
||||
pub fn find_repr_attr(diagnostic: &SpanHandler, attr: &Attribute, acc: ReprAttr)
|
||||
-> ReprAttr {
|
||||
let mut acc = acc;
|
||||
match attr.node {
|
||||
match attr.node.value.node {
|
||||
ast::MetaList(ref s, ref items) if s.equiv(&("repr")) => {
|
||||
mark_used(attr);
|
||||
for item in items.iter() {
|
||||
match item.node {
|
||||
ast::MetaWord(ref word) => {
|
||||
|
@ -12,6 +12,7 @@
|
||||
use ast::{P, Ident};
|
||||
use ast;
|
||||
use ast_util;
|
||||
use attr;
|
||||
use codemap::{Span, respan, Spanned, DUMMY_SP};
|
||||
use ext::base::ExtCtxt;
|
||||
use ext::quote::rt::*;
|
||||
@ -927,6 +928,7 @@ fn item_ty(&self, span: Span, name: Ident, ty: P<ast::Ty>) -> @ast::Item {
|
||||
|
||||
fn attribute(&self, sp: Span, mi: @ast::MetaItem) -> ast::Attribute {
|
||||
respan(sp, ast::Attribute_ {
|
||||
id: attr::mk_attr_id(),
|
||||
style: ast::AttrOuter,
|
||||
value: mi,
|
||||
is_sugared_doc: false,
|
||||
|
@ -182,6 +182,7 @@ fn eq(&self, other: &int) -> bool {
|
||||
use ast;
|
||||
use ast::{P, EnumDef, Expr, Ident, Generics, StructDef};
|
||||
use ast_util;
|
||||
use attr;
|
||||
use attr::AttrMetaMethods;
|
||||
use ext::base::ExtCtxt;
|
||||
use ext::build::AstBuilder;
|
||||
@ -430,6 +431,8 @@ fn create_derived_impl(&self,
|
||||
self.span,
|
||||
cx.meta_word(self.span,
|
||||
InternedString::new("automatically_derived")));
|
||||
// Just mark it now since we know that it'll end up used downstream
|
||||
attr::mark_used(&attr);
|
||||
let opt_trait_ref = Some(trait_ref);
|
||||
let ident = ast_util::impl_pretty_name(&opt_trait_ref, self_type);
|
||||
cx.item(
|
||||
|
@ -265,6 +265,8 @@ pub fn expand_item(it: @ast::Item, fld: &mut MacroExpander)
|
||||
|
||||
match fld.extsbox.find(&intern(mname.get())) {
|
||||
Some(&ItemDecorator(dec_fn)) => {
|
||||
attr::mark_used(attr);
|
||||
|
||||
fld.cx.bt_push(ExpnInfo {
|
||||
call_site: attr.span,
|
||||
callee: NameAndSpan {
|
||||
@ -336,6 +338,7 @@ fn expand_item_modifiers(mut it: @ast::Item, fld: &mut MacroExpander)
|
||||
|
||||
match fld.extsbox.find(&intern(mname.get())) {
|
||||
Some(&ItemModifier(dec_fn)) => {
|
||||
attr::mark_used(attr);
|
||||
fld.cx.bt_push(ExpnInfo {
|
||||
call_site: attr.span,
|
||||
callee: NameAndSpan {
|
||||
@ -474,7 +477,7 @@ pub fn expand_view_item(vi: &ast::ViewItem,
|
||||
match vi.node {
|
||||
ast::ViewItemExternCrate(..) => {
|
||||
let should_load = vi.attrs.iter().any(|attr| {
|
||||
attr.name().get() == "phase" &&
|
||||
attr.check_name("phase") &&
|
||||
attr.meta_item_list().map_or(false, |phases| {
|
||||
attr::contains_name(phases, "syntax")
|
||||
})
|
||||
@ -972,6 +975,7 @@ mod test {
|
||||
use super::*;
|
||||
use ast;
|
||||
use ast::{Attribute_, AttrOuter, MetaWord};
|
||||
use attr;
|
||||
use codemap;
|
||||
use codemap::Spanned;
|
||||
use ext::base::{CrateLoader, MacroCrate};
|
||||
@ -1103,6 +1107,7 @@ fn make_dummy_attr(s: &str) -> ast::Attribute {
|
||||
Spanned {
|
||||
span:codemap::DUMMY_SP,
|
||||
node: Attribute_ {
|
||||
id: attr::mk_attr_id(),
|
||||
style: AttrOuter,
|
||||
value: @Spanned {
|
||||
node: MetaWord(token::intern_and_get_ident(s)),
|
||||
|
@ -360,6 +360,7 @@ fn fold_attribute_<T: Folder>(at: Attribute, fld: &mut T) -> Attribute {
|
||||
Spanned {
|
||||
span: fld.new_span(at.span),
|
||||
node: ast::Attribute_ {
|
||||
id: at.node.id,
|
||||
style: at.node.style,
|
||||
value: fold_meta_item_(at.node.value, fld),
|
||||
is_sugared_doc: at.node.is_sugared_doc
|
||||
|
@ -8,6 +8,7 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use attr;
|
||||
use ast;
|
||||
use codemap::{spanned, Spanned, mk_sp, Span};
|
||||
use parse::common::*; //resolve bug?
|
||||
@ -39,6 +40,7 @@ fn parse_outer_attributes(&mut self) -> Vec<ast::Attribute> {
|
||||
}
|
||||
token::DOC_COMMENT(s) => {
|
||||
let attr = ::attr::mk_sugared_doc_attr(
|
||||
attr::mk_attr_id(),
|
||||
self.id_to_interned_str(s),
|
||||
self.span.lo,
|
||||
self.span.hi
|
||||
@ -101,6 +103,7 @@ fn parse_attribute(&mut self, permit_inner: bool) -> ast::Attribute {
|
||||
return Spanned {
|
||||
span: span,
|
||||
node: ast::Attribute_ {
|
||||
id: attr::mk_attr_id(),
|
||||
style: style,
|
||||
value: value,
|
||||
is_sugared_doc: false
|
||||
@ -132,7 +135,10 @@ fn parse_inner_attrs_and_next(&mut self)
|
||||
// we need to get the position of this token before we bump.
|
||||
let Span { lo, hi, .. } = self.span;
|
||||
self.bump();
|
||||
::attr::mk_sugared_doc_attr(self.id_to_interned_str(s), lo, hi)
|
||||
attr::mk_sugared_doc_attr(attr::mk_attr_id(),
|
||||
self.id_to_interned_str(s),
|
||||
lo,
|
||||
hi)
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
|
@ -12,9 +12,12 @@
|
||||
// injected intrinsics by the compiler.
|
||||
|
||||
#![deny(attribute_usage)]
|
||||
#![deny(unused_attribute)]
|
||||
|
||||
mod a {
|
||||
#![crate_type = "bin"] //~ ERROR: crate-level attribute
|
||||
//~^ ERROR: unused attribute
|
||||
}
|
||||
|
||||
#[crate_type = "bin"] fn main() {} //~ ERROR: crate-level attribute
|
||||
//~^ ERROR: unused attribute
|
||||
|
@ -12,10 +12,13 @@
|
||||
// injected intrinsics by the compiler.
|
||||
|
||||
#![deny(attribute_usage)]
|
||||
#![deny(unused_attribute)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[abi="stdcall"] extern {} //~ ERROR: obsolete attribute
|
||||
//~^ ERROR: unused attribute
|
||||
|
||||
#[fixed_stack_segment] fn f() {} //~ ERROR: obsolete attribute
|
||||
//~^ ERROR: unused attribute
|
||||
|
||||
fn main() {}
|
||||
|
@ -12,9 +12,13 @@
|
||||
// injected intrinsics by the compiler.
|
||||
|
||||
#![deny(attribute_usage)]
|
||||
#![deny(unused_attribute)]
|
||||
|
||||
#![mutable_doc] //~ ERROR: unknown crate attribute
|
||||
//~^ ERROR: unused attribute
|
||||
|
||||
#[dance] mod a {} //~ ERROR: unknown attribute
|
||||
//~^ ERROR: unused attribute
|
||||
|
||||
#[dance] fn main() {} //~ ERROR: unknown attribute
|
||||
//~^ ERROR: unused attribute
|
||||
|
@ -7,6 +7,7 @@
|
||||
// <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.
|
||||
#![allow(unused_attribute)]
|
||||
|
||||
#[foo(bar)]
|
||||
mod foo {
|
||||
|
@ -9,8 +9,6 @@
|
||||
// except according to those terms.
|
||||
|
||||
// ignore-win32 FIXME #13259
|
||||
#![no_uv]
|
||||
|
||||
extern crate native;
|
||||
|
||||
use std::os;
|
||||
|
@ -9,6 +9,7 @@
|
||||
// except according to those terms.
|
||||
|
||||
// pp-exact - Make sure we actually print the attributes
|
||||
#![allow(unused_attribute)]
|
||||
|
||||
struct cat {
|
||||
name: StrBuf,
|
||||
|
@ -7,6 +7,7 @@
|
||||
// <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.
|
||||
#![allow(unused_attribute)]
|
||||
|
||||
struct cat {
|
||||
name: StrBuf,
|
||||
|
@ -1,15 +0,0 @@
|
||||
// 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 <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.
|
||||
|
||||
#[auto_serialize]
|
||||
|
||||
type t = (uint, uint);
|
||||
|
||||
pub fn main() { }
|
@ -11,6 +11,7 @@
|
||||
// These are attributes of the implicit crate. Really this just needs to parse
|
||||
// for completeness since .rs files linked from .rc files support this
|
||||
// notation to specify their module's attributes
|
||||
#![allow(unused_attribute)]
|
||||
#![attr1 = "val"]
|
||||
#![attr2 = "val"]
|
||||
#![attr3]
|
||||
|
@ -9,6 +9,7 @@
|
||||
// except according to those terms.
|
||||
|
||||
// pp-exact - Make sure we print all the attributes
|
||||
#![allow(unused_attribute)]
|
||||
|
||||
#[frobable]
|
||||
trait frobable {
|
||||
|
Loading…
Reference in New Issue
Block a user