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