1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! A pass that annotates every item and method with its stability level,
12 //! propagating default levels lexically from parent to children ast nodes.
17 use middle::def_id::{DefId, LOCAL_CRATE};
19 use middle::privacy::PublicItems;
20 use metadata::csearch;
21 use syntax::parse::token::InternedString;
22 use syntax::codemap::{Span, DUMMY_SP};
24 use syntax::ast::{NodeId, Attribute};
25 use syntax::feature_gate::{GateIssue, emit_feature_err};
26 use syntax::attr::{self, Stability, AttrMetaMethods};
27 use util::nodemap::{DefIdMap, FnvHashSet, FnvHashMap};
30 use rustc_front::hir::{FnDecl, Block, Crate, Item, Generics, StructField, Variant};
31 use rustc_front::visit::{self, FnKind, Visitor};
33 use std::mem::replace;
34 use std::cmp::Ordering;
36 /// A stability index, giving the stability level for items and methods.
37 pub struct Index<'tcx> {
38 /// This is mostly a cache, except the stabilities of local items
39 /// are filled by the annotator.
40 map: DefIdMap<Option<&'tcx Stability>>,
42 /// Maps for each crate whether it is part of the staged API.
43 staged_api: FnvHashMap<ast::CrateNum, bool>
46 // A private tree-walker for producing an Index.
47 struct Annotator<'a, 'tcx: 'a> {
48 tcx: &'a ty::ctxt<'tcx>,
49 index: &'a mut Index<'tcx>,
50 parent: Option<&'tcx Stability>,
51 export_map: &'a PublicItems,
54 impl<'a, 'tcx: 'a> Annotator<'a, 'tcx> {
55 // Determine the stability for a node based on its attributes and inherited
56 // stability. The stability is recorded in the index and used as the parent.
57 fn annotate<F>(&mut self, id: NodeId, use_parent: bool,
58 attrs: &Vec<Attribute>, item_sp: Span, f: F, required: bool) where
59 F: FnOnce(&mut Annotator),
61 if self.index.staged_api[&LOCAL_CRATE] {
62 debug!("annotate(id = {:?}, attrs = {:?})", id, attrs);
63 match attr::find_stability(self.tcx.sess.diagnostic(), attrs, item_sp) {
65 debug!("annotate: found {:?}", stab);
66 // if parent is deprecated and we're not, inherit this by merging
67 // deprecated_since and its reason.
68 if let Some(parent_stab) = self.parent {
69 if parent_stab.deprecated_since.is_some()
70 && stab.deprecated_since.is_none() {
71 stab.deprecated_since = parent_stab.deprecated_since.clone();
72 stab.reason = parent_stab.reason.clone();
76 let stab = self.tcx.intern_stability(stab);
78 // Check if deprecated_since < stable_since. If it is,
79 // this is *almost surely* an accident.
80 let deprecated_predates_stable = match (stab.deprecated_since.as_ref(),
81 stab.since.as_ref()) {
82 (Some(dep_since), Some(stab_since)) => {
83 // explicit version of iter::order::lt to handle parse errors properly
84 let mut is_less = false;
85 for (dep_v, stab_v) in dep_since.split(".").zip(stab_since.split(".")) {
86 match (dep_v.parse::<u64>(), stab_v.parse::<u64>()) {
87 (Ok(dep_v), Ok(stab_v)) => match dep_v.cmp(&stab_v) {
92 Ordering::Equal => { continue; }
93 Ordering::Greater => { break; }
96 self.tcx.sess.span_err(item_sp,
97 "Invalid stability or deprecation version found");
98 // act like it isn't less because the question is now
99 // nonsensical, and this makes us not do anything else
110 if deprecated_predates_stable {
111 self.tcx.sess.span_err(item_sp,
112 "An API can't be stabilized after it is deprecated");
115 self.index.map.insert(DefId::local(id), Some(stab));
117 // Don't inherit #[stable(feature = "rust1", since = "1.0.0")]
118 if stab.level != attr::Stable {
119 let parent = replace(&mut self.parent, Some(stab));
121 self.parent = parent;
127 debug!("annotate: not found, use_parent = {:?}, parent = {:?}",
128 use_parent, self.parent);
130 if let Some(stab) = self.parent {
131 self.index.map.insert(DefId::local(id), Some(stab));
132 } else if self.index.staged_api[&LOCAL_CRATE] && required
133 && self.export_map.contains(&id)
134 && !self.tcx.sess.opts.test {
135 self.tcx.sess.span_err(item_sp,
136 "This node does not \
137 have a stability attribute");
144 // Emit warnings for non-staged-api crates. These should be errors.
146 let tag = attr.name();
147 if tag == "unstable" || tag == "stable" || tag == "deprecated" {
148 attr::mark_used(attr);
149 self.tcx.sess.span_err(attr.span(),
150 "stability attributes may not be used outside \
151 of the standard library");
159 impl<'a, 'tcx, 'v> Visitor<'v> for Annotator<'a, 'tcx> {
160 fn visit_item(&mut self, i: &Item) {
161 // FIXME (#18969): the following is a hack around the fact
162 // that we cannot currently annotate the stability of
163 // `deriving`. Basically, we do *not* allow stability
164 // inheritance on trait implementations, so that derived
165 // implementations appear to be unannotated. This then allows
166 // derived implementations to be automatically tagged with the
167 // stability of the trait. This is WRONG, but expedient to get
168 // libstd stabilized for the 1.0 release.
169 let use_parent = match i.node {
170 hir::ItemImpl(_, _, _, Some(_), _, _) => false,
174 // In case of a `pub use <mod>;`, we should not error since the stability
175 // is inherited from the module itself
176 let required = match i.node {
177 hir::ItemUse(_) => i.vis != hir::Public,
181 self.annotate(i.id, use_parent, &i.attrs, i.span,
182 |v| visit::walk_item(v, i), required);
184 if let hir::ItemStruct(ref sd, _) = i.node {
185 sd.ctor_id.map(|id| {
186 self.annotate(id, true, &i.attrs, i.span, |_| {}, true)
191 fn visit_fn(&mut self, _: FnKind<'v>, _: &'v FnDecl,
192 _: &'v Block, _: Span, _: NodeId) {
193 // Items defined in a function body have no reason to have
194 // a stability attribute, so we don't recurse.
197 fn visit_trait_item(&mut self, ti: &hir::TraitItem) {
198 self.annotate(ti.id, true, &ti.attrs, ti.span,
199 |v| visit::walk_trait_item(v, ti), true);
202 fn visit_impl_item(&mut self, ii: &hir::ImplItem) {
203 self.annotate(ii.id, true, &ii.attrs, ii.span,
204 |v| visit::walk_impl_item(v, ii), true);
207 fn visit_variant(&mut self, var: &Variant, g: &'v Generics) {
208 self.annotate(var.node.id, true, &var.node.attrs, var.span,
209 |v| visit::walk_variant(v, var, g), true)
212 fn visit_struct_field(&mut self, s: &StructField) {
213 self.annotate(s.node.id, true, &s.node.attrs, s.span,
214 |v| visit::walk_struct_field(v, s), true);
217 fn visit_foreign_item(&mut self, i: &hir::ForeignItem) {
218 self.annotate(i.id, true, &i.attrs, i.span, |_| {}, true);
222 impl<'tcx> Index<'tcx> {
223 /// Construct the stability index for a crate being compiled.
224 pub fn build(&mut self, tcx: &ty::ctxt<'tcx>, krate: &Crate, export_map: &PublicItems) {
225 let mut annotator = Annotator {
229 export_map: export_map,
231 annotator.annotate(ast::CRATE_NODE_ID, true, &krate.attrs, krate.span,
232 |v| visit::walk_crate(v, krate), true);
235 pub fn new(krate: &Crate) -> Index {
236 let mut is_staged_api = false;
237 for attr in &krate.attrs {
238 if &attr.name()[..] == "staged_api" {
239 match attr.node.value.node {
240 ast::MetaWord(_) => {
241 attr::mark_used(attr);
242 is_staged_api = true;
248 let mut staged_api = FnvHashMap();
249 staged_api.insert(LOCAL_CRATE, is_staged_api);
251 staged_api: staged_api,
257 /// Cross-references the feature names of unstable APIs with enabled
258 /// features and possibly prints errors. Returns a list of all
260 pub fn check_unstable_api_usage(tcx: &ty::ctxt)
261 -> FnvHashMap<InternedString, attr::StabilityLevel> {
262 let ref active_lib_features = tcx.sess.features.borrow().declared_lib_features;
264 // Put the active features into a map for quick lookup
265 let active_features = active_lib_features.iter().map(|&(ref s, _)| s.clone()).collect();
267 let mut checker = Checker {
269 active_features: active_features,
270 used_features: FnvHashMap()
273 let krate = tcx.map.krate();
274 visit::walk_crate(&mut checker, krate);
276 let used_features = checker.used_features;
277 return used_features;
280 struct Checker<'a, 'tcx: 'a> {
281 tcx: &'a ty::ctxt<'tcx>,
282 active_features: FnvHashSet<InternedString>,
283 used_features: FnvHashMap<InternedString, attr::StabilityLevel>
286 impl<'a, 'tcx> Checker<'a, 'tcx> {
287 fn check(&mut self, id: DefId, span: Span, stab: &Option<&Stability>) {
288 // Only the cross-crate scenario matters when checking unstable APIs
289 let cross_crate = !id.is_local();
290 if !cross_crate { return }
293 Some(&Stability { level: attr::Unstable, ref feature, ref reason, issue, .. }) => {
294 self.used_features.insert(feature.clone(), attr::Unstable);
296 if !self.active_features.contains(feature) {
297 let msg = match *reason {
298 Some(ref r) => format!("use of unstable library feature '{}': {}",
300 None => format!("use of unstable library feature '{}'", &feature)
303 emit_feature_err(&self.tcx.sess.parse_sess.span_diagnostic,
304 &feature, span, GateIssue::Library(issue), &msg);
307 Some(&Stability { level, ref feature, .. }) => {
308 self.used_features.insert(feature.clone(), level);
310 // Stable APIs are always ok to call and deprecated APIs are
311 // handled by a lint.
314 // This is an 'unmarked' API, which should not exist
315 // in the standard library.
316 if self.tcx.sess.features.borrow().unmarked_api {
317 self.tcx.sess.span_warn(span, "use of unmarked library feature");
318 self.tcx.sess.span_note(span, "this is either a bug in the library you are \
319 using or a bug in the compiler - please \
320 report it in both places");
322 self.tcx.sess.span_err(span, "use of unmarked library feature");
323 self.tcx.sess.span_note(span, "this is either a bug in the library you are \
324 using or a bug in the compiler - please \
325 report it in both places");
326 self.tcx.sess.span_note(span, "use #![feature(unmarked_api)] in the \
327 crate attributes to override this");
334 impl<'a, 'v, 'tcx> Visitor<'v> for Checker<'a, 'tcx> {
335 fn visit_item(&mut self, item: &hir::Item) {
336 // When compiling with --test we don't enforce stability on the
337 // compiler-generated test module, demarcated with `DUMMY_SP` plus the
339 if item.span == DUMMY_SP && item.ident.name == "__test" { return }
341 check_item(self.tcx, item, true,
342 &mut |id, sp, stab| self.check(id, sp, stab));
343 visit::walk_item(self, item);
346 fn visit_expr(&mut self, ex: &hir::Expr) {
347 check_expr(self.tcx, ex,
348 &mut |id, sp, stab| self.check(id, sp, stab));
349 visit::walk_expr(self, ex);
352 fn visit_path(&mut self, path: &hir::Path, id: ast::NodeId) {
353 check_path(self.tcx, path, id,
354 &mut |id, sp, stab| self.check(id, sp, stab));
355 visit::walk_path(self, path)
358 fn visit_pat(&mut self, pat: &hir::Pat) {
359 check_pat(self.tcx, pat,
360 &mut |id, sp, stab| self.check(id, sp, stab));
361 visit::walk_pat(self, pat)
365 /// Helper for discovering nodes to check for stability
366 pub fn check_item(tcx: &ty::ctxt, item: &hir::Item, warn_about_defns: bool,
367 cb: &mut FnMut(DefId, Span, &Option<&Stability>)) {
369 hir::ItemExternCrate(_) => {
370 // compiler-generated `extern crate` items have a dummy span.
371 if item.span == DUMMY_SP { return }
373 let cnum = match tcx.sess.cstore.find_extern_mod_stmt_cnum(item.id) {
377 let id = DefId { krate: cnum, node: ast::CRATE_NODE_ID };
378 maybe_do_stability_check(tcx, id, item.span, cb);
381 // For implementations of traits, check the stability of each item
382 // individually as it's possible to have a stable trait with unstable
384 hir::ItemImpl(_, _, _, Some(ref t), _, ref impl_items) => {
385 let trait_did = tcx.def_map.borrow().get(&t.ref_id).unwrap().def_id();
386 let trait_items = tcx.trait_items(trait_did);
388 for impl_item in impl_items {
389 let item = trait_items.iter().find(|item| {
390 item.name() == impl_item.ident.name
392 if warn_about_defns {
393 maybe_do_stability_check(tcx, item.def_id(), impl_item.span, cb);
402 /// Helper for discovering nodes to check for stability
403 pub fn check_expr(tcx: &ty::ctxt, e: &hir::Expr,
404 cb: &mut FnMut(DefId, Span, &Option<&Stability>)) {
406 let id = match e.node {
407 hir::ExprMethodCall(i, _, _) => {
409 let method_call = ty::MethodCall::expr(e.id);
410 tcx.tables.borrow().method_map[&method_call].def_id
412 hir::ExprField(ref base_e, ref field) => {
414 match tcx.expr_ty_adjusted(base_e).sty {
415 ty::TyStruct(def, _) => def.struct_variant().field_named(field.node.name).did,
416 _ => tcx.sess.span_bug(e.span,
417 "stability::check_expr: named field access on non-struct")
420 hir::ExprTupField(ref base_e, ref field) => {
422 match tcx.expr_ty_adjusted(base_e).sty {
423 ty::TyStruct(def, _) => def.struct_variant().fields[field.node].did,
424 ty::TyTuple(..) => return,
425 _ => tcx.sess.span_bug(e.span,
426 "stability::check_expr: unnamed field access on \
427 something other than a tuple or struct")
430 hir::ExprStruct(_, ref expr_fields, _) => {
431 let type_ = tcx.expr_ty(e);
433 ty::TyStruct(def, _) => {
434 // check the stability of each field that appears
435 // in the construction expression.
436 for field in expr_fields {
437 let did = def.struct_variant()
438 .field_named(field.ident.node.name)
440 maybe_do_stability_check(tcx, did, field.span, cb);
446 // we don't look at stability attributes on
447 // struct-like enums (yet...), but it's definitely not
448 // a bug to have construct one.
449 ty::TyEnum(..) => return,
451 tcx.sess.span_bug(e.span,
452 &format!("stability::check_expr: struct construction \
453 of non-struct, type {:?}",
461 maybe_do_stability_check(tcx, id, span, cb);
464 pub fn check_path(tcx: &ty::ctxt, path: &hir::Path, id: ast::NodeId,
465 cb: &mut FnMut(DefId, Span, &Option<&Stability>)) {
466 match tcx.def_map.borrow().get(&id).map(|d| d.full_def()) {
467 Some(def::DefPrimTy(..)) => {}
469 maybe_do_stability_check(tcx, def.def_id(), path.span, cb);
476 pub fn check_pat(tcx: &ty::ctxt, pat: &hir::Pat,
477 cb: &mut FnMut(DefId, Span, &Option<&Stability>)) {
478 debug!("check_pat(pat = {:?})", pat);
479 if is_internal(tcx, pat.span) { return; }
481 let v = match tcx.pat_ty_opt(pat) {
482 Some(&ty::TyS { sty: ty::TyStruct(def, _), .. }) => def.struct_variant(),
483 Some(_) | None => return,
487 hir::PatEnum(_, Some(ref pat_fields)) => {
488 for (field, struct_field) in pat_fields.iter().zip(&v.fields) {
489 // a .. pattern is fine, but anything positional is
491 if let hir::PatWild(hir::PatWildMulti) = field.node {
494 maybe_do_stability_check(tcx, struct_field.did, field.span, cb)
498 hir::PatStruct(_, ref pat_fields, _) => {
499 for field in pat_fields {
500 let did = v.field_named(field.node.ident.name).did;
501 maybe_do_stability_check(tcx, did, field.span, cb);
504 // everything else is fine.
509 fn maybe_do_stability_check(tcx: &ty::ctxt, id: DefId, span: Span,
510 cb: &mut FnMut(DefId, Span, &Option<&Stability>)) {
511 if !is_staged_api(tcx, id) {
512 debug!("maybe_do_stability_check: \
513 skipping id={:?} since it is not staged_api", id);
516 if is_internal(tcx, span) {
517 debug!("maybe_do_stability_check: \
518 skipping span={:?} since it is internal", span);
521 let ref stability = lookup(tcx, id);
522 debug!("maybe_do_stability_check: \
523 inspecting id={:?} span={:?} of stability={:?}", id, span, stability);
524 cb(id, span, stability);
527 fn is_internal(tcx: &ty::ctxt, span: Span) -> bool {
528 tcx.sess.codemap().span_allows_unstable(span)
531 fn is_staged_api(tcx: &ty::ctxt, id: DefId) -> bool {
532 match tcx.trait_item_of_item(id) {
533 Some(ty::MethodTraitItemId(trait_method_id))
534 if trait_method_id != id => {
535 is_staged_api(tcx, trait_method_id)
538 *tcx.stability.borrow_mut().staged_api.entry(id.krate).or_insert_with(
539 || csearch::is_staged_api(&tcx.sess.cstore, id.krate))
544 /// Lookup the stability for a node, loading external crate
545 /// metadata as necessary.
546 pub fn lookup<'tcx>(tcx: &ty::ctxt<'tcx>, id: DefId) -> Option<&'tcx Stability> {
547 if let Some(st) = tcx.stability.borrow().map.get(&id) {
551 let st = lookup_uncached(tcx, id);
552 tcx.stability.borrow_mut().map.insert(id, st);
556 fn lookup_uncached<'tcx>(tcx: &ty::ctxt<'tcx>, id: DefId) -> Option<&'tcx Stability> {
557 debug!("lookup(id={:?})", id);
559 // is this definition the implementation of a trait method?
560 match tcx.trait_item_of_item(id) {
561 Some(ty::MethodTraitItemId(trait_method_id)) if trait_method_id != id => {
562 debug!("lookup: trait_method_id={:?}", trait_method_id);
563 return lookup(tcx, trait_method_id)
568 let item_stab = if id.is_local() {
569 None // The stability cache is filled partially lazily
571 csearch::get_stability(&tcx.sess.cstore, id).map(|st| tcx.intern_stability(st))
574 item_stab.or_else(|| {
576 if let Some(trait_id) = tcx.trait_id_of_impl(id) {
577 // FIXME (#18969): for the time being, simply use the
578 // stability of the trait to determine the stability of any
579 // unmarked impls for it. See FIXME above for more details.
581 debug!("lookup: trait_id={:?}", trait_id);
582 return lookup(tcx, trait_id);
589 /// Given the list of enabled features that were not language features (i.e. that
590 /// were expected to be library features), and the list of features used from
591 /// libraries, identify activated features that don't exist and error about them.
592 pub fn check_unused_or_stable_features(sess: &Session,
593 lib_features_used: &FnvHashMap<InternedString,
594 attr::StabilityLevel>) {
595 let ref declared_lib_features = sess.features.borrow().declared_lib_features;
596 let mut remaining_lib_features: FnvHashMap<InternedString, Span>
597 = declared_lib_features.clone().into_iter().collect();
599 let stable_msg = "this feature is stable. attribute no longer needed";
601 for &span in &sess.features.borrow().declared_stable_lang_features {
602 sess.add_lint(lint::builtin::STABLE_FEATURES,
605 stable_msg.to_string());
608 for (used_lib_feature, level) in lib_features_used {
609 match remaining_lib_features.remove(used_lib_feature) {
611 if *level == attr::Stable {
612 sess.add_lint(lint::builtin::STABLE_FEATURES,
615 stable_msg.to_string());
618 None => ( /* used but undeclared, handled during the previous ast visit */ )
622 for &span in remaining_lib_features.values() {
623 sess.add_lint(lint::builtin::UNUSED_FEATURES,
626 "unused or unknown feature".to_string());