use cairo_lang_defs::diagnostic_utils::StableLocationOption;
use cairo_lang_defs::ids::MemberId;
use cairo_lang_diagnostics::Maybe;
use cairo_lang_semantic as semantic;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
use itertools::{chain, zip_eq, Itertools};
use semantic::items::structure::SemanticStructEx;
use semantic::{ConcreteTypeId, ExprVarMemberPath, TypeLongId};
use super::context::{LoweredExpr, LoweringContext, LoweringFlowError, LoweringResult, VarRequest};
use super::generators;
use super::generators::StatementsBuilder;
use super::refs::{SemanticLoweringMapping, StructRecomposer};
use super::usage::MemberPath;
use crate::diagnostic::LoweringDiagnosticKind;
use crate::{BlockId, FlatBlock, FlatBlockEnd, MatchInfo, Statement, VarRemapping, VariableId};
#[derive(Clone)]
pub struct BlockBuilder {
pub semantics: SemanticLoweringMapping,
changed_semantics: OrderedHashSet<semantic::VarId>,
pub statements: StatementsBuilder,
pub block_id: BlockId,
}
impl BlockBuilder {
pub fn root(_ctx: &mut LoweringContext<'_, '_>, block_id: BlockId) -> Self {
BlockBuilder {
semantics: Default::default(),
changed_semantics: Default::default(),
statements: Default::default(),
block_id,
}
}
pub fn child_block_builder(&self, block_id: BlockId) -> BlockBuilder {
BlockBuilder {
semantics: self.semantics.clone(),
changed_semantics: Default::default(),
statements: Default::default(),
block_id,
}
}
pub fn sibling_block_builder(&self, block_id: BlockId) -> BlockBuilder {
BlockBuilder {
semantics: self.semantics.clone(),
changed_semantics: self.changed_semantics.clone(),
statements: Default::default(),
block_id,
}
}
pub fn put_semantic(&mut self, semantic_var_id: semantic::VarId, var: VariableId) {
self.semantics.introduce(MemberPath::Var(semantic_var_id), var);
self.changed_semantics.insert(semantic_var_id);
}
pub fn update_ref(
&mut self,
ctx: &mut LoweringContext<'_, '_>,
member_path: &ExprVarMemberPath,
var: VariableId,
) {
let location = ctx.get_location(member_path.stable_ptr().untyped());
self.semantics.update(
BlockStructRecomposer { statements: &mut self.statements, ctx, location },
&member_path.into(),
var,
);
self.changed_semantics.insert(member_path.base_var());
}
pub fn get_ref(
&mut self,
ctx: &mut LoweringContext<'_, '_>,
member_path: &ExprVarMemberPath,
) -> Option<VariableId> {
let location = ctx.get_location(member_path.stable_ptr().untyped());
self.semantics.get(
BlockStructRecomposer { statements: &mut self.statements, ctx, location },
&member_path.into(),
)
}
pub fn get_semantic(
&mut self,
ctx: &mut LoweringContext<'_, '_>,
semantic_var_id: semantic::VarId,
location: StableLocationOption,
) -> VariableId {
self.semantics
.get(
BlockStructRecomposer { statements: &mut self.statements, ctx, location },
&MemberPath::Var(semantic_var_id),
)
.expect("Use of undefined variable cannot happen after semantic phase.")
}
pub fn push_statement(&mut self, statement: Statement) {
self.statements.push_statement(statement);
}
pub fn unreachable_match(self, ctx: &mut LoweringContext<'_, '_>, match_info: MatchInfo) {
self.finalize(ctx, FlatBlockEnd::Match { info: match_info });
}
pub fn panic(self, ctx: &mut LoweringContext<'_, '_>, data: VariableId) -> Maybe<()> {
self.finalize(ctx, FlatBlockEnd::Panic(data));
Ok(())
}
pub fn goto_callsite(self, expr: Option<VariableId>) -> SealedBlockBuilder {
SealedBlockBuilder::GotoCallsite { builder: self, expr }
}
pub fn ret(
mut self,
ctx: &mut LoweringContext<'_, '_>,
expr: VariableId,
location: StableLocationOption,
) -> Maybe<()> {
let ref_vars = ctx
.signature
.extra_rets
.clone()
.iter()
.map(|member_path| {
self.semantics.get(
BlockStructRecomposer { statements: &mut self.statements, ctx, location },
&member_path.into(),
)
})
.collect::<Option<Vec<_>>>()
.ok_or_else(|| {
ctx.diagnostics
.report_by_location(location, LoweringDiagnosticKind::UnsupportedMatch)
})?;
self.finalize(ctx, FlatBlockEnd::Return(chain!(ref_vars, [expr]).collect()));
Ok(())
}
fn finalize(self, ctx: &mut LoweringContext<'_, '_>, end: FlatBlockEnd) {
let block = FlatBlock { statements: self.statements.statements, end };
ctx.blocks.set_block(self.block_id, block);
}
pub fn merge_and_end_with_match(
&mut self,
ctx: &mut LoweringContext<'_, '_>,
match_info: MatchInfo,
sealed_blocks: Vec<SealedBlockBuilder>,
location: StableLocationOption,
) -> LoweringResult<LoweredExpr> {
let Some((merged_expr, following_block)) = self.merge_sealed(ctx, sealed_blocks, location) else {
return Err(LoweringFlowError::Match(match_info));
};
let new_scope = self.sibling_block_builder(following_block);
let prev_scope = std::mem::replace(self, new_scope);
prev_scope.finalize(ctx, FlatBlockEnd::Match { info: match_info });
Ok(merged_expr)
}
fn merge_sealed(
&mut self,
ctx: &mut LoweringContext<'_, '_>,
sealed_blocks: Vec<SealedBlockBuilder>,
location: StableLocationOption,
) -> Option<(LoweredExpr, BlockId)> {
let mut semantic_remapping = SemanticRemapping::default();
let mut n_reachable_blocks = 0;
for sealed_block in &sealed_blocks {
let SealedBlockBuilder::GotoCallsite { builder: subscope, expr } = sealed_block else {
continue;
};
n_reachable_blocks += 1;
if let Some(var) = expr {
semantic_remapping.expr.get_or_insert_with(|| {
let var = ctx.variables[*var].clone();
ctx.variables.variables.alloc(var)
});
}
for semantic in subscope.changed_semantics.iter() {
if !self.semantics.contains_var(semantic) {
continue;
}
semantic_remapping.semantics.entry(*semantic).or_insert_with(|| {
let var = self.get_semantic(ctx, *semantic, location);
let var = ctx.variables[var].clone();
ctx.variables.variables.alloc(var)
});
}
}
if n_reachable_blocks == 0 {
return None;
}
let following_block = ctx.blocks.alloc_empty();
for sealed_block in sealed_blocks {
sealed_block.finalize(ctx, following_block, &semantic_remapping, location);
}
for (semantic, var) in semantic_remapping.semantics {
self.put_semantic(semantic, var);
}
let expr = match semantic_remapping.expr {
Some(var) => LoweredExpr::AtVariable(var),
None => LoweredExpr::Tuple { exprs: vec![], location },
};
Some((expr, following_block))
}
}
#[derive(Debug, Default)]
pub struct SemanticRemapping {
expr: Option<VariableId>,
semantics: OrderedHashMap<semantic::VarId, VariableId>,
}
#[allow(clippy::large_enum_variant)]
pub enum SealedBlockBuilder {
GotoCallsite { builder: BlockBuilder, expr: Option<VariableId> },
Ends(BlockId),
}
impl SealedBlockBuilder {
fn finalize(
self,
ctx: &mut LoweringContext<'_, '_>,
target: BlockId,
semantic_remapping: &SemanticRemapping,
location: StableLocationOption,
) {
if let SealedBlockBuilder::GotoCallsite { mut builder, expr } = self {
let mut remapping = VarRemapping::default();
for (semantic, remapped_var) in semantic_remapping.semantics.iter() {
assert!(
remapping
.insert(*remapped_var, builder.get_semantic(ctx, *semantic, location))
.is_none()
);
}
if let Some(remapped_var) = semantic_remapping.expr {
let expr = expr.unwrap_or_else(|| {
LoweredExpr::Tuple {
exprs: vec![],
location: ctx.variables[remapped_var].location,
}
.var(ctx, &mut builder)
.unwrap()
});
assert!(remapping.insert(remapped_var, expr).is_none());
}
builder.finalize(ctx, FlatBlockEnd::Goto(target, remapping));
}
}
}
struct BlockStructRecomposer<'a, 'b, 'c> {
statements: &'a mut StatementsBuilder,
ctx: &'a mut LoweringContext<'b, 'c>,
location: StableLocationOption,
}
impl<'a, 'b, 'c> StructRecomposer for BlockStructRecomposer<'a, 'b, 'c> {
fn deconstruct(
&mut self,
concrete_struct_id: semantic::ConcreteStructId,
value: VariableId,
) -> OrderedHashMap<MemberId, VariableId> {
let members = self.ctx.db.concrete_struct_members(concrete_struct_id).unwrap();
let members = members.values().collect_vec();
let member_ids = members.iter().map(|m| m.id);
let var_reqs = members
.iter()
.map(|member| VarRequest { ty: member.ty, location: self.location })
.collect();
let member_values =
generators::StructDestructure { input: value, var_reqs }.add(self.ctx, self.statements);
OrderedHashMap::from_iter(zip_eq(member_ids, member_values))
}
fn reconstruct(
&mut self,
concrete_struct_id: semantic::ConcreteStructId,
members: Vec<VariableId>,
) -> VariableId {
let ty = self
.ctx
.db
.intern_type(TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id)));
generators::StructConstruct { inputs: members, ty, location: self.location }
.add(self.ctx, self.statements)
}
fn var_ty(&self, var: VariableId) -> semantic::TypeId {
self.ctx.variables[var].ty
}
}