cairo_lang_semantic/items/
feature_kind.rs1use cairo_lang_defs::diagnostic_utils::StableLocation;
2use cairo_lang_defs::ids::{LanguageElementId, ModuleId};
3use cairo_lang_diagnostics::DiagnosticsBuilder;
4use cairo_lang_syntax::attribute::consts::{
5 ALLOW_ATTR, DEPRECATED_ATTR, FEATURE_ATTR, INTERNAL_ATTR, UNSTABLE_ATTR,
6};
7use cairo_lang_syntax::attribute::structured::{
8 self, AttributeArg, AttributeArgVariant, AttributeStructurize,
9};
10use cairo_lang_syntax::node::db::SyntaxGroup;
11use cairo_lang_syntax::node::helpers::QueryAttrs;
12use cairo_lang_syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode, ast};
13use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
14use cairo_lang_utils::try_extract_matches;
15use smol_str::SmolStr;
16
17use crate::SemanticDiagnostic;
18use crate::db::SemanticGroup;
19use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder};
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum FeatureKind {
24 Stable,
26 Unstable { feature: SmolStr, note: Option<SmolStr> },
28 Deprecated { feature: SmolStr, note: Option<SmolStr> },
31 Internal { feature: SmolStr, note: Option<SmolStr> },
33}
34impl FeatureKind {
35 pub fn from_ast(
36 db: &dyn SyntaxGroup,
37 diagnostics: &mut DiagnosticsBuilder<SemanticDiagnostic>,
38 attrs: &ast::AttributeList,
39 ) -> Self {
40 let unstable_attrs = attrs.query_attr(db, UNSTABLE_ATTR);
41 let deprecated_attrs = attrs.query_attr(db, DEPRECATED_ATTR);
42 let internal_attrs = attrs.query_attr(db, INTERNAL_ATTR);
43 if unstable_attrs.is_empty() && deprecated_attrs.is_empty() && internal_attrs.is_empty() {
44 return Self::Stable;
45 };
46 if unstable_attrs.len() + deprecated_attrs.len() + internal_attrs.len() > 1 {
47 add_diag(diagnostics, &attrs.stable_ptr(), FeatureMarkerDiagnostic::MultipleMarkers);
48 return Self::Stable;
49 }
50
51 if !unstable_attrs.is_empty() {
52 let attr = unstable_attrs.into_iter().next().unwrap().structurize(db);
53 let [feature, note, _] =
54 parse_feature_attr(db, diagnostics, &attr, ["feature", "note", "since"]);
55 feature.map(|feature| Self::Unstable { feature, note }).ok_or(attr)
56 } else if !deprecated_attrs.is_empty() {
57 let attr = deprecated_attrs.into_iter().next().unwrap().structurize(db);
58 let [feature, note, _] =
59 parse_feature_attr(db, diagnostics, &attr, ["feature", "note", "since"]);
60 feature.map(|feature| Self::Deprecated { feature, note }).ok_or(attr)
61 } else {
62 let attr = internal_attrs.into_iter().next().unwrap().structurize(db);
63 let [feature, note, _] =
64 parse_feature_attr(db, diagnostics, &attr, ["feature", "note", "since"]);
65 feature.map(|feature| Self::Internal { feature, note }).ok_or(attr)
66 }
67 .unwrap_or_else(|attr| {
68 add_diag(diagnostics, &attr.stable_ptr, FeatureMarkerDiagnostic::MissingAllowFeature);
69 Self::Stable
70 })
71 }
72}
73
74#[derive(Clone, Debug, Eq, Hash, PartialEq)]
76pub enum FeatureMarkerDiagnostic {
77 MultipleMarkers,
79 MissingAllowFeature,
81 UnsupportedArgument,
83 DuplicatedArgument,
85}
86
87fn parse_feature_attr<const EXTRA_ALLOWED: usize>(
89 db: &dyn SyntaxGroup,
90 diagnostics: &mut DiagnosticsBuilder<SemanticDiagnostic>,
91 attr: &structured::Attribute,
92 allowed_args: [&str; EXTRA_ALLOWED],
93) -> [Option<SmolStr>; EXTRA_ALLOWED] {
94 let mut arg_values = std::array::from_fn(|_| None);
95 for AttributeArg { variant, arg, .. } in &attr.args {
96 let AttributeArgVariant::Named { value: ast::Expr::String(value), name } = variant else {
97 add_diag(diagnostics, &arg.stable_ptr(), FeatureMarkerDiagnostic::UnsupportedArgument);
98 continue;
99 };
100 let Some(i) = allowed_args.iter().position(|x| x == &name.text.as_str()) else {
101 add_diag(diagnostics, &name.stable_ptr, FeatureMarkerDiagnostic::UnsupportedArgument);
102 continue;
103 };
104 if arg_values[i].is_some() {
105 add_diag(diagnostics, &name.stable_ptr, FeatureMarkerDiagnostic::DuplicatedArgument);
106 } else {
107 arg_values[i] = Some(value.text(db));
108 }
109 }
110 arg_values
111}
112
113fn add_diag(
115 diagnostics: &mut DiagnosticsBuilder<SemanticDiagnostic>,
116 stable_ptr: &impl TypedStablePtr,
117 diagnostic: FeatureMarkerDiagnostic,
118) {
119 diagnostics.add(SemanticDiagnostic::new(
120 StableLocation::new(stable_ptr.untyped()),
121 SemanticDiagnosticKind::FeatureMarkerDiagnostic(diagnostic),
122 ));
123}
124
125#[derive(Clone, Debug, Default, PartialEq, Eq)]
128pub struct FeatureConfig {
129 pub allowed_features: OrderedHashSet<SmolStr>,
131 pub allow_deprecated: bool,
133 pub allow_unused_imports: bool,
135}
136
137impl FeatureConfig {
138 pub fn override_with(&mut self, other: Self) -> FeatureConfigRestore {
142 let mut restore = FeatureConfigRestore {
143 features_to_remove: vec![],
144 allow_deprecated: self.allow_deprecated,
145 allow_unused_imports: self.allow_unused_imports,
146 };
147 for feature_name in other.allowed_features {
148 if self.allowed_features.insert(feature_name.clone()) {
149 restore.features_to_remove.push(feature_name);
150 }
151 }
152 self.allow_deprecated |= other.allow_deprecated;
153 self.allow_unused_imports |= other.allow_unused_imports;
154 restore
155 }
156
157 pub fn restore(&mut self, restore: FeatureConfigRestore) {
159 for feature_name in restore.features_to_remove {
160 self.allowed_features.swap_remove(&feature_name);
161 }
162 self.allow_deprecated = restore.allow_deprecated;
163 self.allow_unused_imports = restore.allow_unused_imports;
164 }
165}
166
167pub struct FeatureConfigRestore {
169 features_to_remove: Vec<SmolStr>,
171 allow_deprecated: bool,
173 allow_unused_imports: bool,
175}
176
177pub fn extract_item_feature_config(
179 db: &dyn SemanticGroup,
180 syntax: &impl QueryAttrs,
181 diagnostics: &mut SemanticDiagnostics,
182) -> FeatureConfig {
183 let syntax_db = db.upcast();
184 let mut config = FeatureConfig::default();
185 process_feature_attr_kind(
186 syntax_db,
187 syntax,
188 FEATURE_ATTR,
189 || SemanticDiagnosticKind::UnsupportedFeatureAttrArguments,
190 diagnostics,
191 |value| {
192 if let ast::Expr::String(value) = value {
193 config.allowed_features.insert(value.text(syntax_db));
194 true
195 } else {
196 false
197 }
198 },
199 );
200 process_feature_attr_kind(
201 syntax_db,
202 syntax,
203 ALLOW_ATTR,
204 || SemanticDiagnosticKind::UnsupportedAllowAttrArguments,
205 diagnostics,
206 |value| match value.as_syntax_node().get_text_without_trivia(syntax_db).as_str() {
207 "deprecated" => {
208 config.allow_deprecated = true;
209 true
210 }
211 "unused_imports" => {
212 config.allow_unused_imports = true;
213 true
214 }
215 other => db.declared_allows().contains(other),
216 },
217 );
218 config
219}
220
221fn process_feature_attr_kind(
223 db: &dyn SyntaxGroup,
224 syntax: &impl QueryAttrs,
225 attr: &str,
226 diagnostic_kind: impl Fn() -> SemanticDiagnosticKind,
227 diagnostics: &mut SemanticDiagnostics,
228 mut process: impl FnMut(&ast::Expr) -> bool,
229) {
230 for attr_syntax in syntax.query_attr(db, attr) {
231 let attr = attr_syntax.structurize(db);
232 let success = (|| {
233 let [arg] = &attr.args[..] else {
234 return None;
235 };
236 let value = try_extract_matches!(&arg.variant, AttributeArgVariant::Unnamed)?;
237 process(value).then_some(())
238 })()
239 .is_none();
240 if success {
241 diagnostics.report(attr.args_stable_ptr.untyped(), diagnostic_kind());
242 }
243 }
244}
245
246pub fn extract_feature_config(
249 db: &dyn SemanticGroup,
250 element_id: &impl LanguageElementId,
251 syntax: &impl QueryAttrs,
252 diagnostics: &mut SemanticDiagnostics,
253) -> FeatureConfig {
254 let defs_db = db.upcast();
255 let mut current_module_id = element_id.parent_module(defs_db);
256 let mut config_stack = vec![extract_item_feature_config(db, syntax, diagnostics)];
257 let mut config = loop {
258 match current_module_id {
259 ModuleId::CrateRoot(crate_id) => {
260 let settings =
261 db.crate_config(crate_id).map(|config| config.settings).unwrap_or_default();
262 break FeatureConfig {
263 allowed_features: OrderedHashSet::default(),
264 allow_deprecated: false,
265 allow_unused_imports: settings.edition.ignore_visibility(),
266 };
267 }
268 ModuleId::Submodule(id) => {
269 current_module_id = id.parent_module(defs_db);
270 let module = &db.module_submodules(current_module_id).unwrap()[&id];
271 let ignored = &mut SemanticDiagnostics::default();
273 config_stack.push(extract_item_feature_config(db, module, ignored));
274 }
275 }
276 };
277 for module_config in config_stack.into_iter().rev() {
278 config.override_with(module_config);
279 }
280 config
281}