cairo_lang_semantic/items/
enm.rs

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