| // 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. |
| |
| //! A pass that annotates every item and method with its stability level, |
| //! propagating default levels lexically from parent to children ast nodes. |
| |
| use session::Session; |
| use lint; |
| use middle::def; |
| use middle::ty; |
| use middle::privacy::PublicItems; |
| use metadata::csearch; |
| use syntax::parse::token::InternedString; |
| use syntax::codemap::{Span, DUMMY_SP}; |
| use syntax::{attr, visit}; |
| use syntax::ast; |
| use syntax::ast::{Attribute, Block, Crate, DefId, FnDecl, NodeId, Variant}; |
| use syntax::ast::{Item, Generics, StructField}; |
| use syntax::ast_util::is_local; |
| use syntax::attr::{Stability, AttrMetaMethods}; |
| use syntax::visit::{FnKind, Visitor}; |
| use syntax::feature_gate::emit_feature_err; |
| use util::nodemap::{NodeMap, DefIdMap, FnvHashSet, FnvHashMap}; |
| use util::ppaux::Repr; |
| |
| use std::mem::replace; |
| |
| /// A stability index, giving the stability level for items and methods. |
| pub struct Index { |
| // Indicates whether this crate has #![feature(staged_api)] |
| staged_api: bool, |
| // stability for crate-local items; unmarked stability == no entry |
| local: NodeMap<Stability>, |
| // cache for extern-crate items; unmarked stability == entry with None |
| extern_cache: DefIdMap<Option<Stability>> |
| } |
| |
| // A private tree-walker for producing an Index. |
| struct Annotator<'a> { |
| sess: &'a Session, |
| index: &'a mut Index, |
| parent: Option<Stability>, |
| export_map: &'a PublicItems, |
| } |
| |
| impl<'a> Annotator<'a> { |
| // Determine the stability for a node based on its attributes and inherited |
| // stability. The stability is recorded in the index and used as the parent. |
| fn annotate<F>(&mut self, id: NodeId, use_parent: bool, |
| attrs: &Vec<Attribute>, item_sp: Span, f: F, required: bool) where |
| F: FnOnce(&mut Annotator), |
| { |
| debug!("annotate(id = {:?}, attrs = {:?})", id, attrs); |
| match attr::find_stability(self.sess.diagnostic(), attrs, item_sp) { |
| Some(stab) => { |
| debug!("annotate: found {:?}", stab); |
| self.index.local.insert(id, stab.clone()); |
| |
| // Don't inherit #[stable(feature = "rust1", since = "1.0.0")] |
| if stab.level != attr::Stable { |
| let parent = replace(&mut self.parent, Some(stab)); |
| f(self); |
| self.parent = parent; |
| } else { |
| f(self); |
| } |
| } |
| None => { |
| debug!("annotate: not found, use_parent = {:?}, parent = {:?}", |
| use_parent, self.parent); |
| if use_parent { |
| if let Some(stab) = self.parent.clone() { |
| self.index.local.insert(id, stab); |
| } else if self.index.staged_api && required |
| && self.export_map.contains(&id) |
| && !self.sess.opts.test { |
| self.sess.span_err(item_sp, |
| "This node does not have a stability attribute"); |
| } |
| } |
| f(self); |
| } |
| } |
| } |
| } |
| |
| impl<'a, 'v> Visitor<'v> for Annotator<'a> { |
| fn visit_item(&mut self, i: &Item) { |
| // FIXME (#18969): the following is a hack around the fact |
| // that we cannot currently annotate the stability of |
| // `deriving`. Basically, we do *not* allow stability |
| // inheritance on trait implementations, so that derived |
| // implementations appear to be unannotated. This then allows |
| // derived implementations to be automatically tagged with the |
| // stability of the trait. This is WRONG, but expedient to get |
| // libstd stabilized for the 1.0 release. |
| let use_parent = match i.node { |
| ast::ItemImpl(_, _, _, Some(_), _, _) => false, |
| _ => true, |
| }; |
| |
| // In case of a `pub use <mod>;`, we should not error since the stability |
| // is inherited from the module itself |
| let required = match i.node { |
| ast::ItemUse(_) => i.vis != ast::Public, |
| _ => true |
| }; |
| |
| self.annotate(i.id, use_parent, &i.attrs, i.span, |
| |v| visit::walk_item(v, i), required); |
| |
| if let ast::ItemStruct(ref sd, _) = i.node { |
| sd.ctor_id.map(|id| { |
| self.annotate(id, true, &i.attrs, i.span, |_| {}, true) |
| }); |
| } |
| } |
| |
| fn visit_fn(&mut self, _: FnKind<'v>, _: &'v FnDecl, |
| _: &'v Block, _: Span, _: NodeId) { |
| // Items defined in a function body have no reason to have |
| // a stability attribute, so we don't recurse. |
| } |
| |
| fn visit_trait_item(&mut self, ti: &ast::TraitItem) { |
| self.annotate(ti.id, true, &ti.attrs, ti.span, |
| |v| visit::walk_trait_item(v, ti), true); |
| } |
| |
| fn visit_impl_item(&mut self, ii: &ast::ImplItem) { |
| self.annotate(ii.id, true, &ii.attrs, ii.span, |
| |v| visit::walk_impl_item(v, ii), true); |
| } |
| |
| fn visit_variant(&mut self, var: &Variant, g: &'v Generics) { |
| self.annotate(var.node.id, true, &var.node.attrs, var.span, |
| |v| visit::walk_variant(v, var, g), true) |
| } |
| |
| fn visit_struct_field(&mut self, s: &StructField) { |
| self.annotate(s.node.id, true, &s.node.attrs, s.span, |
| |v| visit::walk_struct_field(v, s), true); |
| } |
| |
| fn visit_foreign_item(&mut self, i: &ast::ForeignItem) { |
| self.annotate(i.id, true, &i.attrs, i.span, |_| {}, true); |
| } |
| } |
| |
| impl Index { |
| /// Construct the stability index for a crate being compiled. |
| pub fn build(&mut self, sess: &Session, krate: &Crate, export_map: &PublicItems) { |
| if !self.staged_api { |
| return; |
| } |
| let mut annotator = Annotator { |
| sess: sess, |
| index: self, |
| parent: None, |
| export_map: export_map, |
| }; |
| annotator.annotate(ast::CRATE_NODE_ID, true, &krate.attrs, krate.span, |
| |v| visit::walk_crate(v, krate), true); |
| } |
| |
| pub fn new(krate: &Crate) -> Index { |
| let mut staged_api = false; |
| for attr in &krate.attrs { |
| if &attr.name()[..] == "staged_api" { |
| match attr.node.value.node { |
| ast::MetaWord(_) => { |
| attr::mark_used(attr); |
| staged_api = true; |
| } |
| _ => (/*pass*/) |
| } |
| } |
| } |
| Index { |
| staged_api: staged_api, |
| local: NodeMap(), |
| extern_cache: DefIdMap() |
| } |
| } |
| } |
| |
| /// Cross-references the feature names of unstable APIs with enabled |
| /// features and possibly prints errors. Returns a list of all |
| /// features used. |
| pub fn check_unstable_api_usage(tcx: &ty::ctxt) |
| -> FnvHashMap<InternedString, attr::StabilityLevel> { |
| let ref active_lib_features = tcx.sess.features.borrow().declared_lib_features; |
| |
| // Put the active features into a map for quick lookup |
| let active_features = active_lib_features.iter().map(|&(ref s, _)| s.clone()).collect(); |
| |
| let mut checker = Checker { |
| tcx: tcx, |
| active_features: active_features, |
| used_features: FnvHashMap() |
| }; |
| |
| let krate = tcx.map.krate(); |
| visit::walk_crate(&mut checker, krate); |
| |
| let used_features = checker.used_features; |
| return used_features; |
| } |
| |
| struct Checker<'a, 'tcx: 'a> { |
| tcx: &'a ty::ctxt<'tcx>, |
| active_features: FnvHashSet<InternedString>, |
| used_features: FnvHashMap<InternedString, attr::StabilityLevel> |
| } |
| |
| impl<'a, 'tcx> Checker<'a, 'tcx> { |
| fn check(&mut self, id: ast::DefId, span: Span, stab: &Option<Stability>) { |
| // Only the cross-crate scenario matters when checking unstable APIs |
| let cross_crate = !is_local(id); |
| if !cross_crate { return } |
| |
| match *stab { |
| Some(Stability { level: attr::Unstable, ref feature, ref reason, .. }) => { |
| self.used_features.insert(feature.clone(), attr::Unstable); |
| |
| if !self.active_features.contains(feature) { |
| let msg = match *reason { |
| Some(ref r) => format!("use of unstable library feature '{}': {}", |
| &feature, &r), |
| None => format!("use of unstable library feature '{}'", &feature) |
| }; |
| |
| emit_feature_err(&self.tcx.sess.parse_sess.span_diagnostic, |
| &feature, span, &msg); |
| } |
| } |
| Some(Stability { level, ref feature, .. }) => { |
| self.used_features.insert(feature.clone(), level); |
| |
| // Stable APIs are always ok to call and deprecated APIs are |
| // handled by a lint. |
| } |
| None => { |
| // This is an 'unmarked' API, which should not exist |
| // in the standard library. |
| if self.tcx.sess.features.borrow().unmarked_api { |
| self.tcx.sess.span_warn(span, "use of unmarked library feature"); |
| self.tcx.sess.span_note(span, "this is either a bug in the library you are \ |
| using or a bug in the compiler - please \ |
| report it in both places"); |
| } else { |
| self.tcx.sess.span_err(span, "use of unmarked library feature"); |
| self.tcx.sess.span_note(span, "this is either a bug in the library you are \ |
| using or a bug in the compiler - please \ |
| report it in both places"); |
| self.tcx.sess.span_note(span, "use #![feature(unmarked_api)] in the \ |
| crate attributes to override this"); |
| } |
| } |
| } |
| } |
| } |
| |
| impl<'a, 'v, 'tcx> Visitor<'v> for Checker<'a, 'tcx> { |
| fn visit_item(&mut self, item: &ast::Item) { |
| // When compiling with --test we don't enforce stability on the |
| // compiler-generated test module, demarcated with `DUMMY_SP` plus the |
| // name `__test` |
| if item.span == DUMMY_SP && item.ident.as_str() == "__test" { return } |
| |
| check_item(self.tcx, item, true, |
| &mut |id, sp, stab| self.check(id, sp, stab)); |
| visit::walk_item(self, item); |
| } |
| |
| fn visit_expr(&mut self, ex: &ast::Expr) { |
| check_expr(self.tcx, ex, |
| &mut |id, sp, stab| self.check(id, sp, stab)); |
| visit::walk_expr(self, ex); |
| } |
| |
| fn visit_path(&mut self, path: &ast::Path, id: ast::NodeId) { |
| check_path(self.tcx, path, id, |
| &mut |id, sp, stab| self.check(id, sp, stab)); |
| visit::walk_path(self, path) |
| } |
| |
| fn visit_pat(&mut self, pat: &ast::Pat) { |
| check_pat(self.tcx, pat, |
| &mut |id, sp, stab| self.check(id, sp, stab)); |
| visit::walk_pat(self, pat) |
| } |
| } |
| |
| /// Helper for discovering nodes to check for stability |
| pub fn check_item(tcx: &ty::ctxt, item: &ast::Item, warn_about_defns: bool, |
| cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) { |
| match item.node { |
| ast::ItemExternCrate(_) => { |
| // compiler-generated `extern crate` items have a dummy span. |
| if item.span == DUMMY_SP { return } |
| |
| let cnum = match tcx.sess.cstore.find_extern_mod_stmt_cnum(item.id) { |
| Some(cnum) => cnum, |
| None => return, |
| }; |
| let id = ast::DefId { krate: cnum, node: ast::CRATE_NODE_ID }; |
| maybe_do_stability_check(tcx, id, item.span, cb); |
| } |
| |
| // For implementations of traits, check the stability of each item |
| // individually as it's possible to have a stable trait with unstable |
| // items. |
| ast::ItemImpl(_, _, _, Some(ref t), _, ref impl_items) => { |
| let trait_did = tcx.def_map.borrow()[t.ref_id].def_id(); |
| let trait_items = ty::trait_items(tcx, trait_did); |
| |
| for impl_item in impl_items { |
| let item = trait_items.iter().find(|item| { |
| item.name() == impl_item.ident.name |
| }).unwrap(); |
| if warn_about_defns { |
| maybe_do_stability_check(tcx, item.def_id(), impl_item.span, cb); |
| } |
| } |
| } |
| |
| _ => (/* pass */) |
| } |
| } |
| |
| /// Helper for discovering nodes to check for stability |
| pub fn check_expr(tcx: &ty::ctxt, e: &ast::Expr, |
| cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) { |
| let span; |
| let id = match e.node { |
| ast::ExprMethodCall(i, _, _) => { |
| span = i.span; |
| let method_call = ty::MethodCall::expr(e.id); |
| match tcx.method_map.borrow().get(&method_call) { |
| Some(method) => { |
| match method.origin { |
| ty::MethodStatic(def_id) => { |
| def_id |
| } |
| ty::MethodStaticClosure(def_id) => { |
| def_id |
| } |
| ty::MethodTypeParam(ty::MethodParam { |
| ref trait_ref, |
| method_num: index, |
| .. |
| }) | |
| ty::MethodTraitObject(ty::MethodObject { |
| ref trait_ref, |
| method_num: index, |
| .. |
| }) => { |
| ty::trait_item(tcx, trait_ref.def_id, index).def_id() |
| } |
| } |
| } |
| None => return |
| } |
| } |
| ast::ExprField(ref base_e, ref field) => { |
| span = field.span; |
| match ty::expr_ty_adjusted(tcx, base_e).sty { |
| ty::ty_struct(did, _) => { |
| ty::lookup_struct_fields(tcx, did) |
| .iter() |
| .find(|f| f.name == field.node.name) |
| .unwrap_or_else(|| { |
| tcx.sess.span_bug(field.span, |
| "stability::check_expr: unknown named field access") |
| }) |
| .id |
| } |
| _ => tcx.sess.span_bug(e.span, |
| "stability::check_expr: named field access on non-struct") |
| } |
| } |
| ast::ExprTupField(ref base_e, ref field) => { |
| span = field.span; |
| match ty::expr_ty_adjusted(tcx, base_e).sty { |
| ty::ty_struct(did, _) => { |
| ty::lookup_struct_fields(tcx, did) |
| .get(field.node) |
| .unwrap_or_else(|| { |
| tcx.sess.span_bug(field.span, |
| "stability::check_expr: unknown unnamed field access") |
| }) |
| .id |
| } |
| ty::ty_tup(..) => return, |
| _ => tcx.sess.span_bug(e.span, |
| "stability::check_expr: unnamed field access on \ |
| something other than a tuple or struct") |
| } |
| } |
| ast::ExprStruct(_, ref expr_fields, _) => { |
| let type_ = ty::expr_ty(tcx, e); |
| match type_.sty { |
| ty::ty_struct(did, _) => { |
| let struct_fields = ty::lookup_struct_fields(tcx, did); |
| // check the stability of each field that appears |
| // in the construction expression. |
| for field in expr_fields { |
| let did = struct_fields |
| .iter() |
| .find(|f| f.name == field.ident.node.name) |
| .unwrap_or_else(|| { |
| tcx.sess.span_bug(field.span, |
| "stability::check_expr: unknown named \ |
| field access") |
| }) |
| .id; |
| maybe_do_stability_check(tcx, did, field.span, cb); |
| } |
| |
| // we're done. |
| return |
| } |
| // we don't look at stability attributes on |
| // struct-like enums (yet...), but it's definitely not |
| // a bug to have construct one. |
| ty::ty_enum(..) => return, |
| _ => { |
| tcx.sess.span_bug(e.span, |
| &format!("stability::check_expr: struct construction \ |
| of non-struct, type {:?}", |
| type_.repr(tcx))); |
| } |
| } |
| } |
| _ => return |
| }; |
| |
| maybe_do_stability_check(tcx, id, span, cb); |
| } |
| |
| pub fn check_path(tcx: &ty::ctxt, path: &ast::Path, id: ast::NodeId, |
| cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) { |
| match tcx.def_map.borrow().get(&id).map(|d| d.full_def()) { |
| Some(def::DefPrimTy(..)) => {} |
| Some(def) => { |
| maybe_do_stability_check(tcx, def.def_id(), path.span, cb); |
| } |
| None => {} |
| } |
| |
| } |
| |
| pub fn check_pat(tcx: &ty::ctxt, pat: &ast::Pat, |
| cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) { |
| debug!("check_pat(pat = {:?})", pat); |
| if is_internal(tcx, pat.span) { return; } |
| |
| let did = match ty::pat_ty_opt(tcx, pat) { |
| Some(&ty::TyS { sty: ty::ty_struct(did, _), .. }) => did, |
| Some(_) | None => return, |
| }; |
| let struct_fields = ty::lookup_struct_fields(tcx, did); |
| match pat.node { |
| // Foo(a, b, c) |
| ast::PatEnum(_, Some(ref pat_fields)) => { |
| for (field, struct_field) in pat_fields.iter().zip(struct_fields.iter()) { |
| // a .. pattern is fine, but anything positional is |
| // not. |
| if let ast::PatWild(ast::PatWildMulti) = field.node { |
| continue |
| } |
| maybe_do_stability_check(tcx, struct_field.id, field.span, cb) |
| } |
| } |
| // Foo { a, b, c } |
| ast::PatStruct(_, ref pat_fields, _) => { |
| for field in pat_fields { |
| let did = struct_fields |
| .iter() |
| .find(|f| f.name == field.node.ident.name) |
| .unwrap_or_else(|| { |
| tcx.sess.span_bug(field.span, |
| "stability::check_pat: unknown named field access") |
| }) |
| .id; |
| maybe_do_stability_check(tcx, did, field.span, cb); |
| } |
| } |
| // everything else is fine. |
| _ => {} |
| } |
| } |
| |
| fn maybe_do_stability_check(tcx: &ty::ctxt, id: ast::DefId, span: Span, |
| cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) { |
| if !is_staged_api(tcx, id) { return } |
| if is_internal(tcx, span) { return } |
| let ref stability = lookup(tcx, id); |
| cb(id, span, stability); |
| } |
| |
| fn is_internal(tcx: &ty::ctxt, span: Span) -> bool { |
| tcx.sess.codemap().span_allows_unstable(span) |
| } |
| |
| fn is_staged_api(tcx: &ty::ctxt, id: DefId) -> bool { |
| match ty::trait_item_of_item(tcx, id) { |
| Some(ty::MethodTraitItemId(trait_method_id)) |
| if trait_method_id != id => { |
| is_staged_api(tcx, trait_method_id) |
| } |
| _ if is_local(id) => { |
| tcx.stability.borrow().staged_api |
| } |
| _ => { |
| csearch::is_staged_api(&tcx.sess.cstore, id) |
| } |
| } |
| } |
| |
| /// Lookup the stability for a node, loading external crate |
| /// metadata as necessary. |
| pub fn lookup(tcx: &ty::ctxt, id: DefId) -> Option<Stability> { |
| debug!("lookup(id={})", |
| id.repr(tcx)); |
| |
| // is this definition the implementation of a trait method? |
| match ty::trait_item_of_item(tcx, id) { |
| Some(ty::MethodTraitItemId(trait_method_id)) if trait_method_id != id => { |
| debug!("lookup: trait_method_id={:?}", trait_method_id); |
| return lookup(tcx, trait_method_id) |
| } |
| _ => {} |
| } |
| |
| let item_stab = if is_local(id) { |
| tcx.stability.borrow().local.get(&id.node).cloned() |
| } else { |
| let stab = csearch::get_stability(&tcx.sess.cstore, id); |
| let mut index = tcx.stability.borrow_mut(); |
| (*index).extern_cache.insert(id, stab.clone()); |
| stab |
| }; |
| |
| item_stab.or_else(|| { |
| if let Some(trait_id) = ty::trait_id_of_impl(tcx, id) { |
| // FIXME (#18969): for the time being, simply use the |
| // stability of the trait to determine the stability of any |
| // unmarked impls for it. See FIXME above for more details. |
| |
| debug!("lookup: trait_id={:?}", trait_id); |
| lookup(tcx, trait_id) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| /// Given the list of enabled features that were not language features (i.e. that |
| /// were expected to be library features), and the list of features used from |
| /// libraries, identify activated features that don't exist and error about them. |
| pub fn check_unused_or_stable_features(sess: &Session, |
| lib_features_used: &FnvHashMap<InternedString, |
| attr::StabilityLevel>) { |
| let ref declared_lib_features = sess.features.borrow().declared_lib_features; |
| let mut remaining_lib_features: FnvHashMap<InternedString, Span> |
| = declared_lib_features.clone().into_iter().collect(); |
| |
| let stable_msg = "this feature is stable. attribute no longer needed"; |
| |
| for &span in sess.features.borrow().declared_stable_lang_features.iter() { |
| sess.add_lint(lint::builtin::STABLE_FEATURES, |
| ast::CRATE_NODE_ID, |
| span, |
| stable_msg.to_string()); |
| } |
| |
| for (used_lib_feature, level) in lib_features_used.iter() { |
| match remaining_lib_features.remove(used_lib_feature) { |
| Some(span) => { |
| if *level == attr::Stable { |
| sess.add_lint(lint::builtin::STABLE_FEATURES, |
| ast::CRATE_NODE_ID, |
| span, |
| stable_msg.to_string()); |
| } |
| } |
| None => ( /* used but undeclared, handled during the previous ast visit */ ) |
| } |
| } |
| |
| for (_, &span) in remaining_lib_features.iter() { |
| sess.add_lint(lint::builtin::UNUSED_FEATURES, |
| ast::CRATE_NODE_ID, |
| span, |
| "unused or unknown feature".to_string()); |
| } |
| } |