Implement support for indicating the stability of items.

There are 6 new compiler recognised attributes: deprecated, experimental,
unstable, stable, frozen, locked (these levels are taken directly from
Node's "stability index"[1]). These indicate the stability of the
item to which they are attached; e.g. `#[deprecated] fn foo() { .. }`
says that `foo` is deprecated.

This comes with 3 lints for the first 3 levels (with matching names) that
will detect the use of items marked with them (the `unstable` lint
includes items with no stability attribute). The attributes can be given
a short text note that will be displayed by the lint. An example:

    #[warn(unstable)]; // `allow` by default

    #[deprecated="use `bar`"]
    fn foo() { }

    #[stable]
    fn bar() { }

    fn baz() { }

    fn main() {
        foo(); // "warning: use of deprecated item: use `bar`"

        bar(); // all fine

        baz(); // "warning: use of unmarked item"
    }

The lints currently only check the "edges" of the AST: i.e. functions,
methods[2], structs and enum variants. Any stability attributes on modules,
enums, traits and impls are not checked.

[1]: http://nodejs.org/api/documentation.html
[2]: the method check is currently incorrect and doesn't work.
This commit is contained in:
Huon Wilson 2013-08-31 17:13:57 +10:00
parent 7048e05d5f
commit 506f69aed7
8 changed files with 745 additions and 3 deletions

View file

@ -330,6 +330,7 @@ fn encode_enum_variant_info(ecx: &EncodeContext,
encode_name(ecx, ebml_w, variant.node.name);
encode_parent_item(ebml_w, local_def(id));
encode_visibility(ebml_w, variant.node.vis);
encode_attributes(ebml_w, variant.node.attrs);
match variant.node.kind {
ast::tuple_variant_kind(ref args)
if args.len() > 0 && generics.ty_params.len() == 0 => {

View file

@ -12,6 +12,7 @@
use driver::session;
use middle::ty;
use middle::pat_util;
use metadata::csearch;
use util::ppaux::{ty_to_str};
use std::cmp;
@ -27,7 +28,7 @@ use std::u8;
use extra::smallintmap::SmallIntMap;
use syntax::ast_map;
use syntax::attr;
use syntax::attr::AttrMetaMethods;
use syntax::attr::{AttrMetaMethods, AttributeMethods};
use syntax::codemap::Span;
use syntax::codemap;
use syntax::parse::token;
@ -97,6 +98,10 @@ pub enum lint {
missing_doc,
unreachable_code,
deprecated,
experimental,
unstable,
warnings,
}
@ -281,6 +286,27 @@ static lint_table: &'static [(&'static str, LintSpec)] = &[
default: warn
}),
("deprecated",
LintSpec {
lint: deprecated,
desc: "detects use of #[deprecated] items",
default: warn
}),
("experimental",
LintSpec {
lint: experimental,
desc: "detects use of #[experimental] items",
default: warn
}),
("unstable",
LintSpec {
lint: unstable,
desc: "detects use of #[unstable] items (incl. items with no stability attribute)",
default: allow
}),
("warnings",
LintSpec {
lint: warnings,
@ -1375,6 +1401,107 @@ fn lint_missing_doc() -> @mut OuterLint {
@mut MissingDocLintVisitor { stopping_on_items: false } as @mut OuterLint
}
/// Checks for use of items with #[deprecated], #[experimental] and
/// #[unstable] (or none of them) attributes.
struct StabilityLintVisitor { stopping_on_items: bool }
impl StabilityLintVisitor {
fn handle_def(&mut self, sp: Span, def: &ast::Def, cx: @mut Context) {
let id = ast_util::def_id_of_def(*def);
let stability = if ast_util::is_local(id) {
// this crate
match cx.tcx.items.find(&id.node) {
Some(ast_node) => {
let s = do ast_node.with_attrs |attrs| {
do attrs.map_move |a| {
attr::find_stability(a.iter().map(|a| a.meta()))
}
};
match s {
Some(s) => s,
// no possibility of having attributes
// (e.g. it's a local variable), so just
// ignore it.
None => return
}
}
_ => cx.tcx.sess.bug(fmt!("handle_def: %? not found", id))
}
} else {
// cross-crate
let mut s = None;
// run through all the attributes and take the first
// stability one.
do csearch::get_item_attrs(cx.tcx.cstore, id) |meta_items| {
if s.is_none() {
s = attr::find_stability(meta_items.move_iter())
}
}
s
};
let (lint, label) = match stability {
// no stability attributes == Unstable
None => (unstable, "unmarked"),
Some(attr::Stability { level: attr::Unstable, _ }) => (unstable, "unstable"),
Some(attr::Stability { level: attr::Experimental, _ }) => {
(experimental, "experimental")
}
Some(attr::Stability { level: attr::Deprecated, _ }) => (deprecated, "deprecated"),
_ => return
};
let msg = match stability {
Some(attr::Stability { text: Some(ref s), _ }) => {
fmt!("use of %s item: %s", label, *s)
}
_ => fmt!("use of %s item", label)
};
cx.span_lint(lint, sp, msg);
}
}
impl SubitemStoppableVisitor for StabilityLintVisitor {
fn is_running_on_items(&mut self) -> bool { !self.stopping_on_items }
}
impl Visitor<@mut Context> for StabilityLintVisitor {
fn visit_item(&mut self, i:@ast::item, e:@mut Context) {
self.OVERRIDE_visit_item(i, e);
}
fn visit_fn(&mut self, fk:&visit::fn_kind, fd:&ast::fn_decl,
b:&ast::Block, s:Span, n:ast::NodeId, e:@mut Context) {
self.OVERRIDE_visit_fn(fk, fd, b, s, n, e);
}
fn visit_expr(&mut self, ex: @ast::Expr, cx: @mut Context) {
match ex.node {
ast::ExprMethodCall(*) |
ast::ExprPath(*) |
ast::ExprStruct(*) => {
match cx.tcx.def_map.find(&ex.id) {
Some(def) => self.handle_def(ex.span, def, cx),
None => {}
}
}
_ => {}
}
visit::walk_expr(self, ex, cx)
}
}
outer_lint_boilerplate_impl!(StabilityLintVisitor)
fn lint_stability() -> @mut OuterLint {
@mut StabilityLintVisitor { stopping_on_items: false } as @mut OuterLint
}
struct LintCheckVisitor;
impl Visitor<@mut Context> for LintCheckVisitor {
@ -1458,6 +1585,7 @@ pub fn check_crate(tcx: ty::ctxt, crate: @ast::Crate) {
cx.add_old_lint(lint_unused_mut());
cx.add_old_lint(lint_unnecessary_allocations());
cx.add_old_lint(lint_missing_doc());
cx.add_old_lint(lint_stability());
cx.add_lint(lint_session(cx));
// Actually perform the lint checks (iterating the ast)

View file

@ -4706,4 +4706,3 @@ pub fn trait_of_method(tcx: ctxt, def_id: ast::DefId)
result
}

View file

@ -80,6 +80,26 @@ pub enum ast_node {
node_callee_scope(@Expr)
}
impl ast_node {
pub fn with_attrs<T>(&self, f: &fn(Option<&[Attribute]>) -> T) -> T {
let attrs = match *self {
node_item(i, _) => Some(i.attrs.as_slice()),
node_foreign_item(fi, _, _, _) => Some(fi.attrs.as_slice()),
node_trait_method(tm, _, _) => match *tm {
required(ref type_m) => Some(type_m.attrs.as_slice()),
provided(m) => Some(m.attrs.as_slice())
},
node_method(m, _, _) => Some(m.attrs.as_slice()),
node_variant(ref v, _, _) => Some(v.node.attrs.as_slice()),
// unit/tuple structs take the attributes straight from
// the struct definition.
node_struct_ctor(_, strct, _) => Some(strct.attrs.as_slice()),
_ => None
};
f(attrs)
}
}
pub type map = @mut HashMap<NodeId, ast_node>;
pub struct Ctx {

View file

@ -313,6 +313,44 @@ pub fn test_cfg<AM: AttrMetaMethods, It: Iterator<AM>>
no_cfgs || some_cfg_matches
}
/// Represents the #[deprecated="foo"] (etc) attributes.
pub struct Stability {
level: StabilityLevel,
text: Option<@str>
}
/// The available stability levels.
#[deriving(Eq,Ord,Clone)]
pub enum StabilityLevel {
Deprecated,
Experimental,
Unstable,
Stable,
Frozen,
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().as_slice() {
"deprecated" => Deprecated,
"experimental" => Experimental,
"unstable" => Unstable,
"stable" => Stable,
"frozen" => Frozen,
"locked" => Locked,
_ => loop // not a stability level
};
return Some(Stability {
level: level,
text: m.value_str()
});
}
None
}
pub fn require_unique_names(diagnostic: @mut span_handler,
metas: &[@MetaItem]) {
let mut set = HashSet::new();

View file

@ -0,0 +1,162 @@
// Copyright 2013 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.
#[link(name = "lint_stability",
vers = "0.1")];
#[crate_type = "lib"];
#[deprecated]
pub fn deprecated() {}
#[deprecated="text"]
pub fn deprecated_text() {}
#[experimental]
pub fn experimental() {}
#[experimental="text"]
pub fn experimental_text() {}
#[unstable]
pub fn unstable() {}
#[unstable="text"]
pub fn unstable_text() {}
pub fn unmarked() {}
#[stable]
pub fn stable() {}
#[stable="text"]
pub fn stable_text() {}
#[locked]
pub fn locked() {}
#[locked="text"]
pub fn locked_text() {}
#[frozen]
pub fn frozen() {}
#[frozen="text"]
pub fn frozen_text() {}
#[stable]
pub struct MethodTester;
impl MethodTester {
#[deprecated]
pub fn method_deprecated(&self) {}
#[deprecated="text"]
pub fn method_deprecated_text(&self) {}
#[experimental]
pub fn method_experimental(&self) {}
#[experimental="text"]
pub fn method_experimental_text(&self) {}
#[unstable]
pub fn method_unstable(&self) {}
#[unstable="text"]
pub fn method_unstable_text(&self) {}
pub fn method_unmarked(&self) {}
#[stable]
pub fn method_stable(&self) {}
#[stable="text"]
pub fn method_stable_text(&self) {}
#[locked]
pub fn method_locked(&self) {}
#[locked="text"]
pub fn method_locked_text(&self) {}
#[frozen]
pub fn method_frozen(&self) {}
#[frozen="text"]
pub fn method_frozen_text(&self) {}
}
pub trait Trait {
#[deprecated]
fn trait_deprecated(&self) {}
#[deprecated="text"]
fn trait_deprecated_text(&self) {}
#[experimental]
fn trait_experimental(&self) {}
#[experimental="text"]
fn trait_experimental_text(&self) {}
#[unstable]
fn trait_unstable(&self) {}
#[unstable="text"]
fn trait_unstable_text(&self) {}
fn trait_unmarked(&self) {}
#[stable]
fn trait_stable(&self) {}
#[stable="text"]
fn trait_stable_text(&self) {}
#[locked]
fn trait_locked(&self) {}
#[locked="text"]
fn trait_locked_text(&self) {}
#[frozen]
fn trait_frozen(&self) {}
#[frozen="text"]
fn trait_frozen_text(&self) {}
}
impl Trait for MethodTester {}
#[deprecated]
pub struct DeprecatedStruct { i: int }
#[experimental]
pub struct ExperimentalStruct { i: int }
#[unstable]
pub struct UnstableStruct { i: int }
pub struct UnmarkedStruct { i: int }
#[stable]
pub struct StableStruct { i: int }
#[frozen]
pub struct FrozenStruct { i: int }
#[locked]
pub struct LockedStruct { i: int }
#[deprecated]
pub struct DeprecatedUnitStruct;
#[experimental]
pub struct ExperimentalUnitStruct;
#[unstable]
pub struct UnstableUnitStruct;
pub struct UnmarkedUnitStruct;
#[stable]
pub struct StableUnitStruct;
#[frozen]
pub struct FrozenUnitStruct;
#[locked]
pub struct LockedUnitStruct;
pub enum Enum {
#[deprecated]
DeprecatedVariant,
#[experimental]
ExperimentalVariant,
#[unstable]
UnstableVariant,
UnmarkedVariant,
#[stable]
StableVariant,
#[frozen]
FrozenVariant,
#[locked]
LockedVariant,
}

View file

@ -0,0 +1,338 @@
// Copyright 2013 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.
// xfail-fast aux-build
// aux-build:lint_stability.rs
#[deny(unstable)];
#[deny(deprecated)];
#[deny(experimental)];
mod cross_crate {
extern mod lint_stability;
use self::lint_stability::*;
fn test() {
// XXX: attributes on methods are not encoded cross crate.
let foo = MethodTester;
deprecated(); //~ ERROR use of deprecated item
foo.method_deprecated(); // ~ ERROR use of deprecated item
foo.trait_deprecated(); // ~ ERROR use of deprecated item
deprecated_text(); //~ ERROR use of deprecated item: text
foo.method_deprecated_text(); // ~ ERROR use of deprecated item: text
foo.trait_deprecated_text(); // ~ ERROR use of deprecated item: text
experimental(); //~ ERROR use of experimental item
foo.method_experimental(); // ~ ERROR use of experimental item
foo.trait_experimental(); // ~ ERROR use of experimental item
experimental_text(); //~ ERROR use of experimental item: text
foo.method_experimental_text(); // ~ ERROR use of experimental item: text
foo.trait_experimental_text(); // ~ ERROR use of experimental item: text
unstable(); //~ ERROR use of unstable item
foo.method_unstable(); // ~ ERROR use of unstable item
foo.trait_unstable(); // ~ ERROR use of unstable item
unstable_text(); //~ ERROR use of unstable item: text
foo.method_unstable_text(); // ~ ERROR use of unstable item: text
foo.trait_unstable_text(); // ~ ERROR use of unstable item: text
unmarked(); //~ ERROR use of unmarked item
foo.method_unmarked(); // ~ ERROR use of unmarked item
foo.trait_unmarked(); // ~ ERROR use of unmarked item
stable();
foo.method_stable();
foo.trait_stable();
stable_text();
foo.method_stable_text();
foo.trait_stable_text();
frozen();
foo.method_frozen();
foo.trait_frozen();
frozen_text();
foo.method_frozen_text();
foo.trait_frozen_text();
locked();
foo.method_locked();
foo.trait_locked();
locked_text();
foo.method_locked_text();
foo.trait_locked_text();
let _ = DeprecatedStruct { i: 0 }; //~ ERROR use of deprecated item
let _ = ExperimentalStruct { i: 0 }; //~ ERROR use of experimental item
let _ = UnstableStruct { i: 0 }; //~ ERROR use of unstable item
let _ = UnmarkedStruct { i: 0 }; //~ ERROR use of unmarked item
let _ = StableStruct { i: 0 };
let _ = FrozenStruct { i: 0 };
let _ = LockedStruct { i: 0 };
let _ = DeprecatedUnitStruct; //~ ERROR use of deprecated item
let _ = ExperimentalUnitStruct; //~ ERROR use of experimental item
let _ = UnstableUnitStruct; //~ ERROR use of unstable item
let _ = UnmarkedUnitStruct; //~ ERROR use of unmarked item
let _ = StableUnitStruct;
let _ = FrozenUnitStruct;
let _ = LockedUnitStruct;
let _ = DeprecatedVariant; //~ ERROR use of deprecated item
let _ = ExperimentalVariant; //~ ERROR use of experimental item
let _ = UnstableVariant; //~ ERROR use of unstable item
let _ = UnmarkedVariant; //~ ERROR use of unmarked item
let _ = StableVariant;
let _ = FrozenVariant;
let _ = LockedVariant;
}
}
mod this_crate {
#[deprecated]
pub fn deprecated() {}
#[deprecated="text"]
pub fn deprecated_text() {}
#[experimental]
pub fn experimental() {}
#[experimental="text"]
pub fn experimental_text() {}
#[unstable]
pub fn unstable() {}
#[unstable="text"]
pub fn unstable_text() {}
pub fn unmarked() {}
#[stable]
pub fn stable() {}
#[stable="text"]
pub fn stable_text() {}
#[locked]
pub fn locked() {}
#[locked="text"]
pub fn locked_text() {}
#[frozen]
pub fn frozen() {}
#[frozen="text"]
pub fn frozen_text() {}
#[stable]
pub struct MethodTester;
impl MethodTester {
#[deprecated]
pub fn method_deprecated(&self) {}
#[deprecated="text"]
pub fn method_deprecated_text(&self) {}
#[experimental]
pub fn method_experimental(&self) {}
#[experimental="text"]
pub fn method_experimental_text(&self) {}
#[unstable]
pub fn method_unstable(&self) {}
#[unstable="text"]
pub fn method_unstable_text(&self) {}
pub fn method_unmarked(&self) {}
#[stable]
pub fn method_stable(&self) {}
#[stable="text"]
pub fn method_stable_text(&self) {}
#[locked]
pub fn method_locked(&self) {}
#[locked="text"]
pub fn method_locked_text(&self) {}
#[frozen]
pub fn method_frozen(&self) {}
#[frozen="text"]
pub fn method_frozen_text(&self) {}
}
pub trait Trait {
#[deprecated]
fn trait_deprecated(&self) {}
#[deprecated="text"]
fn trait_deprecated_text(&self) {}
#[experimental]
fn trait_experimental(&self) {}
#[experimental="text"]
fn trait_experimental_text(&self) {}
#[unstable]
fn trait_unstable(&self) {}
#[unstable="text"]
fn trait_unstable_text(&self) {}
fn trait_unmarked(&self) {}
#[stable]
fn trait_stable(&self) {}
#[stable="text"]
fn trait_stable_text(&self) {}
#[locked]
fn trait_locked(&self) {}
#[locked="text"]
fn trait_locked_text(&self) {}
#[frozen]
fn trait_frozen(&self) {}
#[frozen="text"]
fn trait_frozen_text(&self) {}
}
impl Trait for MethodTester {}
#[deprecated]
pub struct DeprecatedStruct { i: int }
#[experimental]
pub struct ExperimentalStruct { i: int }
#[unstable]
pub struct UnstableStruct { i: int }
pub struct UnmarkedStruct { i: int }
#[stable]
pub struct StableStruct { i: int }
#[frozen]
pub struct FrozenStruct { i: int }
#[locked]
pub struct LockedStruct { i: int }
#[deprecated]
pub struct DeprecatedUnitStruct;
#[experimental]
pub struct ExperimentalUnitStruct;
#[unstable]
pub struct UnstableUnitStruct;
pub struct UnmarkedUnitStruct;
#[stable]
pub struct StableUnitStruct;
#[frozen]
pub struct FrozenUnitStruct;
#[locked]
pub struct LockedUnitStruct;
pub enum Enum {
#[deprecated]
DeprecatedVariant,
#[experimental]
ExperimentalVariant,
#[unstable]
UnstableVariant,
UnmarkedVariant,
#[stable]
StableVariant,
#[frozen]
FrozenVariant,
#[locked]
LockedVariant,
}
fn test() {
let foo = MethodTester;
deprecated(); //~ ERROR use of deprecated item
foo.method_deprecated(); // ~ ERROR use of deprecated item
foo.trait_deprecated(); // ~ ERROR use of deprecated item
deprecated_text(); //~ ERROR use of deprecated item: text
foo.method_deprecated_text(); // ~ ERROR use of deprecated item: text
foo.trait_deprecated_text(); // ~ ERROR use of deprecated item: text
experimental(); //~ ERROR use of experimental item
foo.method_experimental(); // ~ ERROR use of experimental item
foo.trait_experimental(); // ~ ERROR use of experimental item
experimental_text(); //~ ERROR use of experimental item: text
foo.method_experimental_text(); // ~ ERROR use of experimental item: text
foo.trait_experimental_text(); // ~ ERROR use of experimental item: text
unstable(); //~ ERROR use of unstable item
foo.method_unstable(); // ~ ERROR use of unstable item
foo.trait_unstable(); // ~ ERROR use of unstable item
unstable_text(); //~ ERROR use of unstable item: text
foo.method_unstable_text(); // ~ ERROR use of unstable item: text
foo.trait_unstable_text(); // ~ ERROR use of unstable item: text
unmarked(); //~ ERROR use of unmarked item
foo.method_unmarked(); // ~ ERROR use of unmarked item
foo.trait_unmarked(); // ~ ERROR use of unmarked item
stable();
foo.method_stable();
foo.trait_stable();
stable_text();
foo.method_stable_text();
foo.trait_stable_text();
frozen();
foo.method_frozen();
foo.trait_frozen();
frozen_text();
foo.method_frozen_text();
foo.trait_frozen_text();
locked();
foo.method_locked();
foo.trait_locked();
locked_text();
foo.method_locked_text();
foo.trait_locked_text();
let _ = DeprecatedStruct { i: 0 }; //~ ERROR use of deprecated item
let _ = ExperimentalStruct { i: 0 }; //~ ERROR use of experimental item
let _ = UnstableStruct { i: 0 }; //~ ERROR use of unstable item
let _ = UnmarkedStruct { i: 0 }; //~ ERROR use of unmarked item
let _ = StableStruct { i: 0 };
let _ = FrozenStruct { i: 0 };
let _ = LockedStruct { i: 0 };
let _ = DeprecatedUnitStruct; //~ ERROR use of deprecated item
let _ = ExperimentalUnitStruct; //~ ERROR use of experimental item
let _ = UnstableUnitStruct; //~ ERROR use of unstable item
let _ = UnmarkedUnitStruct; //~ ERROR use of unmarked item
let _ = StableUnitStruct;
let _ = FrozenUnitStruct;
let _ = LockedUnitStruct;
let _ = DeprecatedVariant; //~ ERROR use of deprecated item
let _ = ExperimentalVariant; //~ ERROR use of experimental item
let _ = UnstableVariant; //~ ERROR use of unstable item
let _ = UnmarkedVariant; //~ ERROR use of unmarked item
let _ = StableVariant;
let _ = FrozenVariant;
let _ = LockedVariant;
}
}
fn main() {}