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, 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::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#[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#[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::default();
48 let enum_ast = db.module_enum_by_id(enum_id)?;
52
53 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 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#[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.module_id(db);
79 let mut diagnostics = SemanticDiagnostics::default();
80 let enum_ast = db.module_enum_by_id(enum_id)?;
81
82 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 pub idx: usize,
119}
120
121#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, SemanticObject, 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 #[dont_rewrite]
128 pub idx: usize,
129}
130
131#[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#[derive(Clone, Debug, Hash, PartialEq, Eq, SemanticObject)]
142pub enum MatchArmSelector<'db> {
143 VariantId(ConcreteVariant<'db>),
144 Value(ValueSelectorArm),
145}
146
147#[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.module_id(db);
154 let crate_id = module_id.owning_crate(db);
155 let mut diagnostics = SemanticDiagnostics::default();
156 let enum_ast = db.module_enum_by_id(enum_id)?;
160
161 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 let mut variants = OrderedHashMap::default();
173 let mut variant_semantic = OrderedHashMap::default();
174 for (variant_idx, variant) in enum_ast.variants(db).elements(db).enumerate() {
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 if let Some(_other_variant) = variants.insert(variant_name, id) {
186 diagnostics
187 .report(variant.stable_ptr(db), EnumVariantRedefinition { enum_id, variant_name });
188 }
189 variant_semantic.insert(id, Variant { enum_id, id, ty, idx: variant_idx });
190 resolver.restore_feature_config(feature_restore);
191 }
192
193 let inference = &mut resolver.inference();
195 inference.finalize(&mut diagnostics, enum_ast.stable_ptr(db).untyped());
196
197 for (_, variant) in variant_semantic.iter_mut() {
198 variant.ty = inference.rewrite(variant.ty).no_err();
199 }
200
201 let resolver_data = Arc::new(resolver.data);
202 Ok(EnumDefinitionData {
203 diagnostics: diagnostics.build(),
204 variants,
205 variant_semantic,
206 resolver_data,
207 })
208}
209
210#[salsa::tracked]
212fn enum_definition_diagnostics<'db>(
213 db: &'db dyn Database,
214 enum_id: EnumId<'db>,
215) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
216 let Ok(data) = enum_definition_data(db, enum_id) else {
217 return Default::default();
218 };
219
220 let crate_id = data.resolver_data.module_id.owning_crate(db);
221
222 if db
225 .declared_phantom_type_attributes(crate_id)
226 .iter()
227 .any(|attr| enum_id.has_attr(db, attr.long(db)).unwrap_or_default())
228 {
229 return data.diagnostics.clone();
230 }
231 let mut diagnostics = SemanticDiagnostics::from(data.diagnostics.clone());
232 for (_, variant) in data.variant_semantic.iter() {
233 let stable_ptr = variant.id.stable_ptr(db);
234 add_type_based_diagnostics(db, &mut diagnostics, variant.ty, stable_ptr);
235 if variant.ty.is_phantom(db) {
236 diagnostics.report(stable_ptr, NonPhantomTypeContainingPhantomType);
237 }
238 }
239 diagnostics.build()
240}
241
242pub trait SemanticEnumEx: Database {
244 fn concrete_enum_variant<'db>(
246 &'db self,
247 concrete_enum_id: ConcreteEnumId<'db>,
248 variant: &Variant<'db>,
249 ) -> Maybe<ConcreteVariant<'db>> {
250 let db = self.as_dyn_database();
253 let generic_params = db.enum_generic_params(concrete_enum_id.enum_id(db))?;
254 let generic_args = &concrete_enum_id.long(db).generic_args;
255 GenericSubstitution::new(generic_params, generic_args).substitute(
256 db,
257 ConcreteVariant { concrete_enum_id, id: variant.id, ty: variant.ty, idx: variant.idx },
258 )
259 }
260
261 fn concrete_enum_variants<'db>(
263 &'db self,
264 concrete_enum_id: ConcreteEnumId<'db>,
265 ) -> Maybe<Vec<ConcreteVariant<'db>>> {
266 let db = self.as_dyn_database();
269 let enum_id = concrete_enum_id.enum_id(db);
270 db.enum_variants(enum_id)?
271 .values()
272 .map(|variant_id| {
273 db.concrete_enum_variant(
274 concrete_enum_id,
275 &db.variant_semantic(enum_id, *variant_id)?,
276 )
277 })
278 .collect()
279 }
280}
281
282impl<T: Database + ?Sized> SemanticEnumEx for T {}
283
284pub trait EnumSemantic<'db>: Database {
286 fn enum_declaration_diagnostics(
288 &'db self,
289 enum_id: EnumId<'db>,
290 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
291 let db = self.as_dyn_database();
292 enum_declaration_data(db, enum_id)
293 .as_ref()
294 .map(|data| data.diagnostics.clone())
295 .unwrap_or_default()
296 }
297 fn enum_generic_params(&'db self, enum_id: EnumId<'db>) -> Maybe<&'db [GenericParam<'db>]> {
299 let db = self.as_dyn_database();
300 Ok(&enum_generic_params_data(db, enum_id).maybe_as_ref()?.generic_params)
301 }
302 fn enum_attributes(&'db self, enum_id: EnumId<'db>) -> Maybe<&'db [Attribute<'db>]> {
304 let db = self.as_dyn_database();
305 Ok(&enum_declaration_data(db, enum_id).maybe_as_ref()?.attributes)
306 }
307 fn enum_declaration_resolver_data(
309 &'db self,
310 enum_id: EnumId<'db>,
311 ) -> Maybe<Arc<ResolverData<'db>>> {
312 let db = self.as_dyn_database();
313 Ok(enum_declaration_data(db, enum_id).maybe_as_ref()?.resolver_data.clone())
314 }
315 fn enum_definition_diagnostics(
317 &'db self,
318 enum_id: EnumId<'db>,
319 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
320 enum_definition_diagnostics(self.as_dyn_database(), enum_id)
321 }
322 fn enum_variants(
324 &'db self,
325 enum_id: EnumId<'db>,
326 ) -> Maybe<&'db OrderedHashMap<SmolStrId<'db>, VariantId<'db>>> {
327 let db = self.as_dyn_database();
328 Ok(&enum_definition_data(db, enum_id).maybe_as_ref()?.variants)
329 }
330 fn variant_semantic(
332 &'db self,
333 enum_id: EnumId<'db>,
334 variant_id: VariantId<'db>,
335 ) -> Maybe<semantic::Variant<'db>> {
336 let db = self.as_dyn_database();
337 enum_definition_data(db, enum_id)
338 .maybe_as_ref()?
339 .variant_semantic
340 .get(&variant_id)
341 .cloned()
342 .ok_or_else(skip_diagnostic)
343 }
344 fn enum_definition_resolver_data(
346 &'db self,
347 enum_id: EnumId<'db>,
348 ) -> Maybe<Arc<ResolverData<'db>>> {
349 let db = self.as_dyn_database();
350 Ok(enum_definition_data(db, enum_id).maybe_as_ref()?.resolver_data.clone())
351 }
352}
353impl<'db, T: Database + ?Sized> EnumSemantic<'db> for T {}