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_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#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum FeatureKind {
26 Stable,
28 Unstable { feature: SmolStr, note: Option<SmolStr> },
30 Deprecated { feature: SmolStr, note: Option<SmolStr> },
33 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
76pub trait HasFeatureKind {
78 fn feature_kind(&self) -> &FeatureKind;
80}
81
82#[derive(Clone, Debug, Eq, Hash, PartialEq)]
84pub enum FeatureMarkerDiagnostic {
85 MultipleMarkers,
87 MissingAllowFeature,
89 UnsupportedArgument,
91 DuplicatedArgument,
93}
94
95fn 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
125fn 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#[derive(Clone, Debug, Default, PartialEq, Eq)]
140pub struct FeatureConfig {
141 pub allowed_features: OrderedHashSet<SmolStr>,
143 pub allow_deprecated: bool,
145 pub allow_unused_imports: bool,
147}
148
149impl FeatureConfig {
150 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 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
179pub struct FeatureConfigRestore {
181 features_to_remove: Vec<SmolStr>,
183 allow_deprecated: bool,
185 allow_unused_imports: bool,
187}
188
189pub 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
233fn 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
258pub 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 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}