1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Detecting lib features (i.e., features that are not lang features).
//!
//! These are declared using stability attributes (e.g., `#[stable (..)]` and `#[unstable (..)]`),
//! but are not declared in one single location (unlike lang features), which means we need to
//! collect them instead.

use rustc_ast::{Attribute, MetaItemKind};
use rustc_attr::{rust_version_symbol, VERSION_PLACEHOLDER};
use rustc_hir::intravisit::Visitor;
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::lib_features::LibFeatures;
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_span::symbol::Symbol;
use rustc_span::{sym, Span};

use crate::errors::{FeaturePreviouslyDeclared, FeatureStableTwice};

fn new_lib_features() -> LibFeatures {
    LibFeatures { stable: Default::default(), unstable: Default::default() }
}

pub struct LibFeatureCollector<'tcx> {
    tcx: TyCtxt<'tcx>,
    lib_features: LibFeatures,
}

impl<'tcx> LibFeatureCollector<'tcx> {
    fn new(tcx: TyCtxt<'tcx>) -> LibFeatureCollector<'tcx> {
        LibFeatureCollector { tcx, lib_features: new_lib_features() }
    }

    fn extract(&self, attr: &Attribute) -> Option<(Symbol, Option<Symbol>, Span)> {
        let stab_attrs = [
            sym::stable,
            sym::unstable,
            sym::rustc_const_stable,
            sym::rustc_const_unstable,
            sym::rustc_default_body_unstable,
        ];

        // Find a stability attribute: one of #[stable(…)], #[unstable(…)],
        // #[rustc_const_stable(…)], #[rustc_const_unstable(…)] or #[rustc_default_body_unstable].
        if let Some(stab_attr) = stab_attrs.iter().find(|stab_attr| attr.has_name(**stab_attr)) {
            let meta_kind = attr.meta_kind();
            if let Some(MetaItemKind::List(ref metas)) = meta_kind {
                let mut feature = None;
                let mut since = None;
                for meta in metas {
                    if let Some(mi) = meta.meta_item() {
                        // Find the `feature = ".."` meta-item.
                        match (mi.name_or_empty(), mi.value_str()) {
                            (sym::feature, val) => feature = val,
                            (sym::since, val) => since = val,
                            _ => {}
                        }
                    }
                }

                if let Some(s) = since && s.as_str() == VERSION_PLACEHOLDER {
                    since = Some(rust_version_symbol());
                }

                if let Some(feature) = feature {
                    // This additional check for stability is to make sure we
                    // don't emit additional, irrelevant errors for malformed
                    // attributes.
                    let is_unstable = matches!(
                        *stab_attr,
                        sym::unstable
                            | sym::rustc_const_unstable
                            | sym::rustc_default_body_unstable
                    );
                    if since.is_some() || is_unstable {
                        return Some((feature, since, attr.span));
                    }
                }
                // We need to iterate over the other attributes, because
                // `rustc_const_unstable` is not mutually exclusive with
                // the other stability attributes, so we can't just `break`
                // here.
            }
        }

        None
    }

    fn collect_feature(&mut self, feature: Symbol, since: Option<Symbol>, span: Span) {
        let already_in_stable = self.lib_features.stable.contains_key(&feature);
        let already_in_unstable = self.lib_features.unstable.contains_key(&feature);

        match (since, already_in_stable, already_in_unstable) {
            (Some(since), _, false) => {
                if let Some((prev_since, _)) = self.lib_features.stable.get(&feature) {
                    if *prev_since != since {
                        self.tcx.sess.emit_err(FeatureStableTwice {
                            span,
                            feature,
                            since,
                            prev_since: *prev_since,
                        });
                        return;
                    }
                }

                self.lib_features.stable.insert(feature, (since, span));
            }
            (None, false, _) => {
                self.lib_features.unstable.insert(feature, span);
            }
            (Some(_), _, true) | (None, true, _) => {
                let declared = if since.is_some() { "stable" } else { "unstable" };
                let prev_declared = if since.is_none() { "stable" } else { "unstable" };
                self.tcx.sess.emit_err(FeaturePreviouslyDeclared {
                    span,
                    feature,
                    declared,
                    prev_declared,
                });
            }
        }
    }
}

impl<'tcx> Visitor<'tcx> for LibFeatureCollector<'tcx> {
    type NestedFilter = nested_filter::All;

    fn nested_visit_map(&mut self) -> Self::Map {
        self.tcx.hir()
    }

    fn visit_attribute(&mut self, attr: &'tcx Attribute) {
        if let Some((feature, stable, span)) = self.extract(attr) {
            self.collect_feature(feature, stable, span);
        }
    }
}

fn lib_features(tcx: TyCtxt<'_>, (): ()) -> LibFeatures {
    let mut collector = LibFeatureCollector::new(tcx);
    tcx.hir().walk_attributes(&mut collector);
    collector.lib_features
}

pub fn provide(providers: &mut Providers) {
    providers.lib_features = lib_features;
}