Skip to main content

cairo_lang_semantic/items/
enm.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5    EnumId, LanguageElementId, LookupItemId, ModuleItemId, VariantId, VariantLongId,
6};
7use cairo_lang_diagnostics::{Diagnostics, Maybe, MaybeAsRef, skip_diagnostic};
8use cairo_lang_filesystem::ids::SmolStrId;
9use cairo_lang_proc_macros::{DebugWithDb, HeapSize, SemanticObject};
10use cairo_lang_syntax::attribute::structured::{Attribute, AttributeListStructurize};
11use cairo_lang_syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode, ast};
12use cairo_lang_utils::Intern;
13use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
14use salsa::Database;
15
16use super::attribute::SemanticQueryAttrs;
17use super::generics::{GenericParamsData, semantic_generic_params};
18use crate::corelib::unit_ty;
19use crate::diagnostic::SemanticDiagnosticKind::*;
20use crate::diagnostic::{SemanticDiagnostics, SemanticDiagnosticsBuilder};
21use crate::expr::inference::InferenceId;
22use crate::expr::inference::canonic::ResultNoErrEx;
23use crate::resolve::{Resolver, ResolverData};
24use crate::substitution::{GenericSubstitution, SemanticRewriter};
25use crate::types::{add_type_based_diagnostics, resolve_type};
26use crate::{ConcreteEnumId, GenericParam, SemanticDiagnostic, semantic};
27
28#[cfg(test)]
29#[path = "enm_test.rs"]
30mod test;
31
32// Declaration
33#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
34#[debug_db(dyn Database)]
35struct EnumDeclarationData<'db> {
36    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
37    attributes: Vec<Attribute<'db>>,
38    resolver_data: Arc<ResolverData<'db>>,
39}
40
41/// Returns the declaration data of an enum.
42#[salsa::tracked(returns(ref))]
43fn enum_declaration_data<'db>(
44    db: &'db dyn Database,
45    enum_id: EnumId<'db>,
46) -> Maybe<EnumDeclarationData<'db>> {
47    let mut diagnostics = SemanticDiagnostics::new(enum_id.parent_module(db));
48    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
49    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
50    // the item instead of all the module data.
51    let enum_ast = db.module_enum_by_id(enum_id)?;
52
53    // Generic params.
54    let generic_params_data = enum_generic_params_data(db, enum_id).maybe_as_ref()?;
55    let inference_id =
56        InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(ModuleItemId::Enum(enum_id)));
57    let mut resolver = Resolver::with_data(
58        db,
59        (*generic_params_data.resolver_data).clone_with_inference_id(db, inference_id),
60    );
61    diagnostics.extend(generic_params_data.diagnostics.clone());
62    let attributes = enum_ast.attributes(db).structurize(db);
63
64    // Check fully resolved.
65    let inference = &mut resolver.inference();
66    inference.finalize(&mut diagnostics, enum_ast.stable_ptr(db).untyped());
67
68    let resolver_data = Arc::new(resolver.data);
69    Ok(EnumDeclarationData { diagnostics: diagnostics.build(), attributes, resolver_data })
70}
71
72/// Returns the generic parameters data of an enum.
73#[salsa::tracked(returns(ref))]
74fn enum_generic_params_data<'db>(
75    db: &'db dyn Database,
76    enum_id: EnumId<'db>,
77) -> Maybe<GenericParamsData<'db>> {
78    let module_id = enum_id.parent_module(db);
79    let mut diagnostics = SemanticDiagnostics::new(module_id);
80    let enum_ast = db.module_enum_by_id(enum_id)?;
81
82    // Generic params.
83    let inference_id =
84        InferenceId::LookupItemGenerics(LookupItemId::ModuleItem(ModuleItemId::Enum(enum_id)));
85    let mut resolver = Resolver::new(db, module_id, inference_id);
86    resolver.set_feature_config(&enum_id, &enum_ast, &mut diagnostics);
87    let generic_params = semantic_generic_params(
88        db,
89        &mut diagnostics,
90        &mut resolver,
91        module_id,
92        &enum_ast.generic_params(db),
93    );
94    let inference = &mut resolver.inference();
95    inference.finalize(&mut diagnostics, enum_ast.stable_ptr(db).untyped());
96
97    let generic_params = inference.rewrite(generic_params).no_err();
98    let resolver_data = Arc::new(resolver.data);
99    Ok(GenericParamsData { generic_params, diagnostics: diagnostics.build(), resolver_data })
100}
101
102#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
103#[debug_db(dyn Database)]
104struct EnumDefinitionData<'db> {
105    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
106    variants: OrderedHashMap<SmolStrId<'db>, VariantId<'db>>,
107    variant_semantic: OrderedHashMap<VariantId<'db>, Variant<'db>>,
108    resolver_data: Arc<ResolverData<'db>>,
109}
110
111#[derive(Clone, Debug, Hash, PartialEq, Eq, DebugWithDb, salsa::Update)]
112#[debug_db(dyn Database)]
113pub struct Variant<'db> {
114    pub enum_id: EnumId<'db>,
115    pub id: VariantId<'db>,
116    pub ty: semantic::TypeId<'db>,
117    /// The index of the variant from within the variant list.
118    pub idx: usize,
119}
120
121#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, SemanticObject, HeapSize, salsa::Update)]
122pub struct ConcreteVariant<'db> {
123    pub concrete_enum_id: ConcreteEnumId<'db>,
124    pub id: VariantId<'db>,
125    pub ty: semantic::TypeId<'db>,
126    /// The index of the variant from within the variant list.
127    #[dont_rewrite]
128    pub idx: usize,
129}
130
131/// Selector pattern of a match arm of a match on numeric values.
132/// Required for the dont_rewrite attribute to work.
133#[derive(Clone, Debug, Hash, PartialEq, Eq, DebugWithDb, SemanticObject)]
134#[debug_db(dyn Database)]
135pub struct ValueSelectorArm {
136    #[dont_rewrite]
137    pub value: usize,
138}
139
140/// Selector pattern of a match arm.
141#[derive(Clone, Debug, Hash, PartialEq, Eq, SemanticObject)]
142pub enum MatchArmSelector<'db> {
143    VariantId(ConcreteVariant<'db>),
144    Value(ValueSelectorArm),
145}
146
147/// Returns the definition data of an enum.
148#[salsa::tracked(returns(ref))]
149fn enum_definition_data<'db>(
150    db: &'db dyn Database,
151    enum_id: EnumId<'db>,
152) -> Maybe<EnumDefinitionData<'db>> {
153    let module_id = enum_id.parent_module(db);
154    let crate_id = module_id.owning_crate(db);
155    let mut diagnostics = SemanticDiagnostics::new(module_id);
156    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
157    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
158    // the item instead of all the module data.
159    let enum_ast = db.module_enum_by_id(enum_id)?;
160
161    // Generic params.
162    let generic_params_data = enum_generic_params_data(db, enum_id).maybe_as_ref()?;
163    let inference_id =
164        InferenceId::LookupItemDefinition(LookupItemId::ModuleItem(ModuleItemId::Enum(enum_id)));
165    let mut resolver = Resolver::with_data(
166        db,
167        (*generic_params_data.resolver_data).clone_with_inference_id(db, inference_id),
168    );
169    diagnostics.extend(generic_params_data.diagnostics.clone());
170
171    // Variants.
172    let mut variants = OrderedHashMap::default();
173    let mut variant_semantic = OrderedHashMap::default();
174    for variant in enum_ast.variants(db).elements(db) {
175        let feature_restore =
176            resolver.extend_feature_config_from_item(db, crate_id, &mut diagnostics, &variant);
177        let id = VariantLongId(module_id, variant.stable_ptr(db)).intern(db);
178        let ty = match variant.type_clause(db) {
179            ast::OptionTypeClause::Empty(_) => unit_ty(db),
180            ast::OptionTypeClause::TypeClause(type_clause) => {
181                resolve_type(db, &mut diagnostics, &mut resolver, &type_clause.ty(db))
182            }
183        };
184        let variant_name = variant.name(db).text(db);
185        match variants.entry(variant_name) {
186            Entry::Vacant(e) => {
187                e.insert(id);
188                let idx = variant_semantic.len();
189                variant_semantic.insert(id, Variant { enum_id, id, ty, idx });
190            }
191            Entry::Occupied(_) => {
192                diagnostics.report(
193                    variant.stable_ptr(db),
194                    EnumVariantRedefinition { enum_id, variant_name },
195                );
196            }
197        }
198        resolver.restore_feature_config(feature_restore);
199    }
200
201    // Check fully resolved.
202    let inference = &mut resolver.inference();
203    inference.finalize(&mut diagnostics, enum_ast.stable_ptr(db).untyped());
204
205    for (_, variant) in variant_semantic.iter_mut() {
206        variant.ty = inference.rewrite(variant.ty).no_err();
207    }
208
209    let resolver_data = Arc::new(resolver.data);
210    Ok(EnumDefinitionData {
211        diagnostics: diagnostics.build(),
212        variants,
213        variant_semantic,
214        resolver_data,
215    })
216}
217
218/// Query implementation of [EnumSemantic::enum_definition_diagnostics].
219#[salsa::tracked]
220fn enum_definition_diagnostics<'db>(
221    db: &'db dyn Database,
222    enum_id: EnumId<'db>,
223) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
224    let Ok(data) = enum_definition_data(db, enum_id) else {
225        return Default::default();
226    };
227
228    let crate_id = data.resolver_data.module_id.owning_crate(db);
229
230    // If the enum is a phantom type, no need to check if its variants are fully valid types, as
231    // they won't be used.
232    if db
233        .declared_phantom_type_attributes(crate_id)
234        .iter()
235        .any(|attr| enum_id.has_attr(db, attr.long(db)).unwrap_or_default())
236    {
237        return data.diagnostics.clone();
238    }
239    let mut diagnostics =
240        SemanticDiagnostics::from_diagnostics(enum_id.parent_module(db), data.diagnostics.clone());
241    for (_, variant) in data.variant_semantic.iter() {
242        let stable_ptr = variant.id.stable_ptr(db);
243        add_type_based_diagnostics(db, &mut diagnostics, variant.ty, stable_ptr);
244        if variant.ty.is_phantom(db) {
245            diagnostics.report(stable_ptr, NonPhantomTypeContainingPhantomType);
246        }
247    }
248    diagnostics.build()
249}
250
251// TODO(spapini): Consider making these queries.
252pub trait SemanticEnumEx: Database {
253    /// Retrieves the [ConcreteVariant] for a [ConcreteEnumId] and a [Variant].
254    fn concrete_enum_variant<'db>(
255        &'db self,
256        concrete_enum_id: ConcreteEnumId<'db>,
257        variant: &Variant<'db>,
258    ) -> Maybe<ConcreteVariant<'db>> {
259        // TODO(spapini): Uphold the invariant that constructed ConcreteEnumId instances
260        //   always have the correct number of generic arguments.
261        let db = self.as_dyn_database();
262        let generic_params = db.enum_generic_params(concrete_enum_id.enum_id(db))?;
263        let generic_args = &concrete_enum_id.long(db).generic_args;
264        GenericSubstitution::new(generic_params, generic_args).substitute(
265            db,
266            ConcreteVariant { concrete_enum_id, id: variant.id, ty: variant.ty, idx: variant.idx },
267        )
268    }
269
270    /// Retrieves all the [ConcreteVariant]s for a [ConcreteEnumId].
271    fn concrete_enum_variants<'db>(
272        &'db self,
273        concrete_enum_id: ConcreteEnumId<'db>,
274    ) -> Maybe<Vec<ConcreteVariant<'db>>> {
275        // TODO(spapini): Uphold the invariant that constructed ConcreteEnumId instances
276        //   always have the correct number of generic arguments.
277        let db = self.as_dyn_database();
278        let enum_id = concrete_enum_id.enum_id(db);
279        db.enum_variants(enum_id)?
280            .values()
281            .map(|variant_id| {
282                db.concrete_enum_variant(
283                    concrete_enum_id,
284                    &db.variant_semantic(enum_id, *variant_id)?,
285                )
286            })
287            .collect()
288    }
289}
290
291impl<T: Database + ?Sized> SemanticEnumEx for T {}
292
293/// Trait for enum-related semantic queries.
294pub trait EnumSemantic<'db>: Database {
295    /// Returns the diagnostics of an enum declaration.
296    fn enum_declaration_diagnostics(
297        &'db self,
298        enum_id: EnumId<'db>,
299    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
300        let db = self.as_dyn_database();
301        enum_declaration_data(db, enum_id)
302            .as_ref()
303            .map(|data| data.diagnostics.clone())
304            .unwrap_or_default()
305    }
306    /// Returns the generic parameters of an enum.
307    fn enum_generic_params(&'db self, enum_id: EnumId<'db>) -> Maybe<&'db [GenericParam<'db>]> {
308        let db = self.as_dyn_database();
309        Ok(&enum_generic_params_data(db, enum_id).maybe_as_ref()?.generic_params)
310    }
311    /// Returns the attributes attached to an enum.
312    fn enum_attributes(&'db self, enum_id: EnumId<'db>) -> Maybe<&'db [Attribute<'db>]> {
313        let db = self.as_dyn_database();
314        Ok(&enum_declaration_data(db, enum_id).maybe_as_ref()?.attributes)
315    }
316    /// Returns the resolution resolved_items of an enum declaration.
317    fn enum_declaration_resolver_data(
318        &'db self,
319        enum_id: EnumId<'db>,
320    ) -> Maybe<Arc<ResolverData<'db>>> {
321        let db = self.as_dyn_database();
322        Ok(enum_declaration_data(db, enum_id).maybe_as_ref()?.resolver_data.clone())
323    }
324    /// Returns the definition diagnostics of an enum definition.
325    fn enum_definition_diagnostics(
326        &'db self,
327        enum_id: EnumId<'db>,
328    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
329        enum_definition_diagnostics(self.as_dyn_database(), enum_id)
330    }
331    /// Returns the members of an enum.
332    fn enum_variants(
333        &'db self,
334        enum_id: EnumId<'db>,
335    ) -> Maybe<&'db OrderedHashMap<SmolStrId<'db>, VariantId<'db>>> {
336        let db = self.as_dyn_database();
337        Ok(&enum_definition_data(db, enum_id).maybe_as_ref()?.variants)
338    }
339    /// Returns the semantic model of a variant.
340    fn variant_semantic(
341        &'db self,
342        enum_id: EnumId<'db>,
343        variant_id: VariantId<'db>,
344    ) -> Maybe<semantic::Variant<'db>> {
345        let db = self.as_dyn_database();
346        enum_definition_data(db, enum_id)
347            .maybe_as_ref()?
348            .variant_semantic
349            .get(&variant_id)
350            .cloned()
351            .ok_or_else(skip_diagnostic)
352    }
353    /// Returns the resolution resolved_items of an enum definition.
354    fn enum_definition_resolver_data(
355        &'db self,
356        enum_id: EnumId<'db>,
357    ) -> Maybe<Arc<ResolverData<'db>>> {
358        let db = self.as_dyn_database();
359        Ok(enum_definition_data(db, enum_id).maybe_as_ref()?.resolver_data.clone())
360    }
361}
362impl<'db, T: Database + ?Sized> EnumSemantic<'db> for T {}