use std::sync::Arc;
use cairo_lang_defs::ids::FunctionWithBodyId;
use cairo_lang_diagnostics::{DiagnosticAdded, Diagnostics, Maybe, ToMaybe};
use cairo_lang_filesystem::ids::Tracked;
use cairo_lang_proc_macros::DebugWithDb;
use cairo_lang_syntax::attribute::consts::{IMPLICIT_PRECEDENCE_ATTR, INLINE_ATTR};
use cairo_lang_syntax::attribute::structured::{Attribute, AttributeArg, AttributeArgVariant};
use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode, ast};
use cairo_lang_utils::try_extract_matches;
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use itertools::{Itertools, chain};
use salsa::Database;
use super::functions::InlineConfiguration;
use crate::diagnostic::{
NotFoundItemType, SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder,
};
use crate::items::free_function::FreeFunctionSemantic;
use crate::items::functions::ImplicitPrecedence;
use crate::items::imp::ImplSemantic;
use crate::items::trt::TraitSemantic;
use crate::resolve::{ResolvedConcreteItem, Resolver, ResolverData};
use crate::{Arenas, ExprId, PatternId, SemanticDiagnostic, TypeId, semantic};
#[salsa::tracked]
fn function_with_body_generic_params<'db>(
db: &'db dyn Database,
_tracked: Tracked,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<Vec<semantic::GenericParam<'db>>> {
Ok(match function_id {
FunctionWithBodyId::Free(free_function_id) => {
db.free_function_generic_params(free_function_id)?.to_vec()
}
FunctionWithBodyId::Impl(impl_function_id) => chain!(
db.impl_def_generic_params(impl_function_id.impl_def_id(db))?,
db.impl_function_generic_params(impl_function_id)?
)
.cloned()
.collect(),
FunctionWithBodyId::Trait(trait_function_id) => chain!(
db.trait_generic_params(trait_function_id.trait_id(db))?,
db.trait_function_generic_params(trait_function_id)?
)
.cloned()
.collect(),
})
}
#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
#[debug_db(dyn Database)]
pub struct FunctionBodyData<'db> {
pub diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
pub expr_lookup: UnorderedHashMap<ast::ExprPtr<'db>, ExprId>,
pub pattern_lookup: UnorderedHashMap<ast::PatternPtr<'db>, PatternId>,
pub resolver_data: Arc<ResolverData<'db>>,
pub body: FunctionBody<'db>,
}
unsafe impl<'db> salsa::Update for FunctionBodyData<'db> {
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
let old_value = unsafe { &mut *old_pointer };
let res = unsafe {
Diagnostics::maybe_update(&mut old_value.diagnostics, new_value.diagnostics)
| Arc::maybe_update(&mut old_value.resolver_data, new_value.resolver_data)
| FunctionBody::maybe_update(&mut old_value.body, new_value.body)
};
if res {
old_value.expr_lookup = new_value.expr_lookup;
old_value.pattern_lookup = new_value.pattern_lookup;
return true;
}
false
}
}
#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
#[debug_db(dyn Database)]
pub struct FunctionBody<'db> {
pub arenas: Arenas<'db>,
pub body_expr: semantic::ExprId,
}
unsafe impl<'db> salsa::Update for FunctionBody<'db> {
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
let old_value = unsafe { &mut *old_pointer };
if old_value != &new_value {
*old_value = new_value;
return true;
}
false
}
}
pub trait SemanticExprLookup<'db>: Database {
fn lookup_expr_by_ptr(
&'db self,
function_id: FunctionWithBodyId<'db>,
ptr: ast::ExprPtr<'db>,
) -> Maybe<ExprId> {
let body_data = match function_id {
FunctionWithBodyId::Free(id) => self.priv_free_function_body_data(id)?,
FunctionWithBodyId::Impl(id) => self.priv_impl_function_body_data(id)?,
FunctionWithBodyId::Trait(id) => {
self.priv_trait_function_body_data(id)?.ok_or(DiagnosticAdded)?
}
};
body_data.expr_lookup.get(&ptr).copied().to_maybe()
}
fn lookup_pattern_by_ptr(
&'db self,
function_id: FunctionWithBodyId<'db>,
ptr: ast::PatternPtr<'db>,
) -> Maybe<PatternId> {
let body_data = match function_id {
FunctionWithBodyId::Free(id) => self.priv_free_function_body_data(id)?,
FunctionWithBodyId::Impl(id) => self.priv_impl_function_body_data(id)?,
FunctionWithBodyId::Trait(id) => {
self.priv_trait_function_body_data(id)?.ok_or(DiagnosticAdded)?
}
};
body_data.pattern_lookup.get(&ptr).copied().to_maybe()
}
}
impl<'db, T: Database + ?Sized> SemanticExprLookup<'db> for T {}
pub fn get_inline_config<'db>(
db: &'db dyn Database,
diagnostics: &mut SemanticDiagnostics<'db>,
attributes: &[Attribute<'db>],
) -> Maybe<InlineConfiguration<'db>> {
let mut config = InlineConfiguration::None;
let mut seen_inline_attr = false;
for attr in attributes {
if attr.id.long(db) != INLINE_ATTR {
continue;
}
match &attr.args[..] {
[
AttributeArg {
variant: AttributeArgVariant::Unnamed(ast::Expr::Path(path)), ..
},
] if path.as_syntax_node().get_text(db) == "always" => {
config = InlineConfiguration::Always(attr.stable_ptr);
}
[
AttributeArg {
variant: AttributeArgVariant::Unnamed(ast::Expr::Path(path)), ..
},
] if path.as_syntax_node().get_text(db) == "never" => {
config = InlineConfiguration::Never(attr.stable_ptr);
}
[] => {
config = InlineConfiguration::Should(attr.stable_ptr);
}
_ => {
diagnostics.report(
attr.args_stable_ptr.untyped(),
SemanticDiagnosticKind::UnsupportedInlineArguments,
);
}
}
if seen_inline_attr {
diagnostics.report(
attr.id_stable_ptr.untyped(),
SemanticDiagnosticKind::RedundantInlineAttribute,
);
config = InlineConfiguration::None;
}
seen_inline_attr = true;
}
Ok(config)
}
pub fn get_implicit_precedence<'a, 'r>(
db: &'a dyn Database,
diagnostics: &mut SemanticDiagnostics<'a>,
resolver: &mut Resolver<'a>,
attributes: &'r [Attribute<'a>],
) -> (ImplicitPrecedence<'a>, Option<&'r Attribute<'a>>) {
let mut attributes =
attributes.iter().rev().filter(|attr| attr.id.long(db) == IMPLICIT_PRECEDENCE_ATTR);
let Some(attr) = attributes.next() else { return (ImplicitPrecedence::UNSPECIFIED, None) };
for attr in attributes {
diagnostics.report(
attr.id_stable_ptr,
SemanticDiagnosticKind::RedundantImplicitPrecedenceAttribute,
);
}
let Ok(types) =
attr.args
.iter()
.map(|arg| match &arg.variant {
AttributeArgVariant::Unnamed(value) => {
let ast::Expr::Path(path) = value else {
return Err(diagnostics.report(
value.stable_ptr(db),
SemanticDiagnosticKind::UnsupportedImplicitPrecedenceArguments,
));
};
resolver
.resolve_concrete_path(diagnostics, path, NotFoundItemType::Type)
.and_then(|resolved_item: crate::resolve::ResolvedConcreteItem<'_>| {
try_extract_matches!(resolved_item, ResolvedConcreteItem::Type)
.ok_or_else(|| {
diagnostics.report(
value.stable_ptr(db),
SemanticDiagnosticKind::UnknownType,
)
})
})
}
_ => Err(diagnostics.report(
arg.arg.stable_ptr(db),
SemanticDiagnosticKind::UnsupportedImplicitPrecedenceArguments,
)),
})
.try_collect::<TypeId<'_>, Vec<_>, _>()
else {
return (ImplicitPrecedence::UNSPECIFIED, None);
};
let precedence = ImplicitPrecedence::from_iter(types);
(precedence, Some(attr))
}
pub trait FunctionWithBodySemantic<'db>: Database {
fn function_declaration_diagnostics(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
match function_id {
FunctionWithBodyId::Free(id) => self.free_function_declaration_diagnostics(id),
FunctionWithBodyId::Impl(id) => self.impl_function_declaration_diagnostics(id),
FunctionWithBodyId::Trait(id) => self.trait_function_declaration_diagnostics(id),
}
}
fn function_declaration_inline_config(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<InlineConfiguration<'db>> {
match function_id {
FunctionWithBodyId::Free(id) => self.free_function_declaration_inline_config(id),
FunctionWithBodyId::Impl(id) => self.impl_function_declaration_inline_config(id),
FunctionWithBodyId::Trait(id) => self.trait_function_declaration_inline_config(id),
}
}
fn function_declaration_implicit_precedence(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<&'db ImplicitPrecedence<'db>> {
match function_id {
FunctionWithBodyId::Free(id) => self.free_function_declaration_implicit_precedence(id),
FunctionWithBodyId::Impl(id) => self.impl_function_declaration_implicit_precedence(id),
FunctionWithBodyId::Trait(id) => {
self.trait_function_declaration_implicit_precedence(id)
}
}
}
fn function_with_body_signature(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<&'db semantic::Signature<'db>> {
match function_id {
FunctionWithBodyId::Free(id) => self.free_function_signature(id),
FunctionWithBodyId::Impl(id) => self.impl_function_signature(id),
FunctionWithBodyId::Trait(id) => self.trait_function_signature(id),
}
}
fn function_with_body_generic_params(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<Vec<semantic::GenericParam<'db>>> {
function_with_body_generic_params(self.as_dyn_database(), (), function_id)
}
fn function_with_body_attributes(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<&'db [Attribute<'db>]> {
match function_id {
FunctionWithBodyId::Free(id) => self.free_function_attributes(id),
FunctionWithBodyId::Impl(id) => self.impl_function_attributes(id),
FunctionWithBodyId::Trait(id) => self.trait_function_attributes(id),
}
}
fn function_body_diagnostics(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
match function_id {
FunctionWithBodyId::Free(id) => self.free_function_body_diagnostics(id),
FunctionWithBodyId::Impl(id) => self.impl_function_body_diagnostics(id),
FunctionWithBodyId::Trait(id) => self.trait_function_body_diagnostics(id),
}
}
fn function_body(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<&'db FunctionBody<'db>> {
match function_id {
FunctionWithBodyId::Free(id) => self.free_function_body(id),
FunctionWithBodyId::Impl(id) => self.impl_function_body(id),
FunctionWithBodyId::Trait(id) => self.trait_function_body(id)?.ok_or(DiagnosticAdded),
}
}
fn function_body_expr(
&'db self,
function_id: FunctionWithBodyId<'db>,
) -> Maybe<semantic::ExprId> {
Ok(self.function_body(function_id)?.body_expr)
}
fn expr_semantic(
&'db self,
function_id: FunctionWithBodyId<'db>,
id: semantic::ExprId,
) -> semantic::Expr<'db> {
self.function_body(function_id).unwrap().arenas.exprs.get(id).unwrap().clone()
}
fn statement_semantic(
&'db self,
function_id: FunctionWithBodyId<'db>,
id: semantic::StatementId,
) -> semantic::Statement<'db> {
self.function_body(function_id).unwrap().arenas.statements.get(id).unwrap().clone()
}
fn pattern_semantic(
&'db self,
function_id: FunctionWithBodyId<'db>,
id: semantic::PatternId,
) -> semantic::Pattern<'db> {
self.function_body(function_id).unwrap().arenas.patterns.get(id).unwrap().clone()
}
}
impl<'db, T: Database + ?Sized> FunctionWithBodySemantic<'db> for T {}