cairo_lang_semantic/items/
feature_kind.rs1use cairo_lang_defs::ids::{LanguageElementId, ModuleId};
2use cairo_lang_filesystem::db::{FilesGroup, default_crate_settings};
3use cairo_lang_filesystem::ids::{CrateId, SmolStrId};
4use cairo_lang_syntax::attribute::consts::{
5 ALLOW_ATTR, DEPRECATED_ATTR, FEATURE_ATTR, INTERNAL_ATTR, UNSTABLE_ATTR, UNUSED,
6 UNUSED_IMPORTS, UNUSED_VARIABLES,
7};
8use cairo_lang_syntax::attribute::structured::{
9 self, AttributeArg, AttributeArgVariant, AttributeStructurize,
10};
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 itertools::Itertools;
16use salsa::Database;
17
18use crate::db::SemanticGroup;
19use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder};
20
21#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
23pub enum FeatureKind<'db> {
24 Stable,
26 Unstable { feature: SmolStrId<'db>, note: Option<SmolStrId<'db>> },
28 Deprecated { feature: SmolStrId<'db>, note: Option<SmolStrId<'db>> },
31 Internal { feature: SmolStrId<'db>, note: Option<SmolStrId<'db>> },
33}
34impl<'db> FeatureKind<'db> {
35 pub fn from_ast(
36 db: &'db dyn Database,
37 diagnostics: &mut SemanticDiagnostics<'db>,
38 attrs: &ast::AttributeList<'db>,
39 ) -> Self {
40 let unstable_attrs = attrs.query_attr(db, UNSTABLE_ATTR).collect_vec();
41 let deprecated_attrs = attrs.query_attr(db, DEPRECATED_ATTR).collect_vec();
42 let internal_attrs = attrs.query_attr(db, INTERNAL_ATTR).collect_vec();
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(db), 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
74pub trait HasFeatureKind<'db> {
76 fn feature_kind(&self) -> &FeatureKind<'db>;
78}
79
80#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
82pub enum FeatureMarkerDiagnostic {
83 MultipleMarkers,
85 MissingAllowFeature,
87 UnsupportedArgument,
89 DuplicatedArgument,
91}
92
93fn parse_feature_attr<'db, const EXTRA_ALLOWED: usize>(
95 db: &'db dyn Database,
96 diagnostics: &mut SemanticDiagnostics<'db>,
97 attr: &structured::Attribute<'db>,
98 allowed_args: [&str; EXTRA_ALLOWED],
99) -> [Option<SmolStrId<'db>>; EXTRA_ALLOWED] {
100 let mut arg_values = std::array::from_fn(|_| None);
101 for AttributeArg { variant, arg, .. } in &attr.args {
102 let AttributeArgVariant::Named { value: ast::Expr::String(value), name } = variant else {
103 add_diag(diagnostics, arg.stable_ptr(db), FeatureMarkerDiagnostic::UnsupportedArgument);
104 continue;
105 };
106 let Some(i) = allowed_args.iter().position(|x| *x == name.text.long(db)) else {
107 add_diag(diagnostics, name.stable_ptr, FeatureMarkerDiagnostic::UnsupportedArgument);
108 continue;
109 };
110 if arg_values[i].is_some() {
111 add_diag(diagnostics, name.stable_ptr, FeatureMarkerDiagnostic::DuplicatedArgument);
112 } else {
113 arg_values[i] = Some(value.text(db));
114 }
115 }
116 arg_values
117}
118
119fn add_diag<'db>(
121 diagnostics: &mut SemanticDiagnostics<'db>,
122 stable_ptr: impl TypedStablePtr<'db>,
123 diagnostic: FeatureMarkerDiagnostic,
124) {
125 diagnostics
126 .report(stable_ptr.untyped(), SemanticDiagnosticKind::FeatureMarkerDiagnostic(diagnostic));
127}
128
129#[derive(Clone, Debug, Default, PartialEq, Eq, salsa::Update)]
132pub struct FeatureConfig<'db> {
133 pub allowed_features: OrderedHashSet<SmolStrId<'db>>,
135 pub allowed_lints: OrderedHashSet<SmolStrId<'db>>,
137}
138
139impl<'db> FeatureConfig<'db> {
140 pub fn override_with(&mut self, other: Self) -> FeatureConfigRestore<'db> {
144 let mut restore = FeatureConfigRestore::empty();
145 for feature_name in other.allowed_features {
146 if self.allowed_features.insert(feature_name) {
147 restore.features_to_remove.push(feature_name);
148 }
149 }
150 for allow_lint in other.allowed_lints {
151 if self.allowed_lints.insert(allow_lint) {
152 restore.allowed_lints_to_remove.push(allow_lint);
153 }
154 }
155 restore
156 }
157
158 pub fn restore(&mut self, restore: FeatureConfigRestore<'db>) {
160 for feature_name in restore.features_to_remove {
161 self.allowed_features.swap_remove(&feature_name);
162 }
163 for allow_lint in restore.allowed_lints_to_remove {
164 self.allowed_lints.swap_remove(&allow_lint);
165 }
166 }
167}
168
169#[derive(Debug, Default, PartialEq, Eq)]
171pub struct FeatureConfigRestore<'db> {
172 features_to_remove: Vec<SmolStrId<'db>>,
174 allowed_lints_to_remove: Vec<SmolStrId<'db>>,
176}
177
178impl FeatureConfigRestore<'_> {
179 pub fn empty() -> Self {
180 Self::default()
181 }
182}
183
184pub fn feature_config_from_ast_item<'db>(
186 db: &'db dyn Database,
187 crate_id: CrateId<'db>,
188 syntax: &impl QueryAttrs<'db>,
189 diagnostics: &mut SemanticDiagnostics<'db>,
190) -> FeatureConfig<'db> {
191 let mut config = FeatureConfig::default();
192 process_feature_attr_kind(
193 db,
194 syntax,
195 FEATURE_ATTR,
196 || SemanticDiagnosticKind::UnsupportedFeatureAttrArguments,
197 diagnostics,
198 |value| {
199 if let ast::Expr::String(value) = value {
200 config.allowed_features.insert(value.text(db));
201 true
202 } else {
203 false
204 }
205 },
206 );
207 process_feature_attr_kind(
208 db,
209 syntax,
210 ALLOW_ATTR,
211 || SemanticDiagnosticKind::UnsupportedAllowAttrArguments,
212 diagnostics,
213 |value| {
214 let allowed = value.as_syntax_node().get_text_without_trivia(db);
215 if allowed.long(db) == UNUSED {
217 let all_unused_lints = [UNUSED_VARIABLES, UNUSED_IMPORTS];
218 return all_unused_lints.iter().all(|&lint| {
219 let _already_allowed = config.allowed_lints.insert(SmolStrId::from(db, lint));
220 db.declared_allows(crate_id).contains(lint)
221 });
222 }
223
224 let _already_allowed = config.allowed_lints.insert(allowed);
225 db.declared_allows(crate_id).contains(allowed.long(db).as_str())
226 },
227 );
228 config
229}
230
231fn process_feature_attr_kind<'db>(
233 db: &'db dyn Database,
234 syntax: &impl QueryAttrs<'db>,
235 attr: &'db str,
236 diagnostic_kind: impl Fn() -> SemanticDiagnosticKind<'db>,
237 diagnostics: &mut SemanticDiagnostics<'db>,
238 mut process: impl FnMut(&ast::Expr<'db>) -> bool,
239) {
240 for attr_syntax in syntax.query_attr(db, attr) {
241 let attr = attr_syntax.structurize(db);
242 let success = (|| {
243 let [arg] = &attr.args[..] else {
244 return None;
245 };
246 let value = try_extract_matches!(&arg.variant, AttributeArgVariant::Unnamed)?;
247 process(value).then_some(())
248 })()
249 .is_none();
250 if success {
251 diagnostics.report(attr.args_stable_ptr.untyped(), diagnostic_kind());
252 }
253 }
254}
255
256pub fn feature_config_from_item_and_parent_modules<'db>(
259 db: &'db dyn Database,
260 element_id: &impl LanguageElementId<'db>,
261 syntax: &impl QueryAttrs<'db>,
262 diagnostics: &mut SemanticDiagnostics<'db>,
263) -> FeatureConfig<'db> {
264 let mut current_module_id = element_id.parent_module(db);
265 let crate_id = current_module_id.owning_crate(db);
266 let mut config_stack = vec![feature_config_from_ast_item(db, crate_id, syntax, diagnostics)];
267 let mut config = loop {
268 match current_module_id {
269 ModuleId::CrateRoot(crate_id) => {
270 let all_declarations_pub = db
271 .crate_config(crate_id)
272 .map(|config| config.settings.edition.ignore_visibility())
273 .unwrap_or_else(|| default_crate_settings(db).edition.ignore_visibility());
274 let mut allowed_lints = OrderedHashSet::default();
275 if all_declarations_pub {
276 let _already_allowed =
277 allowed_lints.insert(SmolStrId::from(db, UNUSED_IMPORTS));
278 }
279 break FeatureConfig { allowed_features: OrderedHashSet::default(), allowed_lints };
280 }
281 ModuleId::Submodule(id) => {
282 current_module_id = id.parent_module(db);
283 let module = ¤t_module_id.module_data(db).unwrap().submodules(db)[&id];
284 let ignored = &mut SemanticDiagnostics::new(current_module_id);
286 config_stack.push(feature_config_from_ast_item(db, crate_id, module, ignored));
287 }
288 ModuleId::MacroCall { id, .. } => {
289 current_module_id = id.parent_module(db);
290 }
291 }
292 };
293 for module_config in config_stack.into_iter().rev() {
294 config.override_with(module_config);
295 }
296 config
297}