Skip to main content

cairo_lang_semantic/items/
feature_kind.rs

1use cairo_lang_defs::diagnostic_utils::StableLocation;
2use cairo_lang_defs::ids::{LanguageElementId, ModuleId};
3use cairo_lang_diagnostics::DiagnosticsBuilder;
4use cairo_lang_filesystem::ids::CrateId;
5use cairo_lang_syntax::attribute::consts::{
6    ALLOW_ATTR, DEPRECATED_ATTR, FEATURE_ATTR, INTERNAL_ATTR, UNSTABLE_ATTR,
7};
8use cairo_lang_syntax::attribute::structured::{
9    self, AttributeArg, AttributeArgVariant, AttributeStructurize,
10};
11use cairo_lang_syntax::node::db::SyntaxGroup;
12use cairo_lang_syntax::node::helpers::QueryAttrs;
13use cairo_lang_syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode, ast};
14use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
15use cairo_lang_utils::try_extract_matches;
16use itertools::Itertools;
17use smol_str::SmolStr;
18
19use crate::SemanticDiagnostic;
20use crate::db::SemanticGroup;
21use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder};
22
23/// The kind of a feature for an item.
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum FeatureKind {
26    /// The feature of the item is stable.
27    Stable,
28    /// The feature of the item is unstable, with the given name to allow.
29    Unstable { feature: SmolStr, note: Option<SmolStr> },
30    /// The feature of the item is deprecated, with the given name to allow, and an optional note
31    /// to appear in diagnostics.
32    Deprecated { feature: SmolStr, note: Option<SmolStr> },
33    /// This feature is for internal corelib use only. Using it in user code is not advised.
34    Internal { feature: SmolStr, note: Option<SmolStr> },
35}
36impl FeatureKind {
37    pub fn from_ast(
38        db: &dyn SyntaxGroup,
39        diagnostics: &mut DiagnosticsBuilder<SemanticDiagnostic>,
40        attrs: &ast::AttributeList,
41    ) -> Self {
42        let unstable_attrs = attrs.query_attr(db, UNSTABLE_ATTR).collect_vec();
43        let deprecated_attrs = attrs.query_attr(db, DEPRECATED_ATTR).collect_vec();
44        let internal_attrs = attrs.query_attr(db, INTERNAL_ATTR).collect_vec();
45        if unstable_attrs.is_empty() && deprecated_attrs.is_empty() && internal_attrs.is_empty() {
46            return Self::Stable;
47        };
48        if unstable_attrs.len() + deprecated_attrs.len() + internal_attrs.len() > 1 {
49            add_diag(diagnostics, &attrs.stable_ptr(db), FeatureMarkerDiagnostic::MultipleMarkers);
50            return Self::Stable;
51        }
52
53        if !unstable_attrs.is_empty() {
54            let attr = unstable_attrs.into_iter().next().unwrap().structurize(db);
55            let [feature, note, _] =
56                parse_feature_attr(db, diagnostics, &attr, ["feature", "note", "since"]);
57            feature.map(|feature| Self::Unstable { feature, note }).ok_or(attr)
58        } else if !deprecated_attrs.is_empty() {
59            let attr = deprecated_attrs.into_iter().next().unwrap().structurize(db);
60            let [feature, note, _] =
61                parse_feature_attr(db, diagnostics, &attr, ["feature", "note", "since"]);
62            feature.map(|feature| Self::Deprecated { feature, note }).ok_or(attr)
63        } else {
64            let attr = internal_attrs.into_iter().next().unwrap().structurize(db);
65            let [feature, note, _] =
66                parse_feature_attr(db, diagnostics, &attr, ["feature", "note", "since"]);
67            feature.map(|feature| Self::Internal { feature, note }).ok_or(attr)
68        }
69        .unwrap_or_else(|attr| {
70            add_diag(diagnostics, &attr.stable_ptr, FeatureMarkerDiagnostic::MissingAllowFeature);
71            Self::Stable
72        })
73    }
74}
75
76/// A trait for retrieving the `FeatureKind` of an item.
77pub trait HasFeatureKind {
78    /// Returns the `FeatureKind` associated with the implementing struct.
79    fn feature_kind(&self) -> &FeatureKind;
80}
81
82/// Diagnostics for feature markers.
83#[derive(Clone, Debug, Eq, Hash, PartialEq)]
84pub enum FeatureMarkerDiagnostic {
85    /// Multiple markers on the same item.
86    MultipleMarkers,
87    /// Every marker must have a feature argument, to allow ignoring the warning.
88    MissingAllowFeature,
89    /// Unsupported argument in the feature marker attribute.
90    UnsupportedArgument,
91    /// Duplicated argument in the feature marker attribute.
92    DuplicatedArgument,
93}
94
95/// Parses the feature attribute.
96fn parse_feature_attr<const EXTRA_ALLOWED: usize>(
97    db: &dyn SyntaxGroup,
98    diagnostics: &mut DiagnosticsBuilder<SemanticDiagnostic>,
99    attr: &structured::Attribute,
100    allowed_args: [&str; EXTRA_ALLOWED],
101) -> [Option<SmolStr>; EXTRA_ALLOWED] {
102    let mut arg_values = std::array::from_fn(|_| None);
103    for AttributeArg { variant, arg, .. } in &attr.args {
104        let AttributeArgVariant::Named { value: ast::Expr::String(value), name } = variant else {
105            add_diag(
106                diagnostics,
107                &arg.stable_ptr(db),
108                FeatureMarkerDiagnostic::UnsupportedArgument,
109            );
110            continue;
111        };
112        let Some(i) = allowed_args.iter().position(|x| x == &name.text.as_str()) else {
113            add_diag(diagnostics, &name.stable_ptr, FeatureMarkerDiagnostic::UnsupportedArgument);
114            continue;
115        };
116        if arg_values[i].is_some() {
117            add_diag(diagnostics, &name.stable_ptr, FeatureMarkerDiagnostic::DuplicatedArgument);
118        } else {
119            arg_values[i] = Some(value.text(db));
120        }
121    }
122    arg_values
123}
124
125/// Helper for adding a marker diagnostic.
126fn add_diag(
127    diagnostics: &mut DiagnosticsBuilder<SemanticDiagnostic>,
128    stable_ptr: &impl TypedStablePtr,
129    diagnostic: FeatureMarkerDiagnostic,
130) {
131    diagnostics.add(SemanticDiagnostic::new(
132        StableLocation::new(stable_ptr.untyped()),
133        SemanticDiagnosticKind::FeatureMarkerDiagnostic(diagnostic),
134    ));
135}
136
137/// The feature configuration on an item.
138/// May be accumulated, or overridden by inner items.
139#[derive(Clone, Debug, Default, PartialEq, Eq)]
140pub struct FeatureConfig {
141    /// The current set of allowed features.
142    pub allowed_features: OrderedHashSet<SmolStr>,
143    /// Whether to allow all deprecated features.
144    pub allow_deprecated: bool,
145    /// Whether to allow unused imports.
146    pub allow_unused_imports: bool,
147}
148
149impl FeatureConfig {
150    /// Overrides the current configuration with another one.
151    ///
152    /// Returns the data required to restore the configuration.
153    pub fn override_with(&mut self, other: Self) -> FeatureConfigRestore {
154        let mut restore = FeatureConfigRestore {
155            features_to_remove: vec![],
156            allow_deprecated: self.allow_deprecated,
157            allow_unused_imports: self.allow_unused_imports,
158        };
159        for feature_name in other.allowed_features {
160            if self.allowed_features.insert(feature_name.clone()) {
161                restore.features_to_remove.push(feature_name);
162            }
163        }
164        self.allow_deprecated |= other.allow_deprecated;
165        self.allow_unused_imports |= other.allow_unused_imports;
166        restore
167    }
168
169    /// Restores the configuration to a previous state.
170    pub fn restore(&mut self, restore: FeatureConfigRestore) {
171        for feature_name in restore.features_to_remove {
172            self.allowed_features.swap_remove(&feature_name);
173        }
174        self.allow_deprecated = restore.allow_deprecated;
175        self.allow_unused_imports = restore.allow_unused_imports;
176    }
177}
178
179/// The data required to restore the feature configuration after an override.
180pub struct FeatureConfigRestore {
181    /// The features to remove from the configuration after the override.
182    features_to_remove: Vec<SmolStr>,
183    /// The previous state of the allow deprecated flag.
184    allow_deprecated: bool,
185    /// The previous state of the allow unused imports flag.
186    allow_unused_imports: bool,
187}
188
189/// Returns the allowed features of an object which supports attributes.
190pub fn extract_item_feature_config(
191    db: &dyn SemanticGroup,
192    crate_id: CrateId,
193    syntax: &impl QueryAttrs,
194    diagnostics: &mut SemanticDiagnostics,
195) -> FeatureConfig {
196    let mut config = FeatureConfig::default();
197    process_feature_attr_kind(
198        db,
199        syntax,
200        FEATURE_ATTR,
201        || SemanticDiagnosticKind::UnsupportedFeatureAttrArguments,
202        diagnostics,
203        |value| {
204            if let ast::Expr::String(value) = value {
205                config.allowed_features.insert(value.text(db));
206                true
207            } else {
208                false
209            }
210        },
211    );
212    process_feature_attr_kind(
213        db,
214        syntax,
215        ALLOW_ATTR,
216        || SemanticDiagnosticKind::UnsupportedAllowAttrArguments,
217        diagnostics,
218        |value| match value.as_syntax_node().get_text_without_trivia(db).as_str() {
219            "deprecated" => {
220                config.allow_deprecated = true;
221                true
222            }
223            "unused_imports" => {
224                config.allow_unused_imports = true;
225                true
226            }
227            other => db.declared_allows(crate_id).contains(other),
228        },
229    );
230    config
231}
232
233/// Processes the feature attribute kind.
234fn process_feature_attr_kind(
235    db: &dyn SyntaxGroup,
236    syntax: &impl QueryAttrs,
237    attr: &str,
238    diagnostic_kind: impl Fn() -> SemanticDiagnosticKind,
239    diagnostics: &mut SemanticDiagnostics,
240    mut process: impl FnMut(&ast::Expr) -> bool,
241) {
242    for attr_syntax in syntax.query_attr(db, attr) {
243        let attr = attr_syntax.structurize(db);
244        let success = (|| {
245            let [arg] = &attr.args[..] else {
246                return None;
247            };
248            let value = try_extract_matches!(&arg.variant, AttributeArgVariant::Unnamed)?;
249            process(value).then_some(())
250        })()
251        .is_none();
252        if success {
253            diagnostics.report(attr.args_stable_ptr.untyped(), diagnostic_kind());
254        }
255    }
256}
257
258/// Extracts the allowed features of an element, considering its parent modules as well as its
259/// attributes.
260pub fn extract_feature_config(
261    db: &dyn SemanticGroup,
262    element_id: &impl LanguageElementId,
263    syntax: &impl QueryAttrs,
264    diagnostics: &mut SemanticDiagnostics,
265) -> FeatureConfig {
266    let defs_db = db;
267    let mut current_module_id = element_id.parent_module(defs_db);
268    let crate_id = current_module_id.owning_crate(defs_db);
269    let mut config_stack = vec![extract_item_feature_config(db, crate_id, syntax, diagnostics)];
270    let mut config = loop {
271        match current_module_id {
272            ModuleId::CrateRoot(crate_id) => {
273                let settings =
274                    db.crate_config(crate_id).map(|config| config.settings).unwrap_or_default();
275                break FeatureConfig {
276                    allowed_features: OrderedHashSet::default(),
277                    allow_deprecated: false,
278                    allow_unused_imports: settings.edition.ignore_visibility(),
279                };
280            }
281            ModuleId::Submodule(id) => {
282                current_module_id = id.parent_module(defs_db);
283                let module = &db.module_submodules(current_module_id).unwrap()[&id];
284                // TODO(orizi): Add parent module diagnostics.
285                let ignored = &mut SemanticDiagnostics::default();
286                config_stack.push(extract_item_feature_config(db, crate_id, module, ignored));
287            }
288        }
289    };
290    for module_config in config_stack.into_iter().rev() {
291        config.override_with(module_config);
292    }
293    config
294}