// 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.

pub use self::StabilityLevel::*;

use dep_graph::DepNode;
use front::map as hir_map;
use session::Session;
use lint;
use middle::cstore::{CrateStore, LOCAL_CRATE};
use middle::def::Def;
use middle::def_id::{CRATE_DEF_INDEX, DefId};
use ty::{self, TyCtxt};
use middle::privacy::AccessLevels;
use syntax::parse::token::InternedString;
use syntax::codemap::{Span, DUMMY_SP};
use syntax::ast;
use syntax::ast::{NodeId, Attribute};
use syntax::feature_gate::{GateIssue, emit_feature_err};
use syntax::attr::{self, Stability, Deprecation, AttrMetaMethods};
use util::nodemap::{DefIdMap, FnvHashSet, FnvHashMap};

use rustc_front::hir;
use rustc_front::hir::{Item, Generics, StructField, Variant, PatKind};
use rustc_front::intravisit::{self, Visitor};

use std::mem::replace;
use std::cmp::Ordering;

#[derive(RustcEncodable, RustcDecodable, PartialEq, PartialOrd, Clone, Copy, Debug, Eq, Hash)]
pub enum StabilityLevel {
    Unstable,
    Stable,
}

impl StabilityLevel {
    pub fn from_attr_level(level: &attr::StabilityLevel) -> Self {
        if level.is_stable() { Stable } else { Unstable }
    }
}

#[derive(PartialEq)]
enum AnnotationKind {
    // Annotation is required if not inherited from unstable parents
    Required,
    // Annotation is useless, reject it
    Prohibited,
    // Annotation itself is useless, but it can be propagated to children
    Container,
}

/// A stability index, giving the stability level for items and methods.
pub struct Index<'tcx> {
    /// This is mostly a cache, except the stabilities of local items
    /// are filled by the annotator.
    stab_map: DefIdMap<Option<&'tcx Stability>>,
    depr_map: DefIdMap<Option<Deprecation>>,

    /// Maps for each crate whether it is part of the staged API.
    staged_api: FnvHashMap<ast::CrateNum, bool>
}

// A private tree-walker for producing an Index.
struct Annotator<'a, 'tcx: 'a> {
    tcx: &'a TyCtxt<'tcx>,
    index: &'a mut Index<'tcx>,
    parent_stab: Option<&'tcx Stability>,
    parent_depr: Option<Deprecation>,
    access_levels: &'a AccessLevels,
    in_trait_impl: bool,
}

