Skip to main content

cairo_lang_semantic/items/
structure.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5    LanguageElementId, LookupItemId, MemberId, MemberLongId, ModuleItemId, StructId,
6};
7use cairo_lang_diagnostics::{Diagnostics, Maybe, MaybeAsRef};
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};
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 super::visibility::Visibility;
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::{ConcreteStructId, add_type_based_diagnostics, resolve_type};
26use crate::{GenericParam, SemanticDiagnostic, semantic};
27
28#[cfg(test)]
29#[path = "structure_test.rs"]
30mod test;
31
32#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
33#[debug_db(dyn Database)]
34struct StructDeclarationData<'db> {
35    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
36    attributes: Vec<Attribute<'db>>,
37    resolver_data: Arc<ResolverData<'db>>,
38}
39
40/// Returns the declaration data of a struct.
41#[salsa::tracked(returns(ref))]
42fn struct_declaration_data<'db>(
43    db: &'db dyn Database,
44    struct_id: StructId<'db>,
45) -> Maybe<StructDeclarationData<'db>> {
46    let mut diagnostics = SemanticDiagnostics::new(struct_id.parent_module(db));
47    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
48    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
49    // the item instead of all the module data.
50    // TODO(spapini): Add generic args when they are supported on structs.
51    let struct_ast = db.module_struct_by_id(struct_id)?;
52
53    // Generic params.
54    let generic_params_data = struct_generic_params_data(db, struct_id).maybe_as_ref()?;
55    let inference_id = InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(
56        ModuleItemId::Struct(struct_id),
57    ));
58    let mut resolver = Resolver::with_data(
59        db,
60        (*generic_params_data.resolver_data).clone_with_inference_id(db, inference_id),
61    );
62    diagnostics.extend(generic_params_data.diagnostics.clone());
63
64    let attributes = struct_ast.attributes(db).structurize(db);
65
66    // Check fully resolved.
67    let inference = &mut resolver.inference();
68    inference.finalize(&mut diagnostics, struct_ast.stable_ptr(db).untyped());
69
70    let resolver_data = Arc::new(resolver.data);
71    Ok(StructDeclarationData { diagnostics: diagnostics.build(), attributes, resolver_data })
72}
73
74/// Query implementation of [StructSemantic::struct_generic_params_data].
75#[salsa::tracked(returns(ref))]
76fn struct_generic_params_data<'db>(
77    db: &'db dyn Database,
78    struct_id: StructId<'db>,
79) -> Maybe<GenericParamsData<'db>> {
80    let module_id = struct_id.parent_module(db);
81    let mut diagnostics = SemanticDiagnostics::new(module_id);
82    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
83    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
84    // the item instead of all the module data.
85    // TODO(spapini): Add generic args when they are supported on structs.
86    let struct_ast = db.module_struct_by_id(struct_id)?;
87    // Generic params.
88    let inference_id =
89        InferenceId::LookupItemGenerics(LookupItemId::ModuleItem(ModuleItemId::Struct(struct_id)));
90    let mut resolver = Resolver::new(db, module_id, inference_id);
91    resolver.set_feature_config(&struct_id, &struct_ast, &mut diagnostics);
92    let generic_params = semantic_generic_params(
93        db,
94        &mut diagnostics,
95        &mut resolver,
96        module_id,
97        &struct_ast.generic_params(db),
98    );
99    let inference = &mut resolver.inference();
100    inference.finalize(&mut diagnostics, struct_ast.stable_ptr(db).untyped());
101
102    let generic_params = inference.rewrite(generic_params).no_err();
103    let resolver_data = Arc::new(resolver.data);
104    Ok(GenericParamsData { generic_params, diagnostics: diagnostics.build(), resolver_data })
105}
106
107#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
108#[debug_db(dyn Database)]
109struct StructDefinitionData<'db> {
110    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
111    members: OrderedHashMap<SmolStrId<'db>, Member<'db>>,
112    resolver_data: Arc<ResolverData<'db>>,
113}
114
115#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, SemanticObject, salsa::Update)]
116#[debug_db(dyn Database)]
117pub struct Member<'db> {
118    pub id: MemberId<'db>,
119    pub ty: semantic::TypeId<'db>,
120    #[dont_rewrite]
121    pub visibility: Visibility,
122}
123
124/// Returns the definition data of a struct.
125#[salsa::tracked(returns(ref))]
126fn struct_definition_data<'db>(
127    db: &'db dyn Database,
128    struct_id: StructId<'db>,
129) -> Maybe<StructDefinitionData<'db>> {
130    let module_id = struct_id.parent_module(db);
131    let crate_id = module_id.owning_crate(db);
132    let mut diagnostics = SemanticDiagnostics::new(module_id);
133    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
134    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
135    // the item instead of all the module data.
136    // TODO(spapini): Add generic args when they are supported on structs.
137    let struct_ast = db.module_struct_by_id(struct_id)?;
138
139    // Generic params.
140    let generic_params_data = struct_generic_params_data(db, struct_id).maybe_as_ref()?;
141    let inference_id = InferenceId::LookupItemDefinition(LookupItemId::ModuleItem(
142        ModuleItemId::Struct(struct_id),
143    ));
144    let mut resolver = Resolver::with_data(
145        db,
146        (*generic_params_data.resolver_data).clone_with_inference_id(db, inference_id),
147    );
148    diagnostics.extend(generic_params_data.diagnostics.clone());
149
150    // Members.
151    let mut members = OrderedHashMap::default();
152    for member in struct_ast.members(db).elements(db) {
153        let feature_restore =
154            resolver.extend_feature_config_from_item(db, crate_id, &mut diagnostics, &member);
155        let id = MemberLongId(module_id, member.stable_ptr(db)).intern(db);
156        let ty = resolve_type(db, &mut diagnostics, &mut resolver, &member.type_clause(db).ty(db));
157        let visibility = Visibility::from_ast(db, &mut diagnostics, &member.visibility(db));
158        let member_name = member.name(db).text(db);
159        // Keep the first declaration on a redefinition (matching enum variants), rather than
160        // overwriting with the last - the later duplicate is reported and rejected below.
161        match members.entry(member_name) {
162            Entry::Vacant(e) => {
163                e.insert(Member { id, ty, visibility });
164            }
165            Entry::Occupied(_) => {
166                diagnostics.report(
167                    member.stable_ptr(db),
168                    StructMemberRedefinition { struct_id, member_name },
169                );
170            }
171        }
172        resolver.restore_feature_config(feature_restore);
173    }
174
175    // Check fully resolved.
176    let inference = &mut resolver.inference();
177    inference.finalize(&mut diagnostics, struct_ast.stable_ptr(db).untyped());
178
179    for (_, member) in members.iter_mut() {
180        member.ty = inference.rewrite(member.ty).no_err();
181    }
182
183    let resolver_data = Arc::new(resolver.data);
184    Ok(StructDefinitionData { diagnostics: diagnostics.build(), members, resolver_data })
185}
186
187/// Query implementation of [StructSemantic::struct_definition_diagnostics].
188#[salsa::tracked]
189fn struct_definition_diagnostics<'db>(
190    db: &'db dyn Database,
191    struct_id: StructId<'db>,
192) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
193    let Ok(data) = struct_definition_data(db, struct_id) else {
194        return Default::default();
195    };
196
197    let crate_id = data.resolver_data.module_id.owning_crate(db);
198
199    // If the struct is a phantom type, no need to check if its members are fully valid types, as
200    // they won't be used.
201    if db
202        .declared_phantom_type_attributes(crate_id)
203        .iter()
204        .any(|attr| struct_id.has_attr(db, attr.long(db).as_str()).unwrap_or_default())
205    {
206        return data.diagnostics.clone();
207    }
208    let mut diagnostics = SemanticDiagnostics::from_diagnostics(
209        struct_id.parent_module(db),
210        data.diagnostics.clone(),
211    );
212    for (_, member) in data.members.iter() {
213        let stable_ptr = member.id.stable_ptr(db);
214        add_type_based_diagnostics(db, &mut diagnostics, member.ty, stable_ptr);
215        if member.ty.is_phantom(db) {
216            diagnostics.report(stable_ptr, NonPhantomTypeContainingPhantomType);
217        }
218    }
219    diagnostics.build()
220}
221
222/// Implementation of [StructSemantic::concrete_struct_members].
223#[salsa::tracked(returns(ref))]
224fn concrete_struct_members<'db>(
225    db: &'db dyn Database,
226    concrete_struct_id: ConcreteStructId<'db>,
227) -> Maybe<OrderedHashMap<SmolStrId<'db>, semantic::Member<'db>>> {
228    // TODO(spapini): Uphold the invariant that constructed ConcreteEnumId instances
229    //   always have the correct number of generic arguments.
230    let generic_params = db.struct_generic_params(concrete_struct_id.struct_id(db))?;
231    let generic_args = &concrete_struct_id.long(db).generic_args;
232    let substitution = GenericSubstitution::new(generic_params, generic_args);
233
234    let generic_members = db.struct_members(concrete_struct_id.struct_id(db))?;
235    generic_members
236        .iter()
237        .map(|(name, member)| {
238            let ty = substitution.substitute(db, member.ty)?;
239            Ok((*name, semantic::Member { ty, ..member.clone() }))
240        })
241        .collect()
242}
243
244/// Trait for struct-related semantic queries.
245pub trait StructSemantic<'db>: Database {
246    /// Returns the declaration diagnostics of a struct.
247    fn struct_declaration_diagnostics(
248        &'db self,
249        struct_id: StructId<'db>,
250    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
251        let db = self.as_dyn_database();
252        struct_declaration_data(db, struct_id)
253            .as_ref()
254            .map(|data| data.diagnostics.clone())
255            .unwrap_or_default()
256    }
257    /// Returns the generic parameters of a struct.
258    fn struct_generic_params(
259        &'db self,
260        struct_id: StructId<'db>,
261    ) -> Maybe<&'db [GenericParam<'db>]> {
262        let db = self.as_dyn_database();
263        Ok(&struct_generic_params_data(db, struct_id).maybe_as_ref()?.generic_params)
264    }
265    /// Returns the attributes attached to a struct.
266    fn struct_attributes(&'db self, struct_id: StructId<'db>) -> Maybe<&'db [Attribute<'db>]> {
267        let db = self.as_dyn_database();
268        Ok(&struct_declaration_data(db, struct_id).maybe_as_ref()?.attributes)
269    }
270    /// Returns the resolution resolved_items of a struct declaration.
271    fn struct_declaration_resolver_data(
272        &'db self,
273        struct_id: StructId<'db>,
274    ) -> Maybe<Arc<ResolverData<'db>>> {
275        let db = self.as_dyn_database();
276        Ok(struct_declaration_data(db, struct_id).maybe_as_ref()?.resolver_data.clone())
277    }
278    /// Returns the definition diagnostics of a struct definition.
279    fn struct_definition_diagnostics(
280        &'db self,
281        struct_id: StructId<'db>,
282    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
283        struct_definition_diagnostics(self.as_dyn_database(), struct_id)
284    }
285    /// Returns the members of a struct.
286    fn struct_members(
287        &'db self,
288        struct_id: StructId<'db>,
289    ) -> Maybe<&'db OrderedHashMap<SmolStrId<'db>, semantic::Member<'db>>> {
290        let db = self.as_dyn_database();
291        Ok(&struct_definition_data(db, struct_id).maybe_as_ref()?.members)
292    }
293    /// Returns the resolution resolved_items of a struct definition.
294    fn struct_definition_resolver_data(
295        &'db self,
296        struct_id: StructId<'db>,
297    ) -> Maybe<Arc<ResolverData<'db>>> {
298        let db = self.as_dyn_database();
299        Ok(struct_definition_data(db, struct_id).maybe_as_ref()?.resolver_data.clone())
300    }
301    /// Returns the concrete members of a struct.
302    fn concrete_struct_members(
303        &'db self,
304        concrete_struct_id: ConcreteStructId<'db>,
305    ) -> Maybe<&'db OrderedHashMap<SmolStrId<'db>, semantic::Member<'db>>> {
306        concrete_struct_members(self.as_dyn_database(), concrete_struct_id).maybe_as_ref()
307    }
308}
309
310impl<'db, T: Database + ?Sized> StructSemantic<'db> for T {}