cairo_lang_semantic/items/
function_with_body.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::ids::FunctionWithBodyId;
4use cairo_lang_diagnostics::{DiagnosticAdded, Diagnostics, Maybe, ToMaybe};
5use cairo_lang_filesystem::ids::Tracked;
6use cairo_lang_proc_macros::DebugWithDb;
7use cairo_lang_syntax::attribute::consts::{IMPLICIT_PRECEDENCE_ATTR, INLINE_ATTR};
8use cairo_lang_syntax::attribute::structured::{Attribute, AttributeArg, AttributeArgVariant};
9use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode, ast};
10use cairo_lang_utils::try_extract_matches;
11use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
12use itertools::{Itertools, chain};
13use salsa::Database;
14
15use super::functions::InlineConfiguration;
16use crate::diagnostic::{
17    NotFoundItemType, SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder,
18};
19use crate::items::free_function::FreeFunctionSemantic;
20use crate::items::functions::ImplicitPrecedence;
21use crate::items::imp::ImplSemantic;
22use crate::items::trt::TraitSemantic;
23use crate::resolve::{ResolvedConcreteItem, Resolver, ResolverData};
24use crate::{Arenas, ExprId, PatternId, SemanticDiagnostic, TypeId, semantic};
25
26/// Query implementation of [FunctionWithBodySemantic::function_with_body_generic_params].
27#[salsa::tracked]
28fn function_with_body_generic_params<'db>(
29    db: &'db dyn Database,
30    _tracked: Tracked,
31    function_id: FunctionWithBodyId<'db>,
32) -> Maybe<Vec<semantic::GenericParam<'db>>> {
33    Ok(match function_id {
34        FunctionWithBodyId::Free(free_function_id) => {
35            db.free_function_generic_params(free_function_id)?.to_vec()
36        }
37        FunctionWithBodyId::Impl(impl_function_id) => chain!(
38            db.impl_def_generic_params(impl_function_id.impl_def_id(db))?,
39            db.impl_function_generic_params(impl_function_id)?
40        )
41        .cloned()
42        .collect(),
43        FunctionWithBodyId::Trait(trait_function_id) => chain!(
44            db.trait_generic_params(trait_function_id.trait_id(db))?,
45            db.trait_function_generic_params(trait_function_id)?
46        )
47        .cloned()
48        .collect(),
49    })
50}
51
52#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
53#[debug_db(dyn Database)]
54pub struct FunctionBodyData<'db> {
55    pub diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
56    pub expr_lookup: UnorderedHashMap<ast::ExprPtr<'db>, ExprId>,
57    pub pattern_lookup: UnorderedHashMap<ast::PatternPtr<'db>, PatternId>,
58    pub resolver_data: Arc<ResolverData<'db>>,
59    pub body: FunctionBody<'db>,
60}
61
62unsafe impl<'db> salsa::Update for FunctionBodyData<'db> {
63    // Using existing salsa::Update implementations for the fields.
64    // For lookups we assume they are built from the arena,
65    // so a change will be detected and they will be copied.
66    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
67        let old_value = unsafe { &mut *old_pointer };
68        let res = unsafe {
69            Diagnostics::maybe_update(&mut old_value.diagnostics, new_value.diagnostics)
70                | Arc::maybe_update(&mut old_value.resolver_data, new_value.resolver_data)
71                | FunctionBody::maybe_update(&mut old_value.body, new_value.body)
72        };
73        if res {
74            old_value.expr_lookup = new_value.expr_lookup;
75            old_value.pattern_lookup = new_value.pattern_lookup;
76            return true;
77        }
78        false
79    }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
83#[debug_db(dyn Database)]
84pub struct FunctionBody<'db> {
85    pub arenas: Arenas<'db>,
86    pub body_expr: semantic::ExprId,
87}
88
89unsafe impl<'db> salsa::Update for FunctionBody<'db> {
90    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
91        // The function body contains both the arena and the expr id, so a change will be detected.
92        // The comparison should still be safe to do as we won't follow expired references.
93        let old_value = unsafe { &mut *old_pointer };
94
95        if old_value != &new_value {
96            *old_value = new_value;
97            return true;
98        }
99
100        false
101    }
102}
103
104pub trait SemanticExprLookup<'db>: Database {
105    fn lookup_expr_by_ptr(
106        &'db self,
107        function_id: FunctionWithBodyId<'db>,
108        ptr: ast::ExprPtr<'db>,
109    ) -> Maybe<ExprId> {
110        let body_data = match function_id {
111            FunctionWithBodyId::Free(id) => self.priv_free_function_body_data(id)?,
112            FunctionWithBodyId::Impl(id) => self.priv_impl_function_body_data(id)?,
113            FunctionWithBodyId::Trait(id) => {
114                self.priv_trait_function_body_data(id)?.ok_or(DiagnosticAdded)?
115            }
116        };
117        body_data.expr_lookup.get(&ptr).copied().to_maybe()
118    }
119    fn lookup_pattern_by_ptr(
120        &'db self,
121        function_id: FunctionWithBodyId<'db>,
122        ptr: ast::PatternPtr<'db>,
123    ) -> Maybe<PatternId> {
124        let body_data = match function_id {
125            FunctionWithBodyId::Free(id) => self.priv_free_function_body_data(id)?,
126            FunctionWithBodyId::Impl(id) => self.priv_impl_function_body_data(id)?,
127            FunctionWithBodyId::Trait(id) => {
128                self.priv_trait_function_body_data(id)?.ok_or(DiagnosticAdded)?
129            }
130        };
131        body_data.pattern_lookup.get(&ptr).copied().to_maybe()
132    }
133}
134impl<'db, T: Database + ?Sized> SemanticExprLookup<'db> for T {}
135
136/// Get the inline configuration of the given function by parsing its attributes.
137pub fn get_inline_config<'db>(
138    db: &'db dyn Database,
139    diagnostics: &mut SemanticDiagnostics<'db>,
140    attributes: &[Attribute<'db>],
141) -> Maybe<InlineConfiguration<'db>> {
142    let mut config = InlineConfiguration::None;
143    let mut seen_inline_attr = false;
144    for attr in attributes {
145        if attr.id.long(db) != INLINE_ATTR {
146            continue;
147        }
148
149        match &attr.args[..] {
150            [
151                AttributeArg {
152                    variant: AttributeArgVariant::Unnamed(ast::Expr::Path(path)), ..
153                },
154            ] if path.as_syntax_node().get_text(db) == "always" => {
155                config = InlineConfiguration::Always(attr.stable_ptr);
156            }
157            [
158                AttributeArg {
159                    variant: AttributeArgVariant::Unnamed(ast::Expr::Path(path)), ..
160                },
161            ] if path.as_syntax_node().get_text(db) == "never" => {
162                config = InlineConfiguration::Never(attr.stable_ptr);
163            }
164            [] => {
165                config = InlineConfiguration::Should(attr.stable_ptr);
166            }
167            _ => {
168                diagnostics.report(
169                    attr.args_stable_ptr.untyped(),
170                    SemanticDiagnosticKind::UnsupportedInlineArguments,
171                );
172            }
173        }
174
175        if seen_inline_attr {
176            diagnostics.report(
177                attr.id_stable_ptr.untyped(),
178                SemanticDiagnosticKind::RedundantInlineAttribute,
179            );
180            // If we have multiple inline attributes revert to InlineConfiguration::None.
181            config = InlineConfiguration::None;
182        }
183
184        seen_inline_attr = true;
185    }
186    Ok(config)
187}
188
189/// Get [ImplicitPrecedence] of the given function by looking at its attributes.
190///
191/// Returns the generated implicit precedence and the attribute used to get it, if one exists.
192/// If there is no implicit precedence influencing attribute, then this function returns
193/// [ImplicitPrecedence::UNSPECIFIED].
194pub fn get_implicit_precedence<'a, 'r>(
195    db: &'a dyn Database,
196    diagnostics: &mut SemanticDiagnostics<'a>,
197    resolver: &mut Resolver<'a>,
198    attributes: &'r [Attribute<'a>],
199) -> (ImplicitPrecedence<'a>, Option<&'r Attribute<'a>>) {
200    let mut attributes =
201        attributes.iter().rev().filter(|attr| attr.id.long(db) == IMPLICIT_PRECEDENCE_ATTR);
202
203    // Pick the last attribute if any.
204    let Some(attr) = attributes.next() else { return (ImplicitPrecedence::UNSPECIFIED, None) };
205
206    // Report warnings for overridden attributes if any.
207    for attr in attributes {
208        diagnostics.report(
209            attr.id_stable_ptr,
210            SemanticDiagnosticKind::RedundantImplicitPrecedenceAttribute,
211        );
212    }
213
214    let Ok(types) =
215        attr.args
216            .iter()
217            .map(|arg| match &arg.variant {
218                AttributeArgVariant::Unnamed(value) => {
219                    let ast::Expr::Path(path) = value else {
220                        return Err(diagnostics.report(
221                            value.stable_ptr(db),
222                            SemanticDiagnosticKind::UnsupportedImplicitPrecedenceArguments,
223                        ));
224                    };
225
226                    resolver
227                        .resolve_concrete_path(diagnostics, path, NotFoundItemType::Type)
228                        .and_then(|resolved_item: crate::resolve::ResolvedConcreteItem<'_>| {
229                            try_extract_matches!(resolved_item, ResolvedConcreteItem::Type)
230                                .ok_or_else(|| {
231                                    diagnostics.report(
232                                        value.stable_ptr(db),
233                                        SemanticDiagnosticKind::UnknownType,
234                                    )
235                                })
236                        })
237                }
238
239                _ => Err(diagnostics.report(
240                    arg.arg.stable_ptr(db),
241                    SemanticDiagnosticKind::UnsupportedImplicitPrecedenceArguments,
242                )),
243            })
244            .try_collect::<TypeId<'_>, Vec<_>, _>()
245    else {
246        return (ImplicitPrecedence::UNSPECIFIED, None);
247    };
248
249    let precedence = ImplicitPrecedence::from_iter(types);
250
251    (precedence, Some(attr))
252}
253
254/// Trait for function-with-body-related semantic queries.
255pub trait FunctionWithBodySemantic<'db>: Database {
256    /// Returns the semantic diagnostics of a declaration (signature) of a function with a body.
257    fn function_declaration_diagnostics(
258        &'db self,
259        function_id: FunctionWithBodyId<'db>,
260    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
261        match function_id {
262            FunctionWithBodyId::Free(id) => self.free_function_declaration_diagnostics(id),
263            FunctionWithBodyId::Impl(id) => self.impl_function_declaration_diagnostics(id),
264            FunctionWithBodyId::Trait(id) => self.trait_function_declaration_diagnostics(id),
265        }
266    }
267    /// Returns the inline configuration of a declaration (signature) of a function with a body.
268    fn function_declaration_inline_config(
269        &'db self,
270        function_id: FunctionWithBodyId<'db>,
271    ) -> Maybe<InlineConfiguration<'db>> {
272        match function_id {
273            FunctionWithBodyId::Free(id) => self.free_function_declaration_inline_config(id),
274            FunctionWithBodyId::Impl(id) => self.impl_function_declaration_inline_config(id),
275            FunctionWithBodyId::Trait(id) => self.trait_function_declaration_inline_config(id),
276        }
277    }
278    /// Returns the implicit order of a declaration (signature) of a function with a body.
279    fn function_declaration_implicit_precedence(
280        &'db self,
281        function_id: FunctionWithBodyId<'db>,
282    ) -> Maybe<&'db ImplicitPrecedence<'db>> {
283        match function_id {
284            FunctionWithBodyId::Free(id) => self.free_function_declaration_implicit_precedence(id),
285            FunctionWithBodyId::Impl(id) => self.impl_function_declaration_implicit_precedence(id),
286            FunctionWithBodyId::Trait(id) => {
287                self.trait_function_declaration_implicit_precedence(id)
288            }
289        }
290    }
291    /// Returns the signature of a function with a body.
292    fn function_with_body_signature(
293        &'db self,
294        function_id: FunctionWithBodyId<'db>,
295    ) -> Maybe<&'db semantic::Signature<'db>> {
296        match function_id {
297            FunctionWithBodyId::Free(id) => self.free_function_signature(id),
298            FunctionWithBodyId::Impl(id) => self.impl_function_signature(id),
299            FunctionWithBodyId::Trait(id) => self.trait_function_signature(id),
300        }
301    }
302    /// Returns all the available generic params inside a function body.
303    fn function_with_body_generic_params(
304        &'db self,
305        function_id: FunctionWithBodyId<'db>,
306    ) -> Maybe<Vec<semantic::GenericParam<'db>>> {
307        function_with_body_generic_params(self.as_dyn_database(), (), function_id)
308    }
309    /// Returns the attributes of a function with a body.
310    fn function_with_body_attributes(
311        &'db self,
312        function_id: FunctionWithBodyId<'db>,
313    ) -> Maybe<&'db [Attribute<'db>]> {
314        match function_id {
315            FunctionWithBodyId::Free(id) => self.free_function_attributes(id),
316            FunctionWithBodyId::Impl(id) => self.impl_function_attributes(id),
317            FunctionWithBodyId::Trait(id) => self.trait_function_attributes(id),
318        }
319    }
320    /// Returns the semantic diagnostics of a body of a function (with a body).
321    fn function_body_diagnostics(
322        &'db self,
323        function_id: FunctionWithBodyId<'db>,
324    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
325        match function_id {
326            FunctionWithBodyId::Free(id) => self.free_function_body_diagnostics(id),
327            FunctionWithBodyId::Impl(id) => self.impl_function_body_diagnostics(id),
328            FunctionWithBodyId::Trait(id) => self.trait_function_body_diagnostics(id),
329        }
330    }
331    /// Returns the body of a function (with a body).
332    fn function_body(
333        &'db self,
334        function_id: FunctionWithBodyId<'db>,
335    ) -> Maybe<&'db FunctionBody<'db>> {
336        match function_id {
337            FunctionWithBodyId::Free(id) => self.free_function_body(id),
338            FunctionWithBodyId::Impl(id) => self.impl_function_body(id),
339            FunctionWithBodyId::Trait(id) => self.trait_function_body(id)?.ok_or(DiagnosticAdded),
340        }
341    }
342    /// Returns the body expr of a function (with a body).
343    fn function_body_expr(
344        &'db self,
345        function_id: FunctionWithBodyId<'db>,
346    ) -> Maybe<semantic::ExprId> {
347        Ok(self.function_body(function_id)?.body_expr)
348    }
349    /// Assumes function and expression are present.
350    fn expr_semantic(
351        &'db self,
352        function_id: FunctionWithBodyId<'db>,
353        id: semantic::ExprId,
354    ) -> semantic::Expr<'db> {
355        self.function_body(function_id).unwrap().arenas.exprs.get(id).unwrap().clone()
356    }
357    /// Assumes function and statement are valid.
358    fn statement_semantic(
359        &'db self,
360        function_id: FunctionWithBodyId<'db>,
361        id: semantic::StatementId,
362    ) -> semantic::Statement<'db> {
363        self.function_body(function_id).unwrap().arenas.statements.get(id).unwrap().clone()
364    }
365    /// Assumes function and pattern are present.
366    fn pattern_semantic(
367        &'db self,
368        function_id: FunctionWithBodyId<'db>,
369        id: semantic::PatternId,
370    ) -> semantic::Pattern<'db> {
371        self.function_body(function_id).unwrap().arenas.patterns.get(id).unwrap().clone()
372    }
373}
374impl<'db, T: Database + ?Sized> FunctionWithBodySemantic<'db> for T {}