impl<'a, 'tcx: 'a> Annotator<'a, 'tcx> {
    // 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, attrs: &[Attribute],
                   item_sp: Span, kind: AnnotationKind, visit_children: F)
        where F: FnOnce(&mut Annotator)
    {
        if self.index.staged_api[&LOCAL_CRATE] && self.tcx.sess.features.borrow().staged_api {
            debug!("annotate(id = {:?}, attrs = {:?})", id, attrs);
            if let Some(..) = attr::find_deprecation(self.tcx.sess.diagnostic(), attrs, item_sp) {
                self.tcx.sess.span_err(item_sp, "`#[deprecated]` cannot be used in staged api, \
                                                 use `#[rustc_deprecated]` instead");
            }
            if let Some(mut stab) = attr::find_stability(self.tcx.sess.diagnostic(),
                                                         attrs, item_sp) {
                // Error if prohibited, or can't inherit anything from a container
                if kind == AnnotationKind::Prohibited ||
                   (kind == AnnotationKind::Container &&
                    stab.level.is_stable() &&
                    stab.rustc_depr.is_none()) {
                    self.tcx.sess.span_err(item_sp, "This stability annotation is useless");
                }

                debug!("annotate: found {:?}", stab);
                // If parent is deprecated and we're not, inherit this by merging
                // deprecated_since and its reason.
                if let Some(parent_stab) = self.parent_stab {
                    if parent_stab.rustc_depr.is_some() && stab.rustc_depr.is_none() {
                        stab.rustc_depr = parent_stab.rustc_depr.clone()
                    }
                }

                let stab = self.tcx.intern_stability(stab);

                // Check if deprecated_since < stable_since. If it is,
                // this is *almost surely* an accident.
                if let (&Some(attr::RustcDeprecation {since: ref dep_since, ..}),
                        &attr::Stable {since: ref stab_since}) = (&stab.rustc_depr, &stab.level) {
                    // Explicit version of iter::order::lt to handle parse errors properly
                    for (dep_v, stab_v) in dep_since.split(".").zip(stab_since.split(".")) {
                        if let (Ok(dep_v), Ok(stab_v)) = (dep_v.parse::<u64>(), stab_v.parse()) {
                            match dep_v.cmp(&stab_v) {
                                Ordering::Less => {
                                    self.tcx.sess.span_err(item_sp, "An API can't be stabilized \
                                                                     after it is deprecated");
                                    break
                                }
                                Ordering::Equal => continue,
                                Ordering::Greater => break,
                            }
                        } else {
                            // Act like it isn't less because the question is now nonsensical,
                            // and this makes us not do anything else interesting.
                            self.tcx.sess.span_err(item_sp, "Invalid stability or deprecation \
                                                             version found");
                            break
                        }
                    }
                }

                let def_id = self.tcx.map.local_def_id(id);
                self.index.stab_map.insert(def_id, Some(stab));

                let orig_parent_stab = replace(&mut self.parent_stab, Some(stab));
                visit_children(self);
                self.parent_stab = orig_parent_stab;
            } else {
                debug!("annotate: not found, parent = {:?}", self.parent_stab);
                let mut is_error = kind == AnnotationKind::Required &&
                                   self.access_levels.is_reachable(id) &&
                                   !self.tcx.sess.opts.test;
                if let Some(stab) = self.parent_stab {
                    if stab.level.is_unstable() {
                        let def_id = self.tcx.map.local_def_id(id);
                        self.index.stab_map.insert(def_id, Some(stab));
                        is_error = false;
                    }
                }
                if is_error {
                    self.tcx.sess.span_err(item_sp, "This node does not have \
                                                     a stability attribute");
                }
                visit_children(self);
            }
        } else {
            // Emit errors for non-staged-api crates.
            for attr in attrs {
                let tag = attr.name();
                if tag == "unstable" || tag == "stable" || tag == "rustc_deprecated" {
                    attr::mark_used(attr);
                    self.tcx.sess.span_err(attr.span(), "stability attributes may not be used \
                                                         outside of the standard library");
                }
            }

            if let Some(depr) = attr::find_deprecation(self.tcx.sess.diagnostic(), attrs, item_sp) {
                if kind == AnnotationKind::Prohibited {
                    self.tcx.sess.span_err(item_sp, "This deprecation annotation is useless");
                }

                // `Deprecation` is just two pointers, no need to intern it
                let def_id = self.tcx.map.local_def_id(id);
                self.index.depr_map.insert(def_id, Some(depr.clone()));

                let orig_parent_depr = replace(&mut self.parent_depr, Some(depr));
                visit_children(self);
                self.parent_depr = orig_parent_depr;
            } else if let Some(depr) = self.parent_depr.clone() {
                let def_id = self.tcx.map.local_def_id(id);
                self.index.depr_map.insert(def_id, Some(depr));
                visit_children(self);
            } else {
                visit_children(self);
            }
        }
    }
}

impl<'a, 'tcx, 'v> Visitor<'v> for Annotator<'a, 'tcx> {
    /// Because stability levels are scoped lexically, we want to walk
    /// nested items in the context of the outer item, so enable
    /// deep-walking.
    fn visit_nested_item(&mut self, item: hir::ItemId) {
        self.visit_item(self.tcx.map.expect_item(item.id))
    }

