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