    fn visit_item(&mut self, i: &Item) {
        let orig_in_trait_impl = self.in_trait_impl;
        let mut kind = AnnotationKind::Required;
        match i.node {
            // Inherent impls and foreign modules serve only as containers for other items,
            // they don't have their own stability. They still can be annotated as unstable
            // and propagate this unstability to children, but this annotation is completely
            // optional. They inherit stability from their parents when unannotated.
            hir::ItemImpl(_, _, _, None, _, _) | hir::ItemForeignMod(..) => {
                self.in_trait_impl = false;
                kind = AnnotationKind::Container;
            }
            hir::ItemImpl(_, _, _, Some(_), _, _) => {
                self.in_trait_impl = true;
            }
            hir::ItemStruct(ref sd, _) => {
                if !sd.is_struct() {
                    self.annotate(sd.id(), &i.attrs, i.span, AnnotationKind::Required, |_| {})
                }
            }
            _ => {}
        }

        self.annotate(i.id, &i.attrs, i.span, kind, |v| {
            intravisit::walk_item(v, i)
        });
        self.in_trait_impl = orig_in_trait_impl;
    }

    fn visit_trait_item(&mut self, ti: &hir::TraitItem) {
        self.annotate(ti.id, &ti.attrs, ti.span, AnnotationKind::Required, |v| {
            intravisit::walk_trait_item(v, ti);
        });
    }

    fn visit_impl_item(&mut self, ii: &hir::ImplItem) {
        let kind = if self.in_trait_impl {
            AnnotationKind::Prohibited
        } else {
            AnnotationKind::Required
        };
        self.annotate(ii.id, &ii.attrs, ii.span, kind, |v| {
            intravisit::walk_impl_item(v, ii);
        });
    }

    fn visit_variant(&mut self, var: &Variant, g: &'v Generics, item_id: NodeId) {
        self.annotate(var.node.data.id(), &var.node.attrs, var.span, AnnotationKind::Required, |v| {
            intravisit::walk_variant(v, var, g, item_id);
        })
    }

    fn visit_struct_field(&mut self, s: &StructField) {
        self.annotate(s.id, &s.attrs, s.span, AnnotationKind::Required, |v| {
            intravisit::walk_struct_field(v, s);
        });
    }

    fn visit_foreign_item(&mut self, i: &hir::ForeignItem) {
        self.annotate(i.id, &i.attrs, i.span, AnnotationKind::Required, |v| {
            intravisit::walk_foreign_item(v, i);
        });
    }

    fn visit_macro_def(&mut self, md: &'v hir::MacroDef) {
        if md.imported_from.is_none() {
            self.annotate(md.id, &md.attrs, md.span, AnnotationKind::Required, |_| {});
        }
    }
}

impl<'tcx> Index<'tcx> {
    /// Construct the stability index for a crate being compiled.
    pub fn build(&mut self, tcx: &TyCtxt<'tcx>, access_levels: &AccessLevels) {
        let _task = tcx.dep_graph.in_task(DepNode::StabilityIndex);
        let krate = tcx.map.krate();
        let mut annotator = Annotator {
            tcx: tcx,
            index: self,
            parent_stab: None,
            parent_depr: None,
            access_levels: access_levels,
            in_trait_impl: false,
        };
        annotator.annotate(ast::CRATE_NODE_ID, &krate.attrs, krate.span, AnnotationKind::Required,
                           |v| intravisit::walk_crate(v, krate));
    }

    pub fn new(hir_map: &hir_map::Map) -> Index<'tcx> {
        let _task = hir_map.dep_graph.in_task(DepNode::StabilityIndex);
        let krate = hir_map.krate();

        let mut is_staged_api = false;
        for attr in &krate.attrs {
            if attr.name() == "stable" || attr.name() == "unstable" {
                is_staged_api = true;
                break
            }
        }

        let mut staged_api = FnvHashMap();
        staged_api.insert(LOCAL_CRATE, is_staged_api);
        Index {
            staged_api: staged_api,
            stab_map: DefIdMap(),
            depr_map: 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: &TyCtxt)
                                -> FnvHashMap<InternedString, StabilityLevel> {
    let _task = tcx.dep_graph.in_task(DepNode::StabilityCheck);
    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(),
        in_skip_block: 0,
    };
    intravisit::walk_crate(&mut checker, tcx.map.krate());

    checker.used_features
}

struct Checker<'a, 'tcx: 'a> {
    tcx: &'a TyCtxt<'tcx>,
    active_features: FnvHashSet<InternedString>,
    used_features: FnvHashMap<InternedString, StabilityLevel>,
    // Within a block where feature gate checking can be skipped.
    in_skip_block: u32,
}

impl<'a, 'tcx> Checker<'a, 'tcx> {
    fn check(&mut self, id: DefId, span: Span,
             stab: &Option<&Stability>, _depr: &Option<Deprecation>) {
        if !is_staged_api(self.tcx, id) {
            return;
        }
        // Only the cross-crate scenario matters when checking unstable APIs
        let cross_crate = !id.is_local();
        if !cross_crate {
            return
        }

        // We don't need to check for stability - presumably compiler generated code.
        if self.in_skip_block > 0 {
            return;
        }

        match *stab {
            Some(&Stability { level: attr::Unstable {ref reason, issue}, ref feature, .. }) => {
                self.used_features.insert(feature.clone(), 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, GateIssue::Library(Some(issue)), &msg);
                }
            }
            Some(&Stability { ref level, ref feature, .. }) => {
                self.used_features.insert(feature.clone(), StabilityLevel::from_attr_level(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.struct_span_warn(span, "use of unmarked library feature")
                                 .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")
                                 .emit()
                } else {
                    self.tcx.sess.struct_span_err(span, "use of unmarked library feature")
                                 .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")
                                 .span_note(span, "use #![feature(unmarked_api)] in the \
                                                   crate attributes to override this")
                                 .emit()
                }
            }
        }
    }
}

impl<'a, 'v, 'tcx> Visitor<'v> for Checker<'a, 'tcx> {
    /// Because stability levels are scoped lexically, we want to walk
    /// nested items in the context of the outer item, so enable
    /// deep-walking.
    fn visit_nested_item(&mut self, item: hir::ItemId) {
        self.visit_item(self.tcx.map.expect_item(item.id))
    }

    fn visit_item(&mut self, item: &hir::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.name.as_str() == "__test" { return }

        check_item(self.tcx, item, true,
                   &mut |id, sp, stab, depr| self.check(id, sp, stab, depr));
        intravisit::walk_item(self, item);
    }

    fn visit_expr(&mut self, ex: &hir::Expr) {
        check_expr(self.tcx, ex,
                   &mut |id, sp, stab, depr| self.check(id, sp, stab, depr));
        intravisit::walk_expr(self, ex);
    }

    fn visit_path(&mut self, path: &hir::Path, id: ast::NodeId) {
        check_path(self.tcx, path, id,
                   &mut |id, sp, stab, depr| self.check(id, sp, stab, depr));
        intravisit::walk_path(self, path)
    }

    fn visit_path_list_item(&mut self, prefix: &hir::Path, item: &hir::PathListItem) {
        check_path_list_item(self.tcx, item,
                   &mut |id, sp, stab, depr| self.check(id, sp, stab, depr));
        intravisit::walk_path_list_item(self, prefix, item)
    }

    fn visit_pat(&mut self, pat: &hir::Pat) {
        check_pat(self.tcx, pat,
                  &mut |id, sp, stab, depr| self.check(id, sp, stab, depr));
        intravisit::walk_pat(self, pat)
    }

    fn visit_block(&mut self, b: &hir::Block) {
        let old_skip_count = self.in_skip_block;
        match b.rules {
            hir::BlockCheckMode::PushUnstableBlock => {
                self.in_skip_block += 1;
            }
            hir::BlockCheckMode::PopUnstableBlock => {
                self.in_skip_block = self.in_skip_block.checked_sub(1).unwrap();
            }
            _ => {}
        }
        intravisit::walk_block(self, b);
        self.in_skip_block = old_skip_count;
    }
}

/// Helper for discovering nodes to check for stability
pub fn check_item(tcx: &TyCtxt, item: &hir::Item, warn_about_defns: bool,
                  cb: &mut FnMut(DefId, Span, &Option<&Stability>, &Option<Deprecation>)) {
    match item.node {
        hir::ItemExternCrate(_) => {
            // compiler-generated `extern crate` items have a dummy span.
            if item.span == DUMMY_SP { return }

            let cnum = match tcx.sess.cstore.extern_mod_stmt_cnum(item.id) {
                Some(cnum) => cnum,
                None => return,
            };
            let id = DefId { krate: cnum, index: CRATE_DEF_INDEX };
            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.
        hir::ItemImpl(_, _, _, Some(ref t), _, ref impl_items) => {
            let trait_did = tcx.def_map.borrow().get(&t.ref_id).unwrap().def_id();
            let trait_items = tcx.trait_items(trait_did);

            for impl_item in impl_items {
                let item = trait_items.iter().find(|item| {
                    item.name() == impl_item.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: &TyCtxt, e: &hir::Expr,
                  cb: &mut FnMut(DefId, Span, &Option<&Stability>, &Option<Deprecation>)) {
    let span;
    let id = match e.node {
        hir::ExprMethodCall(i, _, _) => {
            span = i.span;
            let method_call = ty::MethodCall::expr(e.id);
            tcx.tables.borrow().method_map[&method_call].def_id
        }
        hir::ExprField(ref base_e, ref field) => {
            span = field.span;
            match tcx.expr_ty_adjusted(base_e).sty {
                ty::TyStruct(def, _) => def.struct_variant().field_named(field.node).did,
                _ => span_bug!(e.span,
                               "stability::check_expr: named field access on non-struct")
            }
        }
        hir::ExprTupField(ref base_e, ref field) => {
            span = field.span;
            match tcx.expr_ty_adjusted(base_e).sty {
                ty::TyStruct(def, _) => def.struct_variant().fields[field.node].did,
                ty::TyTuple(..) => return,
                _ => span_bug!(e.span,
                               "stability::check_expr: unnamed field access on \
                                something other than a tuple or struct")
            }
        }
        hir::ExprStruct(_, ref expr_fields, _) => {
            let type_ = tcx.expr_ty(e);
            match type_.sty {
                ty::TyStruct(def, _) => {
                    // check the stability of each field that appears
                    // in the construction expression.
                    for field in expr_fields {
                        let did = def.struct_variant()
                            .field_named(field.name.node)
                            .did;
                        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::TyEnum(..) => return,
                _ => {
                    span_bug!(e.span,
                              "stability::check_expr: struct construction \
                               of non-struct, type {:?}",
                              type_);
                }
            }
        }
        _ => return
    };

    maybe_do_stability_check(tcx, id, span, cb);
}

pub fn check_path(tcx: &TyCtxt, path: &hir::Path, id: ast::NodeId,
                  cb: &mut FnMut(DefId, Span, &Option<&Stability>, &Option<Deprecation>)) {
    match tcx.def_map.borrow().get(&id).map(|d| d.full_def()) {
        Some(Def::PrimTy(..)) => {}
        Some(Def::SelfTy(..)) => {}
        Some(def) => {
            maybe_do_stability_check(tcx, def.def_id(), path.span, cb);
        }
        None => {}
    }
}

pub fn check_path_list_item(tcx: &TyCtxt, item: &hir::PathListItem,
                  cb: &mut FnMut(DefId, Span, &Option<&Stability>, &Option<Deprecation>)) {
    match tcx.def_map.borrow().get(&item.node.id()).map(|d| d.full_def()) {
        Some(Def::PrimTy(..)) => {}
        Some(def) => {
            maybe_do_stability_check(tcx, def.def_id(), item.span, cb);
        }
        None => {}
    }
}

pub fn check_pat(tcx: &TyCtxt, pat: &hir::Pat,
                 cb: &mut FnMut(DefId, Span, &Option<&Stability>, &Option<Deprecation>)) {
    debug!("check_pat(pat = {:?})", pat);
    if is_internal(tcx, pat.span) { return; }

    let v = match tcx.pat_ty_opt(pat) {
        Some(&ty::TyS { sty: ty::TyStruct(def, _), .. }) => def.struct_variant(),
        Some(_) | None => return,
    };
    match pat.node {
        // Foo(a, b, c)
        // A Variant(..) pattern `PatKind::TupleStruct(_, None)` doesn't have to be recursed into.
        PatKind::TupleStruct(_, Some(ref pat_fields)) => {
            for (field, struct_field) in pat_fields.iter().zip(&v.fields) {
                maybe_do_stability_check(tcx, struct_field.did, field.span, cb)
            }
        }
        // Foo { a, b, c }
        PatKind::Struct(_, ref pat_fields, _) => {
            for field in pat_fields {
                let did = v.field_named(field.node.name).did;
                maybe_do_stability_check(tcx, did, field.span, cb);
            }
        }
        // everything else is fine.
        _ => {}
    }
}

fn maybe_do_stability_check(tcx: &TyCtxt, id: DefId, span: Span,
                            cb: &mut FnMut(DefId, Span,
                                           &Option<&Stability>, &Option<Deprecation>)) {
    if is_internal(tcx, span) {
        debug!("maybe_do_stability_check: \
                skipping span={:?} since it is internal", span);
        return;
    }
    let (stability, deprecation) = if is_staged_api(tcx, id) {
        (lookup_stability(tcx, id), None)
    } else {
        (None, lookup_deprecation(tcx, id))
    };
    debug!("maybe_do_stability_check: \
            inspecting id={:?} span={:?} of stability={:?}", id, span, stability);
    cb(id, span, &stability, &deprecation);
}

fn is_internal(tcx: &TyCtxt, span: Span) -> bool {
    tcx.sess.codemap().span_allows_unstable(span)
}

fn is_staged_api(tcx: &TyCtxt, id: DefId) -> bool {
    match tcx.trait_item_of_item(id) {
        Some(ty::MethodTraitItemId(trait_method_id))
            if trait_method_id != id => {
                is_staged_api(tcx, trait_method_id)
            }
        _ => {
            *tcx.stability.borrow_mut().staged_api.entry(id.krate).or_insert_with(
                || tcx.sess.cstore.is_staged_api(id.krate))
        }
    }
}

/// Lookup the stability for a node, loading external crate
/// metadata as necessary.
pub fn lookup_stability<'tcx>(tcx: &TyCtxt<'tcx>, id: DefId) -> Option<&'tcx Stability> {
    if let Some(st) = tcx.stability.borrow().stab_map.get(&id) {
        return *st;
    }

    let st = lookup_stability_uncached(tcx, id);
    tcx.stability.borrow_mut().stab_map.insert(id, st);
    st
}

pub fn lookup_deprecation<'tcx>(tcx: &TyCtxt<'tcx>, id: DefId) -> Option<Deprecation> {
    if let Some(depr) = tcx.stability.borrow().depr_map.get(&id) {
        return depr.clone();
    }

    let depr = lookup_deprecation_uncached(tcx, id);
    tcx.stability.borrow_mut().depr_map.insert(id, depr.clone());
    depr
}

fn lookup_stability_uncached<'tcx>(tcx: &TyCtxt<'tcx>, id: DefId) -> Option<&'tcx Stability> {
    debug!("lookup(id={:?})", id);
    if id.is_local() {
        None // The stability cache is filled partially lazily
    } else {
        tcx.sess.cstore.stability(id).map(|st| tcx.intern_stability(st))
    }
}

fn lookup_deprecation_uncached<'tcx>(tcx: &TyCtxt<'tcx>, id: DefId) -> Option<Deprecation> {
    debug!("lookup(id={:?})", id);
    if id.is_local() {
        None // The stability cache is filled partially lazily
    } else {
        tcx.sess.cstore.deprecation(id)
    }
}

/// 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,
                                                                      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 {
        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 {
        match remaining_lib_features.remove(used_lib_feature) {
            Some(span) => {
                if *level == 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.values() {
        sess.add_lint(lint::builtin::UNUSED_FEATURES,
                      ast::CRATE_NODE_ID,
                      span,
                      "unused or unknown feature".to_string());
    }
}
