pub mod air; pub mod builder; pub mod decision_tree; pub mod interner; pub mod stick_break_set; pub mod tree; use self::{ air::Air, builder::{ cast_validator_args, convert_type_to_data, extract_constant, modify_cyclic_calls, modify_self_calls, AssignmentProperties, CodeGenSpecialFuncs, CycleFunctionNames, HoistableFunction, Variant, }, tree::{AirTree, TreePath}, }; use crate::{ ast::{ AssignmentKind, BinOp, Bls12_381Point, Curve, DataTypeKey, FunctionAccessKey, Pattern, Span, TraceLevel, Tracing, TypedArg, TypedDataType, TypedFunction, TypedPattern, TypedValidator, UnOp, }, builtins::PRELUDE, expr::TypedExpr, gen_uplc::{ air::ExpectLevel, builder::{ erase_opaque_type_operations, get_generic_variant_name, get_line_columns_by_span, get_src_code_by_span, known_data_to_type, monomorphize, wrap_validator_condition, CodeGenFunction, }, }, line_numbers::LineNumbers, plutus_version::PlutusVersion, tipo::{ check_replaceable_opaque_type, convert_opaque_type, find_and_replace_generics, get_arg_type_name, get_generic_id_and_type, lookup_data_type_by_tipo, ModuleValueConstructor, PatternConstructor, Type, TypeInfo, ValueConstructor, ValueConstructorVariant, }, IdGenerator, }; use builder::{ introduce_name, introduce_pattern, pop_pattern, softcast_data_to_type_otherwise, unknown_data_to_type, DISCARDED, }; use decision_tree::{get_tipo_by_path, Assigned, CaseTest, DecisionTree, TreeGen}; use indexmap::IndexMap; use interner::AirInterner; use itertools::Itertools; use petgraph::{algo, Graph}; use std::{collections::HashMap, rc::Rc}; use stick_break_set::{Builtins, TreeSet}; use tree::Fields; use uplc::{ ast::{Constant as UplcConstant, Name, NamedDeBruijn, Program, Term, Type as UplcType}, builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, EXPECT_ON_LIST}, builtins::DefaultFunction, machine::cost_model::ExBudget, optimize::{aiken_optimize_and_intern, interner::CodeGenInterner, shrinker::NO_INLINE}, }; type Otherwise = Option; const DELAY_ERROR: fn() -> AirTree = || AirTree::anon_func(vec![], AirTree::error(Type::void(), false), true); #[derive(Clone)] pub struct CodeGenerator<'a> { #[allow(dead_code)] plutus_version: PlutusVersion, /// immutable index maps functions: IndexMap<&'a FunctionAccessKey, &'a TypedFunction>, constants: IndexMap<&'a FunctionAccessKey, &'a TypedExpr>, data_types: IndexMap<&'a DataTypeKey, &'a TypedDataType>, module_types: IndexMap<&'a str, &'a TypeInfo>, module_src: IndexMap<&'a str, &'a (String, LineNumbers)>, /// immutable option tracing: TraceLevel, /// mutable index maps that are reset defined_functions: IndexMap, special_functions: CodeGenSpecialFuncs, code_gen_functions: IndexMap, cyclic_functions: IndexMap<(FunctionAccessKey, Variant), (CycleFunctionNames, usize, FunctionAccessKey)>, /// mutable and reset as well interner: AirInterner, id_gen: IdGenerator, } impl<'a> CodeGenerator<'a> { pub fn data_types(&self) -> &IndexMap<&'a DataTypeKey, &'a TypedDataType> { &self.data_types } pub fn new( plutus_version: PlutusVersion, functions: IndexMap<&'a FunctionAccessKey, &'a TypedFunction>, constants: IndexMap<&'a FunctionAccessKey, &'a TypedExpr>, data_types: IndexMap<&'a DataTypeKey, &'a TypedDataType>, module_types: IndexMap<&'a str, &'a TypeInfo>, module_src: IndexMap<&'a str, &'a (String, LineNumbers)>, tracing: Tracing, ) -> Self { CodeGenerator { plutus_version, functions, constants, data_types, module_types, module_src, tracing: tracing.trace_level(true), defined_functions: IndexMap::new(), special_functions: CodeGenSpecialFuncs::new(), code_gen_functions: IndexMap::new(), cyclic_functions: IndexMap::new(), interner: AirInterner::new(), id_gen: IdGenerator::new(), } } pub fn reset(&mut self, reset_special_functions: bool) { self.code_gen_functions = IndexMap::new(); self.defined_functions = IndexMap::new(); self.cyclic_functions = IndexMap::new(); self.interner = AirInterner::new(); self.id_gen = IdGenerator::new(); if reset_special_functions { self.special_functions = CodeGenSpecialFuncs::new(); } } pub fn generate(&mut self, validator: &TypedValidator, module_name: &str) -> Program { let context_name = "__context__".to_string(); let context_name_interned = introduce_name(&mut self.interner, &context_name); validator.params.iter().for_each(|arg| { arg.get_variable_name() .iter() .for_each(|arg_name| self.interner.intern(arg_name.to_string())) }); let air_tree_fun = wrap_validator_condition( self.build(&validator.into_script_context_handler(), module_name, &[]), self.tracing, ); let air_tree_fun = AirTree::anon_func(vec![context_name_interned], air_tree_fun, true); let validator_args_tree = AirTree::no_op(air_tree_fun); let full_tree = self.hoist_functions_to_validator(validator_args_tree); // optimizations on air tree let full_vec = full_tree.to_vec(); let term = self.uplc_code_gen(full_vec); let term = cast_validator_args(term, &validator.params, &self.interner); self.interner.pop_text(context_name); validator.params.iter().for_each(|arg| { arg.get_variable_name() .iter() .for_each(|arg_name| self.interner.pop_text(arg_name.to_string())) }); self.finalize(term) } pub fn generate_raw( &mut self, body: &TypedExpr, args: &[TypedArg], module_name: &str, ) -> Program { args.iter().for_each(|arg| { arg.get_variable_name() .iter() .for_each(|arg_name| self.interner.intern(arg_name.to_string())) }); let mut air_tree = self.build(body, module_name, &[]); air_tree = AirTree::no_op(air_tree); let full_tree = self.hoist_functions_to_validator(air_tree); // optimizations on air tree let full_vec = full_tree.to_vec(); let mut term = self.uplc_code_gen(full_vec); term = if args.is_empty() { term } else { cast_validator_args(term, args, &self.interner) }; args.iter().for_each(|arg| { arg.get_variable_name() .iter() .for_each(|arg_name| self.interner.pop_text(arg_name.to_string())) }); self.finalize(term) } fn new_program(&self, term: Term) -> Program { let version = match self.plutus_version { PlutusVersion::V1 | PlutusVersion::V2 => (1, 0, 0), PlutusVersion::V3 => (1, 1, 0), }; Program { version, term } } fn finalize(&mut self, mut term: Term) -> Program { term = self.special_functions.apply_used_functions(term); let program = aiken_optimize_and_intern(self.new_program(term)); // This is very important to call here. // If this isn't done, re-using the same instance // of the generator will result in free unique errors // among other unpredictable things. In fact, // switching to a shared code generator caused some // instability issues and we fixed it by placing this // method here. self.reset(true); program } fn build( &mut self, body: &TypedExpr, module_build_name: &str, context: &[TypedExpr], ) -> AirTree { if !context.is_empty() { let TypedExpr::Assignment { location, tipo, value, pattern, kind, } = body else { panic!("Dangling expressions without an assignment") }; let air_value = self.build(value, module_build_name, &[]); let otherwise_delayed = { let msg = match (self.tracing, kind) { (TraceLevel::Silent, _) | (_, AssignmentKind::Let { .. }) => "".to_string(), (TraceLevel::Compact, _) => { get_line_columns_by_span(module_build_name, location, &self.module_src) .to_string() } (TraceLevel::Verbose, _) => { get_src_code_by_span(module_build_name, location, &self.module_src) } }; let msg_func_name = msg.split_whitespace().join(""); if msg_func_name.is_empty() { None } else { self.special_functions.insert_new_function( msg_func_name.clone(), Term::Error.delayed_trace(Term::string(msg)).delay(), Type::void(), ); Some(self.special_functions.use_function_tree(msg_func_name)) } }; // Intern vars from pattern here introduce_pattern(&mut self.interner, pattern); let (then, context) = context.split_first().unwrap(); let then = self.build(then, module_build_name, context); let tree = self.assignment( pattern, air_value, then, tipo, AssignmentProperties { value_type: value.tipo(), kind: *kind, remove_unused: kind.is_let(), full_check: !tipo.is_data() && value.tipo().is_data() && kind.is_expect(), otherwise: otherwise_delayed, }, ); // Now pop off interned pattern pop_pattern(&mut self.interner, pattern); tree } else { match body { TypedExpr::Assignment { .. } => { panic!("Reached assignment with no dangling expressions") } TypedExpr::UInt { value, .. } => AirTree::int(value), TypedExpr::String { value, .. } => AirTree::string(value), TypedExpr::ByteArray { bytes, .. } => AirTree::byte_array(bytes.clone()), TypedExpr::Sequence { expressions, .. } | TypedExpr::Pipeline { expressions, .. } => { let (expr, dangling_expressions) = expressions .split_first() .expect("Sequence or Pipeline should have at least one expression"); self.build(expr, module_build_name, dangling_expressions) } TypedExpr::Var { constructor, name, .. } => match constructor.variant { ValueConstructorVariant::LocalVariable { .. } => { AirTree::var(constructor.clone(), self.interner.lookup_interned(name), "") } _ => AirTree::var(constructor.clone(), name, ""), }, TypedExpr::Fn { args, body, .. } => { let params = args .iter() .map(|arg| { arg.get_variable_name() .map(|arg| introduce_name(&mut self.interner, &arg.to_string())) .unwrap_or_else(|| DISCARDED.to_string()) }) .collect_vec(); let anon = AirTree::anon_func(params, self.build(body, module_build_name, &[]), false); args.iter() .filter_map(|arg| arg.get_variable_name()) .for_each(|arg| { self.interner.pop_text(arg.to_string()); }); anon } TypedExpr::List { tipo, elements, tail, .. } => AirTree::list( elements .iter() .map(|elem| self.build(elem, module_build_name, &[])) .collect_vec(), tipo.clone(), tail.as_ref() .map(|tail| self.build(tail, module_build_name, &[])), ), TypedExpr::Call { tipo, fun, args, .. } => match fun.as_ref() { TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { name: constr_name, .. }, tipo: constr_tipo, .. }, .. } | TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name: constr_name, tipo: constr_tipo, .. }, .. } => { let data_type = lookup_data_type_by_tipo(&self.data_types, tipo) .unwrap_or_else(|| panic!( "Creating a record of type {:?} with no record definition. Known definitions: {:?}", tipo.to_pretty(0), self.data_types.keys() ) ); let (constr_index, _) = data_type .constructors .iter() .enumerate() .find(|(_, dt)| &dt.name == constr_name) .unwrap(); let constr_args = args .iter() .zip(constr_tipo.arg_types().unwrap()) .map(|(arg, tipo)| { if tipo.is_data() { AirTree::cast_to_data( self.build(&arg.value, module_build_name, &[]), arg.value.tipo(), ) } else { self.build(&arg.value, module_build_name, &[]) } }) .collect_vec(); AirTree::create_constr(constr_index, constr_tipo.clone(), constr_args) } TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::ModuleFn { builtin, .. }, .. }, .. } => { let fun_arg_types = fun .tipo() .arg_types() .expect("Expected a function type with arguments"); assert!(args.len() == fun_arg_types.len()); let func_args = args .iter() .zip(fun_arg_types) .map(|(arg, arg_tipo)| { let mut arg_val = self.build(&arg.value, module_build_name, &[]); if arg_tipo.is_data() && !arg.value.tipo().is_data() { arg_val = AirTree::cast_to_data(arg_val, arg.value.tipo()) } arg_val }) .collect_vec(); if let Some(func) = builtin { AirTree::builtin(*func, tipo.clone(), func_args) } else { AirTree::call( self.build(fun.as_ref(), module_build_name, &[]), tipo.clone(), func_args, ) } } TypedExpr::ModuleSelect { module_name, constructor: ModuleValueConstructor::Fn { name, .. }, .. } => { let type_info = self.module_types.get(module_name.as_str()).unwrap(); let value = type_info.values.get(name).unwrap(); let ValueConstructorVariant::ModuleFn { builtin, .. } = &value.variant else { unreachable!("Missing module function definition") }; let fun_arg_types = fun .tipo() .arg_types() .expect("Expected a function type with arguments"); assert!(args.len() == fun_arg_types.len()); let func_args = args .iter() .zip(fun_arg_types) .map(|(arg, arg_tipo)| { let mut arg_val = self.build(&arg.value, module_build_name, &[]); if arg_tipo.is_data() && !arg.value.tipo().is_data() { arg_val = AirTree::cast_to_data(arg_val, arg.value.tipo()) } arg_val }) .collect_vec(); if let Some(func) = builtin { AirTree::builtin(*func, tipo.clone(), func_args) } else { AirTree::call( self.build(fun.as_ref(), module_build_name, &[]), tipo.clone(), func_args, ) } } _ => { let fun_arg_types = fun .tipo() .arg_types() .expect("Expected a function type with arguments"); assert!(args.len() == fun_arg_types.len()); let func_args = args .iter() .zip(fun_arg_types) .map(|(arg, arg_tipo)| { let mut arg_val = self.build(&arg.value, module_build_name, &[]); if arg_tipo.is_data() && !arg.value.tipo().is_data() { arg_val = AirTree::cast_to_data(arg_val, arg.value.tipo()) } arg_val }) .collect_vec(); AirTree::call( self.build(fun.as_ref(), module_build_name, &[]), tipo.clone(), func_args, ) } }, TypedExpr::BinOp { name, left, right, tipo, .. } => AirTree::binop( *name, tipo.clone(), self.build(left, module_build_name, &[]), self.build(right, module_build_name, &[]), left.tipo(), ), TypedExpr::Trace { tipo, then, text, .. } => AirTree::trace( self.build(text, module_build_name, &[]), tipo.clone(), self.build(then, module_build_name, &[]), ), TypedExpr::When { subject, clauses, tipo, .. } => { if clauses.is_empty() { unreachable!("We should have one clause at least") // TODO: This whole branch can _probably_ be removed, if handle_each_clause // works fine with an empty clauses list. This is orthogonal to the // current refactoring so not changing it now. } else if clauses.len() == 1 { let subject_val = self.build(subject, module_build_name, &[]); let last_clause = &clauses[0]; // Intern vars from pattern here introduce_pattern(&mut self.interner, &last_clause.pattern); let clause_then = self.build(&last_clause.then, module_build_name, &[]); let subject_type = subject.tipo(); let tree = self.assignment( &last_clause.pattern, subject_val, clause_then, &subject_type, AssignmentProperties { value_type: subject.tipo(), kind: AssignmentKind::let_(), remove_unused: false, full_check: false, otherwise: None, }, ); // Now pop off interned pattern pop_pattern(&mut self.interner, &last_clause.pattern); tree } else { let subject_name = format!( "__subject_var_span_{}_{}", subject.location().start, subject.location().end ); self.interner.intern(subject_name.clone()); let subject_name_interned = self.interner.lookup_interned(&subject_name); let wild_card = TypedPattern::Discard { name: "".to_string(), location: Span::empty(), }; let tree_gen = TreeGen::new(&mut self.interner, &self.data_types, &wild_card); let tree = tree_gen.build_tree(&subject_name_interned, &subject.tipo(), clauses); let stick_set = TreeSet::new(); let clauses = self.handle_decision_tree( &subject_name_interned, subject.tipo(), tipo.clone(), module_build_name, tree, stick_set, ); self.interner.pop_text(subject_name); AirTree::let_assignment( subject_name_interned, self.build(subject, module_build_name, &[]), clauses, ) } } TypedExpr::If { branches, final_else, tipo, .. } => { branches.iter().rfold( self.build(final_else, module_build_name, &[]), |acc, branch| { let condition = self.build(&branch.condition, module_build_name, &[]); match &branch.is { Some((pattern, tipo)) => { introduce_pattern(&mut self.interner, pattern); self.interner.intern("acc_var".to_string()); let body = self.build(&branch.body, module_build_name, &[]); let acc_var = self.interner.lookup_interned(&"acc_var".to_string()); let tree = AirTree::let_assignment( &acc_var, // use anon function as a delay to avoid evaluating the acc AirTree::anon_func(vec![], acc, true), self.assignment( pattern, condition, body, tipo, AssignmentProperties { value_type: branch.condition.tipo(), kind: AssignmentKind::Expect { backpassing: () }, remove_unused: false, full_check: true, otherwise: Some(AirTree::local_var( &acc_var, tipo.clone(), )), }, ), ); pop_pattern(&mut self.interner, pattern); self.interner.pop_text("acc_var".to_string()); tree } None => AirTree::if_branch( tipo.clone(), condition, self.build(&branch.body, module_build_name, &[]), acc, ), } }, ) } TypedExpr::RecordAccess { tipo, index, record, .. } => { assert!( !record.tipo().is_pair(), "illegal record access on a Pair. This should have been a tuple-index access." ); if check_replaceable_opaque_type(&record.tipo(), &self.data_types) { self.build(record, module_build_name, &[]) } else { let function_name = format!("__access_index_{}", *index); if self.code_gen_functions.get(&function_name).is_none() { let mut body = AirTree::local_var("__fields", Type::list(Type::data())); for _ in 0..*index { body = AirTree::builtin( DefaultFunction::TailList, Type::list(Type::data()), vec![body], ) } body = AirTree::builtin( DefaultFunction::HeadList, Type::data(), vec![body], ); self.code_gen_functions.insert( function_name.clone(), CodeGenFunction::Function { body, params: vec!["__fields".to_string()], }, ); } let list_of_fields = AirTree::call( self.special_functions .use_function_tree(CONSTR_FIELDS_EXPOSER.to_string()), Type::list(Type::data()), vec![self.build(record, module_build_name, &[])], ); AirTree::index_access(function_name, tipo.clone(), list_of_fields) } } TypedExpr::ModuleSelect { tipo, module_name, constructor, .. } => match constructor { ModuleValueConstructor::Record { name, arity, tipo, field_map, .. } => { let val_constructor = { let data_type = lookup_data_type_by_tipo(&self.data_types, tipo); ValueConstructor::public( tipo.clone(), ValueConstructorVariant::Record { name: name.clone(), arity: *arity, field_map: field_map.clone(), location: Span::empty(), module: module_name.clone(), constructors_count: data_type .expect("Created a module type without a definition?") .constructors .len() as u16, }, ) }; AirTree::var(val_constructor, name, "") } ModuleValueConstructor::Fn { name, module, .. } => { let func = self.functions.get(&FunctionAccessKey { // NOTE: This is needed because we register prelude functions under an // empty module name. This is to facilitate their access when used // directly. Note that, if we weren't doing this particular // transformation, we would need to do the other direction anyway: // // if module_name.is_empty() { PRELUDE.to_string() } else { module_name.clone() } // // So either way, we need to take care of this. module_name: if module_name == PRELUDE { String::new() } else { module_name.clone() }, function_name: name.clone(), }); let type_info = self.module_types.get(module_name.as_str()).unwrap(); let value = type_info.values.get(name).unwrap(); if let Some(_func) = func { AirTree::var( ValueConstructor::public(tipo.clone(), value.variant.clone()), format!("{module}_{name}"), "", ) } else { let ValueConstructorVariant::ModuleFn { builtin: Some(builtin), .. } = &value.variant else { unreachable!("Didn't find the function definition.") }; AirTree::builtin(*builtin, tipo.clone(), vec![]) } } ModuleValueConstructor::Constant { module, name, .. } => { let type_info = self.module_types.get(module_name.as_str()).unwrap(); let value = type_info.values.get(name).unwrap(); AirTree::var( ValueConstructor::public(tipo.clone(), value.variant.clone()), format!("{module}_{name}"), "", ) } }, TypedExpr::Pair { tipo, fst, snd, .. } => AirTree::pair( self.build(fst, module_build_name, &[]), self.build(snd, module_build_name, &[]), tipo.clone(), ), TypedExpr::Tuple { tipo, elems, .. } => AirTree::tuple( elems .iter() .map(|elem| self.build(elem, module_build_name, &[])) .collect_vec(), tipo.clone(), ), TypedExpr::TupleIndex { index, tuple, tipo, .. } => { if tuple.tipo().is_pair() { AirTree::pair_index( *index, tipo.clone(), self.build(tuple, module_build_name, &[]), ) } else { let function_name = format!("__access_index_{}", *index); if self.code_gen_functions.get(&function_name).is_none() { let mut body = AirTree::local_var("__fields", Type::list(Type::data())); for _ in 0..*index { body = AirTree::builtin( DefaultFunction::TailList, Type::list(Type::data()), vec![body], ) } body = AirTree::builtin( DefaultFunction::HeadList, Type::data(), vec![body], ); self.code_gen_functions.insert( function_name.clone(), CodeGenFunction::Function { body, params: vec!["__fields".to_string()], }, ); } AirTree::index_access( function_name, tipo.clone(), self.build(tuple, module_build_name, &[]), ) } } TypedExpr::ErrorTerm { tipo, .. } => AirTree::error(tipo.clone(), false), TypedExpr::RecordUpdate { tipo, spread, args, .. } => { let mut index_types = vec![]; let mut update_args = vec![]; let mut highest_index = 0; for arg in args .iter() .sorted_by(|arg1, arg2| arg1.index.cmp(&arg2.index)) { let arg_val = self.build(&arg.value, module_build_name, &[]); if arg.index > highest_index { highest_index = arg.index; } index_types.push((arg.index, arg.value.tipo())); update_args.push(arg_val); } AirTree::record_update( index_types, highest_index, tipo.clone(), self.build(spread, module_build_name, &[]), update_args, ) } TypedExpr::UnOp { value, op, .. } => { AirTree::unop(*op, self.build(value, module_build_name, &[])) } TypedExpr::CurvePoint { point, .. } => AirTree::curve(*point.as_ref()), } } } pub fn assignment( &mut self, pattern: &TypedPattern, value: AirTree, then: AirTree, tipo: &Rc, props: AssignmentProperties, ) -> AirTree { assert!( match &value { AirTree::Var { name, .. } if props.kind.is_let() => { name != DISCARDED } _ => true, }, "No discard expressions or let bindings should be in the tree at this point." ); // Cast value to or from data so we don't have to worry from this point onward let assign_casted_value = |name, value, then| { if props.value_type.is_data() && props.kind.is_expect() && !tipo.is_data() { if props.otherwise.is_some() { AirTree::soft_cast_assignment( name, tipo.clone(), value, then, props.otherwise.as_ref().unwrap().clone(), ) } else { AirTree::let_assignment( name, AirTree::cast_from_data(value, tipo.clone(), true), then, ) } } else if !props.value_type.is_data() && tipo.is_data() { AirTree::let_assignment( name, AirTree::cast_to_data(value, props.value_type.clone()), then, ) } else { AirTree::let_assignment(name, value, then) } }; let otherwise = match &props.otherwise { Some(x) => x.clone(), // (delay (error )) None => AirTree::anon_func(vec![], AirTree::error(Type::void(), false), true), }; match pattern { Pattern::Int { value: expected_int, location, .. } => { let name = format!( "__expected_by_{}_span_{}_{}", expected_int, location.start, location.end ); let expect = AirTree::binop( BinOp::Eq, Type::bool(), AirTree::int(expected_int), AirTree::local_var(&name, Type::int()), Type::int(), ); assign_casted_value( name, value, AirTree::assert_bool(true, expect, then, otherwise), ) } Pattern::ByteArray { value: expected_bytes, location, .. } => { let name = format!("__expected_bytes_span_{}_{}", location.start, location.end); let expect = AirTree::binop( BinOp::Eq, Type::bool(), AirTree::byte_array(expected_bytes.clone()), AirTree::local_var(&name, Type::byte_array()), Type::byte_array(), ); assign_casted_value( name, value, AirTree::assert_bool(true, expect, then, otherwise), ) } Pattern::Var { name, .. } => { let name = self.interner.lookup_interned(name); if props.full_check { let mut index_map = IndexMap::new(); let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types, true); let val = AirTree::local_var(&name, tipo.clone()); if non_opaque_tipo.is_primitive() { assign_casted_value(name.clone(), value, then) } else { assign_casted_value( name, value, self.expect_type_assign( &non_opaque_tipo, val, &mut index_map, pattern.location(), then, props.otherwise.clone(), ), ) } } else { assign_casted_value(name.clone(), value, then) } } Pattern::Assign { name, pattern, .. } => { let name = self.interner.lookup_interned(name); // Don't need any data casting for Assign let inner_pattern = self.assignment( pattern, AirTree::local_var(&name, tipo.clone()), then, tipo, props, ); AirTree::let_assignment(name, value, inner_pattern) } Pattern::Discard { name, .. } => { if props.full_check { let name = format!("__discard_expect_{}", name); let name_interned = introduce_name(&mut self.interner, &name); let mut index_map = IndexMap::new(); let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types, true); let val = AirTree::local_var(&name_interned, tipo.clone()); let tree = if non_opaque_tipo.is_primitive() { assign_casted_value(name_interned, value, then) } else { assign_casted_value( name_interned, value, self.expect_type_assign( &non_opaque_tipo, val, &mut index_map, pattern.location(), then, props.otherwise.clone(), ), ) }; self.interner.pop_text(name); tree } else if !props.remove_unused { //No need to intern, name not used assign_casted_value(name.clone(), value, then) } else { then } } Pattern::List { elements, tail, .. } => { assert!(tipo.is_list()); assert!(props.kind.is_expect()); let list_elem_types = tipo.get_inner_types(); let list_elem_type = list_elem_types .first() .unwrap_or_else(|| unreachable!("No list element type?")); let mut elems = vec![]; // If Some then push tail onto elems let then = match tail { None => then, Some(tail) => { let (tail_name, tail_name_interned) = match tail.as_ref() { Pattern::Var { name, .. } => { (None, self.interner.lookup_interned(name)) } // This Pattern one doesn't even make sense Pattern::Assign { .. } => { todo!("Has this ever been reached before?") } Pattern::Discard { name, .. } => { if props.full_check { ( Some(format!("__discard_{}_tail", name)), introduce_name( &mut self.interner, &format!("__discard_{}_tail", name), ), ) } else { (None, DISCARDED.to_string()) } } _ => unreachable!(), }; let val = AirTree::local_var(&tail_name_interned, tipo.clone()); let then = if tail_name_interned != DISCARDED { self.assignment( tail, val, then, tipo, AssignmentProperties { value_type: tipo.clone(), kind: props.kind, // The reason the top level of recursion might have remove_unused // false is to deal with expect _ = thing // next_thing remove_unused: true, full_check: props.full_check, otherwise: props.otherwise.clone(), }, ) } else { then }; elems.push(tail_name_interned); if let Some(tail_name) = tail_name { self.interner.pop_text(tail_name); } then } }; let then = elements .iter() .enumerate() .rfold(then, |then, (index, elem)| { let (elem_name, elem_name_interned) = match elem { Pattern::Var { name, .. } => { (None, self.interner.lookup_interned(name)) } Pattern::Assign { name, .. } => { (None, self.interner.lookup_interned(name)) } Pattern::Discard { name, .. } => { if props.full_check { ( Some(format!("__discard_{}_{}", name, index)), introduce_name( &mut self.interner, &format!("__discard_{}_{}", name, index), ), ) } else { (None, DISCARDED.to_string()) } } _ => { let name = format!( "elem_{}_span_{}_{}", index, elem.location().start, elem.location().end ); let interned = introduce_name(&mut self.interner, &name); (Some(name), interned) } }; let val = AirTree::local_var(&elem_name_interned, list_elem_type.clone()); let then = if elem_name_interned != DISCARDED { self.assignment( elem, val, then, list_elem_type, AssignmentProperties { value_type: list_elem_type.clone(), kind: props.kind, remove_unused: true, full_check: props.full_check, otherwise: props.otherwise.clone(), }, ) } else { then }; elems.push(elem_name_interned); if let Some(elem_name) = elem_name { self.interner.pop_text(elem_name); } then }); elems.reverse(); let name = format!( "__List_span_{}_{}", pattern.location().start, pattern.location().end ); let name_interned = introduce_name(&mut self.interner, &name); let casted_var = AirTree::local_var(&name_interned, tipo.clone()); let tree = if elements.is_empty() { assign_casted_value( name_interned, value, AirTree::list_empty(casted_var, then, otherwise), ) } else { assign_casted_value( name_interned, value, AirTree::list_access( elems, tipo.clone(), tail.is_some(), casted_var, if props.full_check { ExpectLevel::Full } else { ExpectLevel::Items }, then, otherwise, ), ) }; self.interner.pop_text(name); tree } Pattern::Pair { fst, snd, location: _, } => { let mut type_map: IndexMap> = IndexMap::new(); for (index, arg) in tipo.get_inner_types().iter().enumerate() { let field_type = arg.clone(); type_map.insert(index, field_type); } assert!(type_map.len() == 2); let mut fields = vec![]; let then = [fst, snd] .iter() .enumerate() .rfold(then, |then, (field_index, arg)| { let (field_name, field_name_interned) = match arg.as_ref() { Pattern::Var { name, .. } => { (None, self.interner.lookup_interned(name)) } Pattern::Assign { name, .. } => { (None, self.interner.lookup_interned(name)) } Pattern::Discard { name, .. } => { if props.full_check { ( Some(format!("__discard_{}_{}", name, field_index)), introduce_name( &mut self.interner, &format!("__discard_{}_{}", name, field_index), ), ) } else { (None, DISCARDED.to_string()) } } _ => { let name = format!( "field_{}_span_{}_{}", field_index, arg.location().start, arg.location().end ); let interned = introduce_name(&mut self.interner, &name); (Some(name), interned) } }; let arg_type = type_map.get(&field_index).unwrap_or_else(|| { unreachable!("Missing type for field {} of Pair", field_index,) }); let val = AirTree::local_var(&field_name_interned, arg_type.clone()); let then = if field_name_interned != DISCARDED { self.assignment( arg, val, then, arg_type, AssignmentProperties { value_type: arg_type.clone(), kind: props.kind, remove_unused: true, full_check: props.full_check, otherwise: props.otherwise.clone(), }, ) } else { then }; fields.push((field_index, field_name_interned, arg_type.clone())); if let Some(field_name) = field_name { self.interner.pop_text(field_name); } then }); fields.reverse(); // This `value` is either value param that was passed in or // local var let constructor_name = format!( "Pair_span_{}_{}", pattern.location().start, pattern.location().end ); let constructor_name_interned = introduce_name(&mut self.interner, &constructor_name); let local_value = AirTree::local_var(&constructor_name_interned, tipo.clone()); let then = AirTree::pair_access( fields .first() .map(|x| { if x.1 == DISCARDED { None } else { Some(x.1.clone()) } }) .unwrap(), fields .last() .map(|x| { if x.1 == DISCARDED { None } else { Some(x.1.clone()) } }) .unwrap(), tipo.clone(), local_value, props.full_check, then, otherwise, ); let tree = assign_casted_value(constructor_name_interned, value, then); self.interner.pop_text(constructor_name); tree } Pattern::Constructor { constructor: PatternConstructor::Record { name, .. }, .. } if tipo.is_bool() => { assert!(props.kind.is_expect()); let name_var = format!( "__Bool_{}_{}", pattern.location().start, pattern.location().end ); let local_var = AirTree::local_var(&name_var, tipo.clone()); assign_casted_value( name_var, value, AirTree::assert_bool(name == "True", local_var, then, otherwise), ) } Pattern::Constructor { .. } if tipo.is_void() => { // Void type is checked when casting from data // So we just assign the value and move on assign_casted_value(DISCARDED.to_string(), value, then) } Pattern::Constructor { arguments, constructor: PatternConstructor::Record { name, field_map }, tipo: constr_tipo, .. } => { // Constr execution branch let field_map = field_map.clone(); let mut type_map: IndexMap> = IndexMap::new(); for (index, arg) in constr_tipo .arg_types() .expect("Mismatched type") .iter() .enumerate() { let field_type = arg.clone(); type_map.insert(index, field_type); } assert!( type_map.len() >= arguments.len(), "type map len: {}, arguments len: {}; for constructor {:?}", type_map.len(), arguments.len(), name, ); let mut fields = vec![]; let then = arguments .iter() .enumerate() .rfold(then, |then, (index, arg)| { let label = arg.label.clone().unwrap_or_default(); let field_index = if let Some(field_map) = &field_map { *field_map.fields.get(&label).map(|x| &x.0).unwrap_or(&index) } else { index }; let (field_name, field_name_interned) = match &arg.value { Pattern::Var { name, .. } => { (None, self.interner.lookup_interned(name)) } Pattern::Assign { name, .. } => { (None, self.interner.lookup_interned(name)) } Pattern::Discard { name, .. } => { if props.full_check { ( Some(format!("__discard_{}_{}", name, index)), introduce_name( &mut self.interner, &format!("__discard_{}_{}", name, index), ), ) } else { (None, DISCARDED.to_string()) } } _ => { let name = format!( "field_{}_span_{}_{}", field_index, arg.value.location().start, arg.value.location().end ); let interned = introduce_name(&mut self.interner, &name); (Some(name), interned) } }; let arg_type = type_map.get(&field_index).unwrap_or_else(|| { unreachable!( "Missing type for field {} of constr {}", field_index, name ) }); let val = AirTree::local_var(&field_name_interned, arg_type.clone()); let then = if field_name_interned != DISCARDED { self.assignment( &arg.value, val, then, arg_type, AssignmentProperties { value_type: arg_type.clone(), kind: props.kind, remove_unused: true, full_check: props.full_check, otherwise: props.otherwise.clone(), }, ) } else { then }; fields.push((field_index, field_name_interned, arg_type.clone())); if let Some(field_name) = field_name { self.interner.pop_text(field_name); } then }); fields.reverse(); // This `value` is either value param that was passed in or // local var let constructor_name = format!( "__constructor_{}_span_{}_{}", name, pattern.location().start, pattern.location().end ); let subject_name = format!( "__subject_{}_span_{}_{}", name, pattern.location().start, pattern.location().end ); let constructor_name_interned = introduce_name(&mut self.interner, &constructor_name); let subject_name_interned = introduce_name(&mut self.interner, &subject_name); let local_value = AirTree::local_var(&constructor_name_interned, tipo.clone()); let then = if check_replaceable_opaque_type(tipo, &self.data_types) { AirTree::let_assignment(&fields[0].1, local_value, then) } else { AirTree::fields_expose( fields, local_value, props.full_check, then, otherwise.clone(), ) }; let data_type = lookup_data_type_by_tipo(&self.data_types, tipo) .unwrap_or_else(|| unreachable!("Failed to find definition for {}", name)); let then = if props.kind.is_expect() && (data_type.constructors.len() > 1 || props.full_check || data_type.is_never()) { let (index, _) = data_type .constructors .iter() .enumerate() .find(|(_, constr)| constr.name == *name) .unwrap_or_else(|| { panic!("Found constructor type {} with 0 constructors", name) }); AirTree::when( &subject_name_interned, Type::void(), tipo.clone(), AirTree::local_var(&constructor_name_interned, tipo.clone()), AirTree::clause( &subject_name_interned, AirTree::int(index), tipo.clone(), then, otherwise, ), ) } else { assert!( data_type.constructors.len() == 1 || data_type.is_never(), "attempted let-assignment on a type with more or less than 1 constructor: \nis_expect? {}\nfull_check? {}\ndata_type={data_type:#?}\n{}", props.kind.is_expect(), props.full_check, name, ); then }; let tree = assign_casted_value(constructor_name_interned, value, then); self.interner.pop_text(constructor_name); self.interner.pop_text(subject_name); tree } Pattern::Tuple { elems, location, .. } => { let mut type_map: IndexMap> = IndexMap::new(); for (index, arg) in tipo.get_inner_types().iter().enumerate() { let field_type = arg.clone(); type_map.insert(index, field_type); } assert!(type_map.len() == elems.len()); let mut fields = vec![]; let then = elems.iter().enumerate().rfold(then, |then, (index, arg)| { let (tuple_name, tuple_name_interned) = match &arg { Pattern::Var { name, .. } => (None, self.interner.lookup_interned(name)), Pattern::Assign { name, .. } => (None, self.interner.lookup_interned(name)), Pattern::Discard { name, .. } => { if props.full_check { ( Some(format!("__discard_{}_{}", name, index)), introduce_name( &mut self.interner, &format!("__discard_{}_{}", name, index), ), ) } else { (None, DISCARDED.to_string()) } } _ => { let name = format!( "tuple_{}_span_{}_{}", index, arg.location().start, arg.location().end ); let interned = introduce_name(&mut self.interner, &name); (Some(name), interned) } }; let arg_type = type_map.get(&index).unwrap_or_else(|| { unreachable!( "Missing type for tuple index {} of tuple_span_{}_{}", index, location.start, location.end ) }); let val = AirTree::local_var(&tuple_name_interned, arg_type.clone()); let then = if DISCARDED != tuple_name_interned { self.assignment( arg, val, then, arg_type, AssignmentProperties { value_type: arg_type.clone(), kind: props.kind, remove_unused: true, full_check: props.full_check, otherwise: props.otherwise.clone(), }, ) } else { then }; fields.push(tuple_name_interned); if let Some(tuple_name) = tuple_name { self.interner.pop_text(tuple_name); } then }); fields.reverse(); // This `value` is either value param that was passed in or local var let name = format!( "__Tuple_span_{}_{}", pattern.location().start, pattern.location().end ); let name_interned = introduce_name(&mut self.interner, &name); let local_var = AirTree::local_var(&name_interned, tipo.clone()); let tree = assign_casted_value( name_interned, value, AirTree::tuple_access( fields, tipo.clone(), local_var, props.full_check, then, otherwise, ), ); self.interner.pop_text(name); tree } } } pub fn expect_type_assign( &mut self, tipo: &Rc, value: AirTree, defined_data_types: &mut IndexMap, location: Span, then: AirTree, otherwise: Otherwise, ) -> AirTree { assert!( tipo.get_generic().is_none(), "left-hand side of expect is generic: {}", tipo.to_pretty(0) ); // Shouldn't be needed but still here just in case // this function is called from anywhere else besides assignment let tipo = &convert_opaque_type(tipo, &self.data_types, true); let uplc_type = tipo.get_uplc_type(); match uplc_type { // primitives // Untyped Data Some( UplcType::Integer | UplcType::String | UplcType::Bool | UplcType::ByteString | UplcType::Unit | UplcType::Bls12_381G1Element | UplcType::Bls12_381G2Element | UplcType::Bls12_381MlResult | UplcType::Data, ) => then, // Map type Some(UplcType::List(_)) if tipo.is_map() => { assert!(!tipo.get_inner_types().is_empty()); let inner_list_type = &tipo.get_inner_types()[0]; let inner_pair_types = inner_list_type.get_inner_types(); assert!(inner_pair_types.len() == 2); let map_name = format!("__map_span_{}_{}", location.start, location.end); let pair_name = format!("__pair_span_{}_{}", location.start, location.end); let fst_name = format!("__pair_fst_span_{}_{}", location.start, location.end); let snd_name = format!("__pair_snd_span_{}_{}", location.start, location.end); let curried_expect_on_list = "__curried_expect_on_list".to_string(); let list = "__list".to_string(); let map_name_interned = introduce_name(&mut self.interner, &map_name); let pair_name_interned = introduce_name(&mut self.interner, &pair_name); let fst_name_interned = introduce_name(&mut self.interner, &fst_name); let snd_name_interned = introduce_name(&mut self.interner, &snd_name); let curried_expect_on_list_interned = introduce_name(&mut self.interner, &curried_expect_on_list); let list_interned = introduce_name(&mut self.interner, &list); let expect_snd = self.expect_type_assign( &inner_pair_types[1], AirTree::local_var(snd_name_interned.clone(), inner_pair_types[1].clone()), defined_data_types, location, AirTree::call( AirTree::local_var(&curried_expect_on_list_interned, Type::void()), Type::void(), vec![AirTree::builtin( DefaultFunction::TailList, Type::list(Type::data()), vec![AirTree::local_var(&list_interned, tipo.clone())], )], ), otherwise.clone(), ); let expect_fst = self.expect_type_assign( &inner_pair_types[0], AirTree::local_var(fst_name_interned.clone(), inner_pair_types[0].clone()), defined_data_types, location, expect_snd, otherwise.clone(), ); let unwrap_function = AirTree::anon_func( vec![list_interned.clone(), curried_expect_on_list_interned], AirTree::list_empty( AirTree::local_var(&list_interned, tipo.clone()), then, AirTree::anon_func( vec![], AirTree::let_assignment( &pair_name_interned, AirTree::builtin( DefaultFunction::HeadList, Type::pair(Type::data(), Type::data()), vec![AirTree::local_var(list_interned, tipo.clone())], ), AirTree::pair_access( Some(fst_name_interned), Some(snd_name_interned), inner_list_type.clone(), AirTree::local_var( &pair_name_interned, inner_list_type.clone(), ), true, expect_fst, otherwise.unwrap_or_else(DELAY_ERROR), ), ), true, ), ), false, ); let function = self.code_gen_functions.get(EXPECT_ON_LIST); // This function can be defined here on in the branch below if function.is_none() { let expect_list_func = AirTree::expect_on_list2(); self.code_gen_functions.insert( EXPECT_ON_LIST.to_string(), CodeGenFunction::Function { body: expect_list_func, params: vec!["__list_to_check".to_string(), "__check_with".to_string()], }, ); } if let Some(counter) = defined_data_types.get_mut(EXPECT_ON_LIST) { *counter += 1 } else { defined_data_types.insert(EXPECT_ON_LIST.to_string(), 1); } let func_call = AirTree::call( AirTree::var( ValueConstructor::public( Type::void(), ValueConstructorVariant::ModuleFn { name: EXPECT_ON_LIST.to_string(), field_map: None, module: "".to_string(), arity: 1, location, builtin: None, }, ), EXPECT_ON_LIST, "", ), Type::void(), vec![ AirTree::local_var(&map_name_interned, tipo.clone()), unwrap_function, ], ); let tree = AirTree::let_assignment(map_name_interned, value, func_call); self.interner.pop_text(map_name); self.interner.pop_text(pair_name); self.interner.pop_text(fst_name); self.interner.pop_text(snd_name); self.interner.pop_text(curried_expect_on_list); self.interner.pop_text(list); tree } // Tuple type Some(UplcType::List(_)) if tipo.is_tuple() => { let tuple_inner_types = tipo.get_inner_types(); assert!(!tuple_inner_types.is_empty()); let tuple_name = format!("__tuple_span_{}_{}", location.start, location.end); let tuple_name_interned = introduce_name(&mut self.interner, &tuple_name); let mut tuple_expect_items = vec![]; let then = tuple_inner_types .iter() .enumerate() .rfold(then, |then, (index, arg)| { let tuple_index_name = format!( "__tuple_index_{}_span_{}_{}", index, location.start, location.end ); let tuple_index_name_interned = introduce_name(&mut self.interner, &tuple_index_name); let expect_tuple_item = self.expect_type_assign( arg, AirTree::local_var(&tuple_index_name_interned, arg.clone()), defined_data_types, location, then, otherwise.clone(), ); tuple_expect_items.push(tuple_index_name_interned); self.interner.pop_text(tuple_index_name); expect_tuple_item }); tuple_expect_items.reverse(); let tuple_access = AirTree::tuple_access( tuple_expect_items, tipo.clone(), AirTree::local_var(&tuple_name_interned, tipo.clone()), true, then, otherwise.unwrap_or_else(DELAY_ERROR), ); let tree = AirTree::let_assignment(tuple_name_interned, value, tuple_access); self.interner.pop_text(tuple_name); tree } // Regular List type Some(UplcType::List(_)) => { assert!(!tipo.get_inner_types().is_empty()); let inner_list_type = &tipo.get_inner_types()[0]; if inner_list_type.is_data() { then } else { let list_name = format!("__list_span_{}_{}", location.start, location.end); let item_name = format!("__item_span_{}_{}", location.start, location.end); let list = "__list".to_string(); let curried_func = "__curried_expect_on_list".to_string(); let list_name_interned = introduce_name(&mut self.interner, &list_name); let item_name_interned = introduce_name(&mut self.interner, &item_name); let list_interned = introduce_name(&mut self.interner, &list); let curried_func_interned = introduce_name(&mut self.interner, &curried_func); let unwrap_function = AirTree::anon_func( vec![list_interned.clone(), curried_func_interned.clone()], AirTree::list_empty( AirTree::local_var(&list_interned, tipo.clone()), then, AirTree::anon_func( vec![], AirTree::let_assignment( &item_name_interned, AirTree::builtin( DefaultFunction::HeadList, Type::data(), vec![AirTree::local_var(&list_interned, tipo.clone())], ), AirTree::soft_cast_assignment( &item_name_interned, inner_list_type.clone(), AirTree::local_var(&item_name_interned, Type::data()), self.expect_type_assign( inner_list_type, AirTree::local_var( &item_name_interned, inner_list_type.clone(), ), defined_data_types, location, AirTree::call( AirTree::local_var( curried_func_interned, Type::void(), ), Type::void(), vec![AirTree::builtin( DefaultFunction::TailList, Type::list(Type::data()), vec![AirTree::local_var( list_interned, tipo.clone(), )], )], ), otherwise.clone(), ), otherwise.unwrap_or_else(DELAY_ERROR), ), ), true, ), ), false, ); let function = self.code_gen_functions.get(EXPECT_ON_LIST); if function.is_none() { let expect_list_func = AirTree::expect_on_list2(); self.code_gen_functions.insert( EXPECT_ON_LIST.to_string(), CodeGenFunction::Function { body: expect_list_func, params: vec![ "__list_to_check".to_string(), "__check_with".to_string(), ], }, ); } if let Some(counter) = defined_data_types.get_mut(EXPECT_ON_LIST) { *counter += 1 } else { defined_data_types.insert(EXPECT_ON_LIST.to_string(), 1); } let func_call = AirTree::call( AirTree::var( ValueConstructor::public( Type::void(), ValueConstructorVariant::ModuleFn { name: EXPECT_ON_LIST.to_string(), field_map: None, module: "".to_string(), arity: 1, location, builtin: None, }, ), EXPECT_ON_LIST, "", ), Type::void(), vec![ AirTree::local_var(&list_name_interned, tipo.clone()), unwrap_function, ], ); let tree = AirTree::let_assignment(list_name_interned, value, func_call); self.interner.pop_text(list_name); self.interner.pop_text(item_name); self.interner.pop_text(list); self.interner.pop_text(curried_func); tree } } // Pair type Some(UplcType::Pair(_, _)) => { let tuple_inner_types = tipo.get_inner_types(); assert!(tuple_inner_types.len() == 2); let pair_name = format!("__pair_span_{}_{}", location.start, location.end); let fst_name = format!("__pair_fst_span_{}_{}", location.start, location.end); let snd_name = format!("__pair_snd_span_{}_{}", location.start, location.end); let pair_name_interned = introduce_name(&mut self.interner, &pair_name); let fst_name_interned = introduce_name(&mut self.interner, &fst_name); let snd_name_interned = introduce_name(&mut self.interner, &snd_name); let expect_snd = self.expect_type_assign( &tuple_inner_types[1], AirTree::local_var(snd_name_interned.clone(), tuple_inner_types[1].clone()), defined_data_types, location, then, otherwise.clone(), ); let expect_fst = self.expect_type_assign( &tuple_inner_types[0], AirTree::local_var(fst_name_interned.clone(), tuple_inner_types[0].clone()), defined_data_types, location, expect_snd, otherwise.clone(), ); let pair_access = AirTree::pair_access( Some(fst_name_interned), Some(snd_name_interned), tipo.clone(), AirTree::local_var(&pair_name_interned, tipo.clone()), true, expect_fst, otherwise.unwrap_or_else(DELAY_ERROR), ); let tree = AirTree::let_assignment(pair_name_interned, value, pair_access); self.interner.pop_text(pair_name); self.interner.pop_text(fst_name); self.interner.pop_text(snd_name); tree } // Constr type None => { let data_type = lookup_data_type_by_tipo(&self.data_types, tipo).unwrap_or_else(|| { unreachable!("We need a data type definition for type {:#?}", tipo) }); let data_type_variant = tipo .get_inner_types() .iter() .map(|arg| get_arg_type_name(arg)) .join(DISCARDED); assert!(data_type.typed_parameters.len() == tipo.arg_types().unwrap().len()); let mono_types: IndexMap> = if !data_type.typed_parameters.is_empty() { data_type .typed_parameters .iter() .zip(tipo.arg_types().unwrap()) .flat_map(|item| get_generic_id_and_type(item.0, &item.1)) .collect() } else { IndexMap::new() }; let data_type_name = if otherwise.is_some() { format!( "__expect_{}_{}_otherwise", data_type.name, data_type_variant ) } else { format!("__expect_{}_{}", data_type.name, data_type_variant) }; let function = self.code_gen_functions.get(&data_type_name); // mutate code_gen_funcs and defined_data_types in this if branch if function.is_none() && defined_data_types.get(&data_type_name).is_none() { defined_data_types.insert(data_type_name.clone(), 1); let current_defined = defined_data_types.clone(); let mut diff_defined_types = vec![]; let var_then = AirTree::call( AirTree::local_var("then_delayed", Type::void()), Type::void(), vec![], ); let otherwise_delayed = otherwise .as_ref() .map(|_| AirTree::local_var("otherwise_delayed", Type::void())); let is_never = data_type.is_never(); let constr_clauses = data_type.constructors.iter().enumerate().rfold( otherwise_delayed.clone().unwrap_or_else(DELAY_ERROR), |acc, (index, constr)| { // NOTE: For the Never type, we have an placeholder first constructor // that must be ignored. The Never type is considered to have only one // constructor starting at index 1 so it shouldn't be possible to // cast from Data into the first constructor. There's virtually no // constructor at index 0. if is_never && index == 0 { return acc; } let mut constr_args = vec![]; let constr_then = constr.arguments.iter().enumerate().rfold( var_then.clone(), |then, (index, arg)| { let arg_name = arg.label.clone().unwrap_or(format!("__field_{index}")); let arg_tipo = find_and_replace_generics(&arg.tipo, &mono_types); constr_args.push((index, arg_name.clone(), arg_tipo.clone())); self.expect_type_assign( &arg_tipo.clone(), AirTree::local_var(arg_name, arg_tipo), defined_data_types, location, then, otherwise_delayed.clone(), ) }, ); constr_args.reverse(); let then = if constr_args.is_empty() { AirTree::fields_empty( AirTree::local_var( format!( "__constr_var_span_{}_{}", location.start, location.end ), tipo.clone(), ), constr_then, otherwise_delayed.clone().unwrap_or_else(DELAY_ERROR), ) } else { AirTree::fields_expose( constr_args, AirTree::local_var( format!( "__constr_var_span_{}_{}", location.start, location.end ), tipo.clone(), ), true, constr_then, otherwise_delayed.clone().unwrap_or_else(DELAY_ERROR), ) }; AirTree::anon_func( vec![], AirTree::clause( format!("__subject_span_{}_{}", location.start, location.end), AirTree::int(index), tipo.clone(), then, acc, ), true, ) }, ); let when_expr = AirTree::when( format!("__subject_span_{}_{}", location.start, location.end), Type::void(), tipo.clone(), AirTree::local_var( format!("__constr_var_span_{}_{}", location.start, location.end), tipo.clone(), ), AirTree::call(constr_clauses, Type::void(), vec![]), ); let func_body = AirTree::let_assignment( format!("__constr_var_span_{}_{}", location.start, location.end), AirTree::local_var("__param_0", tipo.clone()), when_expr, ); for (inner_data_type, inner_count) in defined_data_types.iter() { if let Some(prev_count) = current_defined.get(inner_data_type) { if inner_count - prev_count > 0 { diff_defined_types.push(inner_data_type.to_string()); } } else { diff_defined_types.push(inner_data_type.to_string()); } } let code_gen_func = CodeGenFunction::Function { body: func_body, params: if otherwise.is_some() { vec![ "__param_0".to_string(), "then_delayed".to_string(), "otherwise_delayed".to_string(), ] } else { vec!["__param_0".to_string(), "then_delayed".to_string()] }, }; self.code_gen_functions .insert(data_type_name.clone(), code_gen_func); } else if let Some(counter) = defined_data_types.get_mut(&data_type_name) { *counter += 1; } else { defined_data_types.insert(data_type_name.to_string(), 1); } let args = if let Some(otherwise) = otherwise { vec![value, AirTree::anon_func(vec![], then, true), otherwise] } else { vec![value, AirTree::anon_func(vec![], then, true)] }; let module_fn = ValueConstructorVariant::ModuleFn { name: data_type_name.to_string(), field_map: None, module: "".to_string(), arity: args.len(), location, builtin: None, }; let func_var = AirTree::var( ValueConstructor::public(tipo.clone(), module_fn), data_type_name, "", ); AirTree::call(func_var, Type::void(), args) } } } fn handle_decision_tree( &mut self, subject_name: &String, subject_tipo: Rc, return_tipo: Rc, module_build_name: &str, tree: decision_tree::DecisionTree<'_>, mut stick_set: TreeSet, ) -> AirTree { match tree { DecisionTree::Switch { path, mut cases, default, } => { //Current path to test let current_tipo = get_tipo_by_path(subject_tipo.clone(), &path); let builtins_path = Builtins::new_from_path(subject_tipo.clone(), path); let current_subject_name = if builtins_path.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, builtins_path.to_string()) }; // Transition process from previous to current let builtins_to_add = stick_set.diff_union_builtins(builtins_path.clone()); // Previous path to apply the transition process too let prev_builtins = Builtins { vec: builtins_path.vec[0..(builtins_path.len() - builtins_to_add.len())] .to_vec(), }; let prev_subject_name = if prev_builtins.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, prev_builtins.to_string()) }; let prev_tipo = prev_builtins .vec .last() .map_or(subject_tipo.clone(), |last| last.tipo()); let data_type = lookup_data_type_by_tipo(&self.data_types, ¤t_tipo); let last_clause = if data_type .as_ref() .map_or(true, |d| d.constructors.len() != cases.len()) { *default.unwrap() } else { cases.pop().unwrap().1 }; let last_clause = self.handle_decision_tree( subject_name, subject_tipo.clone(), return_tipo.clone(), module_build_name, last_clause, stick_set.clone(), ); let test_subject_name = if data_type.is_some() { format!("{}_index", current_subject_name.clone(),) } else { current_subject_name.clone() }; let clauses = cases.into_iter().rfold(last_clause, |acc, (case, then)| { let case_air = self.handle_decision_tree( subject_name, subject_tipo.clone(), return_tipo.clone(), module_build_name, then, stick_set.clone(), ); AirTree::clause( test_subject_name.clone(), case.get_air_pattern(current_tipo.clone()), current_tipo.clone(), case_air, AirTree::anon_func(vec![], acc, true), ) }); let when_air_clauses = AirTree::when( test_subject_name, return_tipo.clone(), current_tipo.clone(), AirTree::local_var(current_subject_name, current_tipo.clone()), clauses, ); builtins_to_add.to_air( // The only reason I pass this in is to ensure I signal // whether or not constr_fields_exposer was used. I could // probably optimize this part out to simplify codegen in // the future &mut self.special_functions, prev_subject_name, prev_tipo, when_air_clauses, ) } DecisionTree::ListSwitch { path, cases, tail_cases, default, } => { //Current path to test let current_tipo = get_tipo_by_path(subject_tipo.clone(), &path); let builtins_path = Builtins::new_from_path(subject_tipo.clone(), path); let current_subject_name = if builtins_path.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, builtins_path.to_string()) }; // Transition process from previous to current let builtins_to_add = stick_set.diff_union_builtins(builtins_path.clone()); // Previous path to apply the transition process too let prev_builtins = Builtins { vec: builtins_path.vec[0..(builtins_path.len() - builtins_to_add.len())] .to_vec(), }; let prev_subject_name = if prev_builtins.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, prev_builtins.to_string()) }; let prev_tipo = prev_builtins .vec .last() .map_or(subject_tipo.clone(), |last| last.tipo()); let longest_pattern = cases.iter().chain(tail_cases.iter()).fold( 0, |longest, (case, _)| match case { CaseTest::List(i) => { if longest < *i { *i } else { longest } } CaseTest::ListWithTail(i) => { if longest < *i { *i - 1 } else { longest } } _ => unreachable!(), }, ); let last_pattern = if tail_cases.is_empty() { *default.as_ref().unwrap().clone() } else { let tree = tail_cases.last().unwrap(); tree.1.clone() }; let builtins_for_pattern = builtins_path.merge(Builtins::new_from_list_case( CaseTest::List(longest_pattern), )); stick_set.diff_union_builtins(builtins_for_pattern.clone()); let last_pattern = self.handle_decision_tree( subject_name, subject_tipo.clone(), return_tipo.clone(), module_build_name, last_pattern, stick_set.clone(), ); let list_clauses = (0..=longest_pattern).rev().with_position().fold( (builtins_for_pattern, last_pattern), |(mut builtins_for_pattern, acc), list_item| match list_item { itertools::Position::First(index) | itertools::Position::Only(index) => { let (_, tree) = cases .iter() .chain(tail_cases.iter()) .find(|x| match x.0 { CaseTest::List(i) => i == index, CaseTest::ListWithTail(i) => i <= index, _ => unreachable!(), }) .cloned() .unwrap_or_else(|| { (CaseTest::Wild, *default.as_ref().unwrap().clone()) }); let tail_name = if builtins_for_pattern.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, builtins_for_pattern.to_string()) }; let then = self.handle_decision_tree( subject_name, subject_tipo.clone(), return_tipo.clone(), module_build_name, tree, stick_set.clone(), ); let acc = AirTree::list_clause( tail_name.clone(), subject_tipo.clone(), then, AirTree::anon_func(vec![], acc, true), None, ); builtins_for_pattern.pop(); (builtins_for_pattern, acc) } itertools::Position::Middle(index) | itertools::Position::Last(index) => { let (_, tree) = cases .iter() .chain(tail_cases.iter()) .find(|x| match x.0 { CaseTest::List(i) => i == index, CaseTest::ListWithTail(i) => i <= index, _ => unreachable!(), }) .cloned() .unwrap_or_else(|| { (CaseTest::Wild, *default.as_ref().unwrap().clone()) }); let tail_name = if builtins_for_pattern.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, builtins_for_pattern.to_string()) }; // TODO: change this in the future to use the Builtins to_string method // to ensure future changes don't break things let next_tail_name = Some(format!("{}_tail", tail_name)); let then = self.handle_decision_tree( subject_name, subject_tipo.clone(), return_tipo.clone(), module_build_name, tree, stick_set.clone(), ); let acc = AirTree::list_clause( tail_name.clone(), subject_tipo.clone(), then, AirTree::anon_func(vec![], acc, true), next_tail_name.map(|next| (tail_name, next)), ); // since we iterate over the list cases in reverse // We pop off a builtin to make it easier to get the name of // prev_tested list case since each name is based off the builtins builtins_for_pattern.pop(); (builtins_for_pattern, acc) } }, ); let when_list_cases = AirTree::when( current_subject_name.clone(), return_tipo.clone(), current_tipo.clone(), AirTree::local_var(current_subject_name, current_tipo.clone()), list_clauses.1, ); builtins_to_add.to_air( // The only reason I pass this in is to ensure I signal // whether or not constr_fields_exposer was used. I could // probably optimize this part out to simplify codegen in // the future &mut self.special_functions, prev_subject_name, prev_tipo, when_list_cases, ) } DecisionTree::HoistedLeaf(name, args) => { let air_args = args .iter() .map(|item| { let current_tipo = get_tipo_by_path(subject_tipo.clone(), &item.path); ( current_tipo.clone(), AirTree::local_var(item.assigned.clone(), current_tipo), ) }) .collect_vec(); let then = AirTree::call( AirTree::local_var( name, Type::function( air_args.iter().map(|i| i.0.clone()).collect_vec(), return_tipo.clone(), ), ), Type::void(), air_args.into_iter().map(|i| i.1).collect_vec(), ); self.handle_assigns(subject_name, subject_tipo, &args, &mut stick_set, then) } DecisionTree::HoistThen { name, assigns, pattern, then, } => { let assign = AirTree::let_assignment( name, AirTree::anon_func( assigns .iter() .map(|i| { let assign = introduce_name(&mut self.interner, &i.assigned); assign }) .collect_vec(), // The one reason we have to pass in mutable self // So we can build the TypedExpr into Air self.build(then, module_build_name, &[]), true, ), self.handle_decision_tree( subject_name, subject_tipo, return_tipo, module_build_name, *pattern, stick_set, ), ); assigns.into_iter().for_each(|x| { self.interner.pop_text(x.assigned); }); assign } } } fn handle_assigns( &mut self, subject_name: &String, subject_tipo: Rc, assigns: &[Assigned], stick_set: &mut TreeSet, then: AirTree, ) -> AirTree { match assigns { [] => then, [assign, rest @ ..] => { let Assigned { path, assigned } = assign; let current_tipo = get_tipo_by_path(subject_tipo.clone(), path); let builtins_path = Builtins::new_from_path(subject_tipo.clone(), path.clone()); let current_subject_name = if builtins_path.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, builtins_path.to_string()) }; // Transition process from previous to current let builtins_to_add = stick_set.diff_union_builtins(builtins_path.clone()); // Previous path to apply the transition process too let prev_builtins = Builtins { vec: builtins_path.vec[0..(builtins_path.len() - builtins_to_add.len())] .to_vec(), }; let prev_subject_name = if prev_builtins.is_empty() { subject_name.clone() } else { format!("{}_{}", subject_name, prev_builtins.to_string()) }; let prev_tipo = prev_builtins .vec .last() .map_or(subject_tipo.clone(), |last| last.tipo()); let assignment = AirTree::let_assignment( assigned, AirTree::local_var(current_subject_name, current_tipo), self.handle_assigns(subject_name, subject_tipo, rest, stick_set, then), ); let thing = builtins_to_add.to_air( &mut self.special_functions, prev_subject_name, prev_tipo, assignment, ); thing } } } fn hoist_functions_to_validator(&mut self, mut air_tree: AirTree) -> AirTree { let mut functions_to_hoist = IndexMap::new(); let mut used_functions = vec![]; let mut defined_functions = vec![]; let mut hoisted_functions = vec![]; let mut validator_hoistable; // TODO change subsequent tree traversals to be more like a stream. air_tree.traverse_tree_with(&mut |air_tree: &mut AirTree, _| { erase_opaque_type_operations(air_tree, &self.data_types); }); self.find_function_vars_and_depth( &mut air_tree, &mut functions_to_hoist, &mut used_functions, &mut TreePath::new(), 0, Fields::FirstField, ); validator_hoistable = used_functions.clone(); while let Some((key, variant_name)) = used_functions.pop() { defined_functions.push((key.clone(), variant_name.clone())); let function_variants = functions_to_hoist .get(&key) .unwrap_or_else(|| panic!("Missing Function Definition")); let (tree_path, function) = function_variants .get(&variant_name) .unwrap_or_else(|| panic!("Missing Function Variant Definition")); match function { HoistableFunction::Function { body, deps, params } => { let mut hoist_body = body.clone(); let mut hoist_deps = deps.clone(); let params = params.clone(); let tree_path = tree_path.clone(); self.define_dependent_functions( &mut hoist_body, &mut functions_to_hoist, &mut used_functions, &defined_functions, &mut hoist_deps, tree_path, ); let function_variants = functions_to_hoist .get_mut(&key) .unwrap_or_else(|| panic!("Missing Function Definition")); let (_, function) = function_variants .get_mut(&variant_name) .expect("Missing Function Variant Definition"); *function = HoistableFunction::Function { body: hoist_body, deps: hoist_deps, params, }; } HoistableFunction::Link(_) => todo!("Deal with Link later"), _ => unreachable!(), } } validator_hoistable.dedup(); // First we need to sort functions by dependencies // here's also where we deal with mutual recursion // Mutual Recursion let inputs = functions_to_hoist .iter() .flat_map(|(function_name, val)| { val.into_iter() .map(|(variant, (_, function))| { if let HoistableFunction::Function { deps, .. } = function { ((function_name.clone(), variant.clone()), deps) } else { todo!("Deal with Link later") } }) .collect_vec() }) .collect_vec(); let capacity = inputs.len(); let mut graph = Graph::<(), ()>::with_capacity(capacity, capacity * 5); let mut indices = HashMap::with_capacity(capacity); let mut values = HashMap::with_capacity(capacity); for (value, _) in &inputs { let index = graph.add_node(()); indices.insert(value.clone(), index); values.insert(index, value.clone()); } for (value, deps) in inputs { if let Some(from_index) = indices.get(&value) { let deps = deps.iter().filter_map(|dep| indices.get(dep)); for to_index in deps { graph.add_edge(*from_index, *to_index, ()); } } } let strong_connections = algo::tarjan_scc(&graph); for (index, connections) in strong_connections.into_iter().enumerate() { // If there's only one function, then it's only self recursive if connections.len() < 2 { continue; } let cyclic_function_names = connections .iter() .map(|index| values.get(index).unwrap()) .collect_vec(); // TODO: Maybe I could come up with a name based off the functions involved? let function_key = FunctionAccessKey { function_name: format!("__cyclic_function_{}", index), module_name: "".to_string(), }; let mut path = TreePath::new(); let mut cycle_of_functions = vec![]; let mut cycle_deps = vec![]; let function_list = cyclic_function_names .iter() .map(|(key, variant)| { format!( "{}{}{}", key.module_name, key.function_name, if variant.is_empty() { "".to_string() } else { format!("_{}", variant) } ) }) .collect_vec(); // By doing this any vars that "call" into a function in the cycle will be // redirected to call the cyclic function instead with the proper index for (index, (func_name, variant)) in cyclic_function_names.iter().enumerate() { self.cyclic_functions.insert( (func_name.clone(), variant.clone()), (function_list.clone(), index, function_key.clone()), ); let (tree_path, func) = functions_to_hoist .get_mut(func_name) .expect("Missing Function Definition") .get_mut(variant) .expect("Missing Function Variant Definition"); match func { HoistableFunction::Function { params, body, deps } => { cycle_of_functions.push((params.clone(), body.clone())); cycle_deps.push(deps.clone()); } _ => unreachable!(), } if path.is_empty() { path = tree_path.clone(); } else { path = path.common_ancestor(tree_path); } // Here we change function to be link so all functions that depend on it know its a // cyclic function *func = HoistableFunction::CyclicLink(function_key.clone()); } let cyclic_function = HoistableFunction::CyclicFunction { functions: cycle_of_functions, deps: cycle_deps .into_iter() .flatten() .dedup() // Make sure to filter out cyclic dependencies .filter(|dependency| { !cyclic_function_names.iter().any(|(func_name, variant)| { func_name == &dependency.0 && variant == &dependency.1 }) }) .collect_vec(), }; let mut cyclic_map = IndexMap::new(); cyclic_map.insert("".to_string(), (path, cyclic_function)); functions_to_hoist.insert(function_key, cyclic_map); } // Rest of code is for hoisting functions // TODO: replace with graph implementation of sorting let mut sorted_function_vec: Vec<(FunctionAccessKey, String)> = vec![]; let functions_to_hoist_cloned = functions_to_hoist.clone(); let mut sorting_attempts: u64 = 0; while let Some((generic_func, variant)) = validator_hoistable.pop() { assert!( sorting_attempts < 5_000_000_000, "Sorting dependency attempts exceeded" ); let function_variants = functions_to_hoist_cloned .get(&generic_func) .unwrap_or_else(|| panic!("Missing Function Definition")); let (_, function) = function_variants .get(&variant) .unwrap_or_else(|| panic!("Missing Function Variant Definition")); match function { HoistableFunction::Function { deps, .. } => { for (dep_generic_func, dep_variant) in deps.iter() { if !(dep_generic_func == &generic_func && dep_variant == &variant) { validator_hoistable .insert(0, (dep_generic_func.clone(), dep_variant.clone())); sorted_function_vec.retain(|(generic_func, variant)| { !(generic_func == dep_generic_func && variant == dep_variant) }); } } // Fix dependencies path to be updated to common ancestor for (dep_key, dep_variant) in deps { let (func_tree_path, _) = functions_to_hoist .get(&generic_func) .unwrap() .get(&variant) .unwrap() .clone(); let (dep_path, _) = functions_to_hoist .get_mut(dep_key) .unwrap() .get_mut(dep_variant) .unwrap(); *dep_path = func_tree_path.common_ancestor(dep_path); } sorted_function_vec.push((generic_func, variant)); } HoistableFunction::Link(_) => todo!("Deal with Link later"), HoistableFunction::CyclicLink(cyclic_name) => { validator_hoistable.insert(0, (cyclic_name.clone(), "".to_string())); sorted_function_vec.retain(|(generic_func, variant)| { !(generic_func == cyclic_name && variant.is_empty()) }); let (func_tree_path, _) = functions_to_hoist .get(&generic_func) .unwrap() .get(&variant) .unwrap() .clone(); let (dep_path, _) = functions_to_hoist .get_mut(cyclic_name) .unwrap() .get_mut("") .unwrap(); *dep_path = func_tree_path.common_ancestor(dep_path); } HoistableFunction::CyclicFunction { deps, .. } => { for (dep_generic_func, dep_variant) in deps.iter() { if !(dep_generic_func == &generic_func && dep_variant == &variant) { validator_hoistable .insert(0, (dep_generic_func.clone(), dep_variant.clone())); sorted_function_vec.retain(|(generic_func, variant)| { !(generic_func == dep_generic_func && variant == dep_variant) }); } } // Fix dependencies path to be updated to common ancestor for (dep_key, dep_variant) in deps { let (func_tree_path, _) = functions_to_hoist .get(&generic_func) .unwrap() .get(&variant) .unwrap() .clone(); let (dep_path, _) = functions_to_hoist .get_mut(dep_key) .unwrap() .get_mut(dep_variant) .unwrap(); *dep_path = func_tree_path.common_ancestor(dep_path); } sorted_function_vec.push((generic_func, variant)); } } sorting_attempts += 1; } sorted_function_vec.dedup(); // Now we need to hoist the functions to the top of the validator for (key, variant) in sorted_function_vec { if hoisted_functions .iter() .any(|(func_key, func_variant)| func_key == &key && func_variant == &variant) { continue; } let function_variants = functions_to_hoist .get(&key) .unwrap_or_else(|| panic!("Missing Function Definition")); let (tree_path, function) = function_variants .get(&variant) .unwrap_or_else(|| panic!("Missing Function Variant Definition")); self.hoist_function( &mut air_tree, tree_path, function, (&key, &variant), &functions_to_hoist, &mut hoisted_functions, ); } air_tree } fn hoist_function( &mut self, air_tree: &mut AirTree, tree_path: &TreePath, function: &HoistableFunction, key_var: (&FunctionAccessKey, &String), functions_to_hoist: &IndexMap< FunctionAccessKey, IndexMap, >, hoisted_functions: &mut Vec<(FunctionAccessKey, String)>, ) { match function { HoistableFunction::Function { body, deps: func_deps, params, } => { let mut body = body.clone(); let (key, variant) = key_var; // check for recursiveness let is_recursive = func_deps .iter() .any(|(dep_key, dep_variant)| dep_key == key && dep_variant == variant); // first grab dependencies let func_params = params; let deps = (tree_path, func_deps.clone()); let recursive_nonstatics = if is_recursive { modify_self_calls(&mut body, key, variant, func_params) } else { func_params.clone() }; let node_to_edit = air_tree.find_air_tree_node(tree_path); let defined_function = AirTree::define_func( &key.function_name, &key.module_name, variant, func_params.clone(), is_recursive, recursive_nonstatics, body, node_to_edit.clone(), ); let defined_dependencies = self.hoist_dependent_functions( deps, (key, variant), hoisted_functions, functions_to_hoist, defined_function, ); // now hoist full function onto validator tree *node_to_edit = defined_dependencies; hoisted_functions.push((key.clone(), variant.clone())); } HoistableFunction::CyclicFunction { functions, deps: func_deps, } => { let (key, variant) = key_var; let deps = (tree_path, func_deps.clone()); let mut functions = functions.clone(); for (_, body) in functions.iter_mut() { modify_cyclic_calls(body, key, &self.cyclic_functions); } let node_to_edit = air_tree.find_air_tree_node(tree_path); let cyclic_func = AirTree::define_cyclic_func( &key.function_name, &key.module_name, variant, functions, node_to_edit.clone(), ); let defined_dependencies = self.hoist_dependent_functions( deps, (key, variant), hoisted_functions, functions_to_hoist, cyclic_func, ); // now hoist full function onto validator tree *node_to_edit = defined_dependencies; hoisted_functions.push((key.clone(), variant.clone())); } HoistableFunction::Link(_) => { todo!("This should probably be unreachable when I get to it") } HoistableFunction::CyclicLink(_) => { unreachable!("Sorted functions should not contain cyclic links") } } } fn hoist_dependent_functions( &mut self, deps: (&TreePath, Vec<(FunctionAccessKey, String)>), func_key_variant: (&FunctionAccessKey, &Variant), hoisted_functions: &mut Vec<(FunctionAccessKey, String)>, functions_to_hoist: &IndexMap< FunctionAccessKey, IndexMap, >, air_tree: AirTree, ) -> AirTree { let (key, variant) = func_key_variant; let (func_path, func_deps) = deps; let mut deps_vec = func_deps; let mut sorted_dep_vec = vec![]; while let Some(dep) = deps_vec.pop() { let function_variants = functions_to_hoist .get(&dep.0) .unwrap_or_else(|| panic!("Missing Function Definition")); let (_, function) = function_variants .get(&dep.1) .unwrap_or_else(|| panic!("Missing Function Variant Definition")); match function { HoistableFunction::Function { deps, .. } => { for (dep_generic_func, dep_variant) in deps.iter() { if !(dep_generic_func == &dep.0 && dep_variant == &dep.1) { sorted_dep_vec.retain(|(generic_func, variant)| { !(generic_func == dep_generic_func && variant == dep_variant) }); deps_vec.insert(0, (dep_generic_func.clone(), dep_variant.clone())); } } sorted_dep_vec.push((dep.0.clone(), dep.1.clone())); } HoistableFunction::CyclicFunction { deps, .. } => { for (dep_generic_func, dep_variant) in deps.iter() { if !(dep_generic_func == &dep.0 && dep_variant == &dep.1) { sorted_dep_vec.retain(|(generic_func, variant)| { !(generic_func == dep_generic_func && variant == dep_variant) }); deps_vec.insert(0, (dep_generic_func.clone(), dep_variant.clone())); } } sorted_dep_vec.push((dep.0.clone(), dep.1.clone())); } HoistableFunction::Link(_) => todo!("Deal with Link later"), HoistableFunction::CyclicLink(cyclic_func) => { sorted_dep_vec.retain(|(generic_func, variant)| { !(generic_func == cyclic_func && variant.is_empty()) }); deps_vec.insert(0, (cyclic_func.clone(), "".to_string())); } } } sorted_dep_vec.dedup(); sorted_dep_vec .into_iter() .fold(air_tree, |then, (dep_key, dep_variant)| { if // if the dependency is the same as the function we're hoisting // or we hoisted it, then skip it hoisted_functions .iter() .any(|(generic, variant)| generic == &dep_key && variant == &dep_variant) || (&dep_key == key && &dep_variant == variant) { return then; } let dependency = functions_to_hoist .get(&dep_key) .unwrap_or_else(|| panic!("Missing Function Definition")); let (dep_path, dep_function) = dependency .get(&dep_variant) .unwrap_or_else(|| panic!("Missing Function Variant Definition")); // In the case of zero args, we need to hoist the dependency function to the top of the zero arg function // The dependency we are hoisting should have an equal path to the function we hoisted // if we are going to hoist it if &dep_path.common_ancestor(func_path) == func_path { match dep_function.clone() { HoistableFunction::Function { body: mut dep_air_tree, deps: dependency_deps, params: dependent_params, } => { let is_dependent_recursive = dependency_deps .iter() .any(|(key, variant)| &dep_key == key && &dep_variant == variant); let recursive_nonstatics = if is_dependent_recursive { modify_self_calls( &mut dep_air_tree, &dep_key, &dep_variant, &dependent_params, ) } else { dependent_params.clone() }; hoisted_functions.push((dep_key.clone(), dep_variant.clone())); AirTree::define_func( &dep_key.function_name, &dep_key.module_name, &dep_variant, dependent_params, is_dependent_recursive, recursive_nonstatics, dep_air_tree, then, ) } HoistableFunction::CyclicFunction { functions, .. } => { let mut functions = functions.clone(); for (_, body) in functions.iter_mut() { modify_cyclic_calls(body, &dep_key, &self.cyclic_functions); } hoisted_functions.push((dep_key.clone(), dep_variant.clone())); AirTree::define_cyclic_func( &dep_key.function_name, &dep_key.module_name, &dep_variant, functions, then, ) } HoistableFunction::Link(_) => unreachable!(), HoistableFunction::CyclicLink(_) => unreachable!(), } } else { then } }) } fn define_dependent_functions( &mut self, air_tree: &mut AirTree, function_usage: &mut IndexMap< FunctionAccessKey, IndexMap, >, used_functions: &mut Vec<(FunctionAccessKey, String)>, defined_functions: &[(FunctionAccessKey, String)], current_function_deps: &mut Vec<(FunctionAccessKey, String)>, mut function_tree_path: TreePath, ) { let Some((depth, index)) = function_tree_path.pop() else { return; }; function_tree_path.push(depth, index); self.find_function_vars_and_depth( air_tree, function_usage, current_function_deps, &mut function_tree_path, depth + 1, Fields::FirstField, ); for (generic_function_key, variant_name) in current_function_deps.iter() { if !used_functions .iter() .any(|(key, name)| key == generic_function_key && name == variant_name) && !defined_functions .iter() .any(|(key, name)| key == generic_function_key && name == variant_name) { used_functions.push((generic_function_key.clone(), variant_name.clone())); } } } fn find_function_vars_and_depth( &mut self, air_tree: &mut AirTree, function_usage: &mut IndexMap< FunctionAccessKey, IndexMap, >, dependency_functions: &mut Vec<(FunctionAccessKey, String)>, path: &mut TreePath, current_depth: usize, depth_index: Fields, ) { air_tree.traverse_tree_with_path( path, current_depth, depth_index, &mut |air_tree, tree_path| { if let AirTree::Var { constructor, variant_name, .. } = air_tree { let ValueConstructorVariant::ModuleFn { name: func_name, module, builtin: None, .. } = &constructor.variant else { return; }; let function_var_tipo = &constructor.tipo; let generic_function_key = FunctionAccessKey { module_name: module.clone(), function_name: func_name.clone(), }; let function_def = self.functions.get(&generic_function_key); let Some(function_def) = function_def else { let code_gen_func = self .code_gen_functions .get(&generic_function_key.function_name) .unwrap_or_else(|| { panic!( "Missing function definition for {}. Known functions: {:?}", generic_function_key.function_name, self.functions.keys(), ) }); if !dependency_functions .iter() .any(|(key, name)| key == &generic_function_key && name.is_empty()) { dependency_functions .push((generic_function_key.clone(), "".to_string())); } // Code gen functions are already monomorphized if let Some(func_variants) = function_usage.get_mut(&generic_function_key) { let (path, _) = func_variants.get_mut("").unwrap(); *path = path.common_ancestor(tree_path); } else { // Shortcut path for compiler generated functions let CodeGenFunction::Function { body, params } = code_gen_func else { unreachable!() }; let mut function_variant_path = IndexMap::new(); let mut body = AirTree::no_op(body.clone()); body.traverse_tree_with(&mut |air_tree, _| { erase_opaque_type_operations(air_tree, &self.data_types); }); function_variant_path.insert( "".to_string(), ( tree_path.clone(), HoistableFunction::Function { body, deps: vec![], params: params.clone(), }, ), ); function_usage.insert(generic_function_key, function_variant_path); } return; }; let mut function_var_types = function_var_tipo .arg_types() .unwrap_or_else(|| panic!("Expected a function tipo with arg types")); function_var_types.push( function_var_tipo .return_type() .unwrap_or_else(|| panic!("Should have return type")), ); let mut function_def_types = function_def .arguments .iter() .map(|arg| convert_opaque_type(&arg.tipo, &self.data_types, true)) .collect_vec(); function_def_types.push(convert_opaque_type( &function_def.return_type, &self.data_types, true, )); let mono_types: IndexMap> = if !function_def_types.is_empty() { function_def_types .iter() .zip(function_var_types.iter()) .flat_map(|(func_tipo, var_tipo)| { get_generic_id_and_type(func_tipo, var_tipo) }) .collect() } else { IndexMap::new() }; // Don't sort here. Mono types map is already in argument order. let variant = mono_types .iter() .map(|(_, tipo)| get_generic_variant_name(tipo)) .join(""); variant_name.clone_from(&variant); if !dependency_functions .iter() .any(|(key, name)| key == &generic_function_key && name == &variant) { dependency_functions.push((generic_function_key.clone(), variant.clone())); } if let Some(func_variants) = function_usage.get_mut(&generic_function_key) { if let Some((path, _)) = func_variants.get_mut(&variant) { *path = path.common_ancestor(tree_path); } else { let args = function_def.arguments.clone(); let params = args .iter() .map(|arg| { arg.arg_name .get_variable_name() .map(|arg| { introduce_name(&mut self.interner, &arg.to_string()) }) .unwrap_or_else(|| DISCARDED.to_string()) }) .collect_vec(); let mut function_air_tree_body = AirTree::no_op(self.build( &function_def.body, &generic_function_key.module_name, &[], )); function_air_tree_body.traverse_tree_with(&mut |air_tree, _| { erase_opaque_type_operations(air_tree, &self.data_types); monomorphize(air_tree, &mono_types); }); args.iter().for_each(|arg| { arg.arg_name.get_variable_name().iter().for_each(|arg| { self.interner.pop_text(arg.to_string()); }) }); func_variants.insert( variant, ( tree_path.clone(), HoistableFunction::Function { body: function_air_tree_body, deps: vec![], params, }, ), ); } } else { let args = function_def.arguments.clone(); let params = args .iter() .map(|arg| { arg.arg_name .get_variable_name() .map(|arg| introduce_name(&mut self.interner, &arg.to_string())) .unwrap_or_else(|| DISCARDED.to_string()) }) .collect_vec(); let mut function_air_tree_body = AirTree::no_op(self.build( &function_def.body, &generic_function_key.module_name, &[], )); function_air_tree_body.traverse_tree_with(&mut |air_tree, _| { erase_opaque_type_operations(air_tree, &self.data_types); monomorphize(air_tree, &mono_types); }); let mut function_variant_path = IndexMap::new(); args.iter().for_each(|arg| { arg.arg_name .get_variable_name() .iter() .for_each(|arg| self.interner.pop_text(arg.to_string())) }); function_variant_path.insert( variant, ( tree_path.clone(), HoistableFunction::Function { body: function_air_tree_body, deps: vec![], params, }, ), ); function_usage.insert(generic_function_key, function_variant_path); } } }, ); } fn uplc_code_gen(&mut self, mut ir_stack: Vec) -> Term { let mut arg_stack: Vec> = vec![]; while let Some(air_element) = ir_stack.pop() { let arg = self.gen_uplc(air_element, &mut arg_stack); if let Some(arg) = arg { arg_stack.push(arg); } } assert!(arg_stack.len() == 1, "Expected one term on the stack"); arg_stack.pop().unwrap() } fn gen_uplc(&mut self, ir: Air, arg_stack: &mut Vec>) -> Option> { match ir { Air::Int { value } => Some(Term::integer(value.parse().unwrap())), Air::String { value } => Some(Term::string(value)), Air::ByteArray { bytes } => Some(Term::byte_string(bytes)), Air::Bool { value } => Some(Term::bool(value)), Air::CurvePoint { point, .. } => match point { Curve::Bls12_381(Bls12_381Point::G1(g1)) => Some(Term::bls12_381_g1(g1)), Curve::Bls12_381(Bls12_381Point::G2(g2)) => Some(Term::bls12_381_g2(g2)), }, Air::Var { name, constructor, variant_name, } => match &constructor.variant { ValueConstructorVariant::LocalVariable { .. } => Some(Term::Var( Name { text: name, unique: 0.into(), } .into(), )), ValueConstructorVariant::ModuleConstant { module, name, .. } => { let access_key = FunctionAccessKey { module_name: module.clone(), function_name: name.clone(), }; let definition = self .constants .get(&access_key) .unwrap_or_else(|| panic!("unknown constant {module}.{name}")); let mut value = AirTree::no_op(self.build(definition, &access_key.module_name, &[])); value.traverse_tree_with(&mut |air_tree, _| { erase_opaque_type_operations(air_tree, &self.data_types); }); value = self.hoist_functions_to_validator(value); let term = self .uplc_code_gen(value.to_vec()) .constr_fields_exposer() .constr_index_exposer(); let mut program = self.new_program(self.special_functions.apply_used_functions(term)); let mut interner = CodeGenInterner::new(); interner.program(&mut program); let eval_program: Program = program.remove_no_inlines().try_into().unwrap(); Some( eval_program .eval(ExBudget::max()) .result() .unwrap_or_else(|e| panic!("Failed to evaluate constant: {e:#?}")) .try_into() .unwrap(), ) } ValueConstructorVariant::ModuleFn { name: func_name, module, builtin, .. } => { if let Some(func) = builtin { return self.gen_uplc( Air::Builtin { count: 0, func: *func, tipo: constructor.tipo, }, arg_stack, ); } if let Some((names, index, cyclic_name)) = self.cyclic_functions.get(&( FunctionAccessKey { module_name: module.clone(), function_name: func_name.clone(), }, variant_name.clone(), )) { let cyclic_var_name = if cyclic_name.module_name.is_empty() { cyclic_name.function_name.to_string() } else { format!("{}_{}", cyclic_name.module_name, cyclic_name.function_name) }; let index_name = names[*index].clone(); let mut arg_var = Term::var(index_name.clone()); for name in names.iter().rev() { arg_var = arg_var.lambda(name); } let term = Term::var(cyclic_var_name).apply(arg_var); Some(term) } else { let name = if !module.is_empty() { format!("{module}_{func_name}{variant_name}") } else { format!("{func_name}{variant_name}") }; Some(Term::Var( Name { text: name, unique: 0.into(), } .into(), )) } } ValueConstructorVariant::Record { name: constr_name, .. } => { // TODO handle pair if constructor.tipo.is_bool() { Some(Term::bool(constr_name == "True")) } else if constructor.tipo.is_void() { Some(Term::Constant(UplcConstant::Unit.into())) } else { let data_type = crate::tipo::lookup_data_type_by_tipo( &self.data_types, &constructor.tipo, ) .unwrap_or_else(|| { panic!( "could not find data-type definition for {} within known set: {:?}", constructor.tipo.to_pretty(0), self.data_types.keys() ) }); let (constr_index, constr_type) = data_type .constructors .iter() .enumerate() .find(|(_, x)| x.name == *constr_name) .unwrap(); let mut term = Term::empty_list(); if constr_type.arguments.is_empty() { term = Term::constr_data() .apply(Term::integer(constr_index.into())) .apply(term); let mut program = self.new_program(term); let mut interner = CodeGenInterner::new(); interner.program(&mut program); let eval_program: Program = program.remove_no_inlines().try_into().unwrap(); let evaluated_term: Term = eval_program .eval(ExBudget::default()) .result() .expect("Evaluated a constant record and got an error"); term = evaluated_term.try_into().unwrap(); } else { for (index, arg) in constructor .tipo .arg_types() .unwrap() .iter() .enumerate() .rev() { term = Term::mk_cons() .apply(convert_type_to_data( Term::var(format!("arg_{index}")), arg, )) .apply(term); } term = Term::constr_data() .apply(Term::integer(constr_index.into())) .apply(term); for (index, _) in constr_type.arguments.iter().enumerate().rev() { term = term.lambda(format!("arg_{index}")) } } Some(term) } } }, Air::Void => Some(Term::Constant(UplcConstant::Unit.into())), Air::List { count, tipo, tail } => { let mut args = vec![]; for _ in 0..count { let arg = arg_stack.pop().unwrap(); args.push(arg); } let mut constants = vec![]; for arg in &args { let maybe_const = extract_constant(arg); if let Some(c) = maybe_const { constants.push(c); } } if constants.len() == args.len() && !tail { let list = if tipo.is_map() { let mut convert_keys = vec![]; let mut convert_values = vec![]; for constant in constants { match constant.as_ref() { UplcConstant::ProtoPair(_, _, fst, snd) => { convert_keys.push(fst.clone()); convert_values.push(snd.clone()); } _ => unreachable!(), } } let convert_keys = builder::convert_constants_to_data(convert_keys); let convert_values = builder::convert_constants_to_data(convert_values); Term::Constant( UplcConstant::ProtoList( UplcType::Pair(UplcType::Data.into(), UplcType::Data.into()), convert_keys .into_iter() .zip(convert_values) .map(|(key, value)| { UplcConstant::ProtoPair( UplcType::Data, UplcType::Data, key.into(), value.into(), ) }) .collect_vec(), ) .into(), ) } else { Term::Constant( UplcConstant::ProtoList( UplcType::Data, builder::convert_constants_to_data(constants), ) .into(), ) }; Some(list) } else { let mut term = if tail { args.pop().unwrap() } else if tipo.is_map() { Term::empty_map() } else { Term::empty_list() }; // move this down here since the constant list path doesn't need to do this let list_element_type = tipo.get_inner_types()[0].clone(); for arg in args.into_iter().rev() { let list_item = if tipo.is_map() { arg } else { builder::convert_type_to_data(arg, &list_element_type) }; term = Term::mk_cons().apply(list_item).apply(term); } Some(term) } } Air::ListAccessor { names, tail, // TODO: rename tipo - // tipo here refers to the list type while the actual return // type is nothing since this is an assignment over some expression tipo, expect_level, } => { let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let otherwise = if matches!(expect_level, ExpectLevel::Full | ExpectLevel::Items) { arg_stack.pop().unwrap() } else { Term::Error.delay() }; let list_id = self.id_gen.next(); let mut id_list = vec![]; id_list.push(list_id); names.iter().for_each(|_| { id_list.push(self.id_gen.next()); }); let names_types = tipo .get_inner_types() .into_iter() .cycle() .take(names.len()) .zip(names) .zip(id_list) .map(|((tipo, name), id)| (name, tipo, id)) .collect_vec(); term = builder::list_access_to_uplc( &names_types, tail, term, true, expect_level, otherwise, ) .apply(value); Some(term) } Air::Fn { params, allow_inline, } => { let mut term = arg_stack.pop().unwrap(); for param in params.iter().rev() { term = term.lambda(param); } term = if allow_inline { term } else { term.lambda(NO_INLINE) }; if params.is_empty() { Some(term.delay()) } else { Some(term) } } Air::Call { count, .. } => { if count >= 1 { let mut term = arg_stack.pop().unwrap(); for _ in 0..count { let arg = arg_stack.pop().unwrap(); term = term.apply(arg); } Some(term) } else { let term = arg_stack.pop().unwrap(); match term.pierce_no_inlines() { Term::Var(_) => Some(term.force()), Term::Delay(inner_term) => Some(inner_term.as_ref().clone()), Term::Apply { .. } => Some(term.force()), _ => unreachable!( "Shouldn't call anything other than var or apply\n{:#?}", term ), } } } Air::Builtin { func, tipo, count } => { let mut arg_vec = vec![]; for _ in 0..count { arg_vec.push(arg_stack.pop().unwrap()); } let ret_tipo = match tipo.as_ref() { Type::Fn { ret, .. } => ret, // In this case the Air Opcode only holds the return type and not the function type _ => &tipo, }; let term = match &func { DefaultFunction::IfThenElse | DefaultFunction::ChooseUnit | DefaultFunction::Trace | DefaultFunction::ChooseList | DefaultFunction::ChooseData | DefaultFunction::MkCons | DefaultFunction::UnConstrData => { builder::special_case_builtin(&func, tipo, count, arg_vec) } DefaultFunction::FstPair | DefaultFunction::SndPair => { builder::undata_builtin(&func, count, ret_tipo, arg_vec) } DefaultFunction::HeadList if !tipo.is_pair() => { builder::undata_builtin(&func, count, ret_tipo, arg_vec) } _ => { let mut term: Term = func.into(); term = builder::apply_builtin_forces(term, func.force_count()); if func.arg_is_unit() { term = term.apply(Term::unit()) } else { for arg in arg_vec { term = term.apply(arg.clone()); } } term } }; Some(term) } Air::BinOp { name, // changed this to argument tipo argument_tipo: tipo, .. } => { let left = arg_stack.pop().unwrap(); let right = arg_stack.pop().unwrap(); let uplc_type = tipo.get_uplc_type(); let term = match name { BinOp::And => left.delayed_if_then_else(right, Term::bool(false)), BinOp::Or => left.delayed_if_then_else(Term::bool(true), right), BinOp::Eq | BinOp::NotEq => { let builtin = match &uplc_type { Some(UplcType::Integer) => Term::equals_integer(), Some(UplcType::String) => Term::equals_string(), Some(UplcType::ByteString) => Term::equals_bytestring(), Some(UplcType::Bls12_381G1Element) => Term::bls12_381_g1_equal(), Some(UplcType::Bls12_381G2Element) => Term::bls12_381_g2_equal(), Some(UplcType::Bool | UplcType::Unit) => Term::unit(), Some(UplcType::List(_) | UplcType::Pair(_, _) | UplcType::Data) | None => Term::equals_data(), Some(UplcType::Bls12_381MlResult) => { panic!("ML Result equality is not supported") } }; let binop_eq = match uplc_type { Some(UplcType::Bool) => { if matches!(name, BinOp::Eq) { left.delayed_if_then_else( right.clone(), right.if_then_else(Term::bool(false), Term::bool(true)), ) } else { left.delayed_if_then_else( right .clone() .if_then_else(Term::bool(false), Term::bool(true)), right, ) } } Some(UplcType::List(_)) if tipo.is_map() => builtin .apply(Term::map_data().apply(left)) .apply(Term::map_data().apply(right)), Some(UplcType::List(_)) => builtin .apply(Term::list_data().apply(left)) .apply(Term::list_data().apply(right)), Some(UplcType::Pair(_, _)) => builtin .apply(Term::map_data().apply( Term::mk_cons().apply(left).apply(Term::empty_map()), )) .apply(Term::map_data().apply( Term::mk_cons().apply(right).apply(Term::empty_map()), )), Some( UplcType::Data | UplcType::Bls12_381G1Element | UplcType::Bls12_381G2Element | UplcType::Bls12_381MlResult | UplcType::Integer | UplcType::String | UplcType::ByteString, ) | None => builtin.apply(left).apply(right), Some(UplcType::Unit) => { left.choose_unit(right.choose_unit(Term::bool(true))) } }; if !tipo.is_bool() && matches!(name, BinOp::NotEq) { binop_eq.if_then_else(Term::bool(false), Term::bool(true)) } else { binop_eq } } BinOp::LtInt => Term::Builtin(DefaultFunction::LessThanInteger) .apply(left) .apply(right), BinOp::LtEqInt => Term::Builtin(DefaultFunction::LessThanEqualsInteger) .apply(left) .apply(right), BinOp::GtEqInt => Term::Builtin(DefaultFunction::LessThanEqualsInteger) .apply(right) .apply(left), BinOp::GtInt => Term::Builtin(DefaultFunction::LessThanInteger) .apply(right) .apply(left), BinOp::AddInt => Term::add_integer().apply(left).apply(right), BinOp::SubInt => Term::Builtin(DefaultFunction::SubtractInteger) .apply(left) .apply(right), BinOp::MultInt => Term::Builtin(DefaultFunction::MultiplyInteger) .apply(left) .apply(right), BinOp::DivInt => Term::Builtin(DefaultFunction::DivideInteger) .apply(left) .apply(right), BinOp::ModInt => Term::Builtin(DefaultFunction::ModInteger) .apply(left) .apply(right), }; Some(term) } Air::DefineFunc { func_name, module_name, variant_name, variant, } => { let func_name = if module_name.is_empty() { format!("{func_name}{variant_name}") } else { format!("{module_name}_{func_name}{variant_name}") }; match variant { air::FunctionVariants::Standard(params) => { let mut func_body = arg_stack.pop().unwrap(); let term = arg_stack.pop().unwrap(); if params.is_empty() { func_body = func_body.delay(); } let func_body = params .into_iter() .rfold(func_body, |term, arg| term.lambda(arg)) .lambda(NO_INLINE); Some(term.lambda(func_name).apply(func_body)) } air::FunctionVariants::Recursive { params, recursive_nonstatic_params, } => { let mut func_body = arg_stack.pop().unwrap(); let term = arg_stack.pop().unwrap(); let no_statics = recursive_nonstatic_params == params; if recursive_nonstatic_params.is_empty() || params.is_empty() { func_body = func_body.delay(); } let func_body = recursive_nonstatic_params .iter() .rfold(func_body, |term, arg| term.lambda(arg)); let func_body = func_body.lambda(func_name.clone()); if no_statics { // If we don't have any recursive-static params, we can just emit the function as is Some( term.lambda(func_name.clone()) .apply( Term::var(func_name.clone()) .apply(Term::var(func_name.clone())), ) .lambda(func_name) .apply(func_body.lambda(NO_INLINE)), ) } else { // If we have parameters that remain static in each recursive call, // we can construct an *outer* function to take those in // and simplify the recursive part to only accept the non-static arguments let mut recursive_func_body = Term::var(&func_name).apply(Term::var(&func_name)); if recursive_nonstatic_params.is_empty() { recursive_func_body = recursive_func_body.force(); } // Introduce a parameter for each parameter // NOTE: we use recursive_nonstatic_params here because // if this is recursive, those are the ones that need to be passed // each time for param in recursive_nonstatic_params.into_iter() { recursive_func_body = recursive_func_body.apply(Term::var(param)); } // Then construct an outer function with *all* parameters, not just the nonstatic ones. let mut outer_func_body = recursive_func_body.lambda(&func_name).apply(func_body); // Now, add *all* parameters, so that other call sites don't know the difference outer_func_body = params .clone() .into_iter() .rfold(outer_func_body, |term, arg| term.lambda(arg)); // And finally, fold that definition into the rest of our program Some( term.lambda(&func_name) .apply(outer_func_body.lambda(NO_INLINE)), ) } } air::FunctionVariants::Cyclic(contained_functions) => { let mut cyclic_functions = vec![]; for params in contained_functions { let func_body = arg_stack.pop().unwrap(); cyclic_functions.push((params, func_body)); } let mut term = arg_stack.pop().unwrap(); let mut cyclic_body = Term::var("__chooser"); for (params, func_body) in cyclic_functions.into_iter() { let mut function = func_body; if params.is_empty() { function = function.delay(); } else { for param in params.iter().rev() { function = function.lambda(param); } } // We basically Scott encode our function bodies and use the chooser function // to determine which function body and params is run // For example say there is a cycle of 3 function bodies // Our choose function can look like this: // \func1 -> \func2 -> \func3 -> func1 // In this case our chooser is a function that takes in 3 functions // and returns the first one to run cyclic_body = cyclic_body.apply(function) } term = term .lambda(&func_name) .apply(Term::var(&func_name).apply(Term::var(&func_name))) .lambda(&func_name) .apply( cyclic_body .lambda("__chooser") .lambda(func_name) .lambda(NO_INLINE), ); Some(term) } } } Air::Let { name } => { let arg = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); term = term.lambda(name).apply(arg); Some(term) } Air::CastFromData { tipo, full_cast } => { let mut term = arg_stack.pop().unwrap(); term = if full_cast { unknown_data_to_type(term, &tipo) } else { known_data_to_type(term, &tipo) }; if extract_constant(term.pierce_no_inlines()).is_some() { let mut program = self.new_program(term); let mut interner = CodeGenInterner::new(); interner.program(&mut program); let eval_program: Program = program.remove_no_inlines().try_into().unwrap(); let evaluated_term: Term = eval_program .eval(ExBudget::default()) .result() .expect("Evaluated on unwrapping a data constant and got an error"); term = evaluated_term.try_into().unwrap(); } Some(term) } Air::CastToData { tipo } => { let mut term = arg_stack.pop().unwrap(); if extract_constant(term.pierce_no_inlines()).is_some() { term = builder::convert_type_to_data(term, &tipo); let mut program = self.new_program(term); let mut interner = CodeGenInterner::new(); interner.program(&mut program); let eval_program: Program = program.remove_no_inlines().try_into().unwrap(); let evaluated_term: Term = eval_program .eval(ExBudget::default()) .result() .expect("Evaluated on wrapping a constant into data and got an error"); term = evaluated_term.try_into().unwrap(); } else { term = builder::convert_type_to_data(term, &tipo); } Some(term) } Air::AssertBool { is_true } => { let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let otherwise = arg_stack.pop().unwrap(); if is_true { term = value.if_then_else(term.delay(), otherwise).force() } else { term = value.if_then_else(otherwise, term.delay()).force() } Some(term) } Air::When { subject_name, // using subject type here subject_tipo: tipo, .. } => { let subject = arg_stack.pop().unwrap(); let uplc_type = tipo.get_uplc_type(); let subject = match uplc_type { Some( UplcType::Bool | UplcType::Integer | UplcType::String | UplcType::ByteString | UplcType::Unit | UplcType::List(_) | UplcType::Pair(_, _) | UplcType::Bls12_381G1Element | UplcType::Bls12_381G2Element | UplcType::Bls12_381MlResult, ) => subject, Some(UplcType::Data) => subject, None => Term::var( self.special_functions .use_function_uplc(CONSTR_INDEX_EXPOSER.to_string()), ) .apply(subject), }; let mut term = arg_stack.pop().unwrap(); term = term.lambda(subject_name).apply(subject); Some(term) } Air::Clause { subject_tipo: tipo, subject_name, } => { // clause to compare let clause = arg_stack.pop().unwrap(); // the body to be run if the clause matches let body = arg_stack.pop().unwrap(); // the next branch in the when expression // Expected to be delayed let term = arg_stack.pop().unwrap(); assert!(matches!(term, Term::Delay(_) | Term::Var(_))); let other_clauses = term.clone(); let body = if tipo.is_bool() { if matches!(clause, Term::Constant(boolean) if matches!(boolean.as_ref(), UplcConstant::Bool(true))) { Term::var(subject_name) .if_then_else(body.delay(), other_clauses) .force() } else { Term::var(subject_name) .if_then_else(other_clauses, body.delay()) .force() } } else { let uplc_type = tipo.get_uplc_type(); let condition = match uplc_type { Some( UplcType::Bool | UplcType::Unit | UplcType::List(_) | UplcType::Pair(_, _) | UplcType::Bls12_381MlResult, ) => unreachable!("{:#?}", tipo), Some(UplcType::Data) => unimplemented!(), Some(UplcType::Integer) => Term::equals_integer() .apply(clause) .apply(Term::var(subject_name)), Some(UplcType::String) => Term::equals_string() .apply(clause) .apply(Term::var(subject_name)), Some(UplcType::ByteString) => Term::equals_bytestring() .apply(clause) .apply(Term::var(subject_name)), Some(UplcType::Bls12_381G1Element) => Term::bls12_381_g1_equal() .apply(clause) .apply(Term::var(subject_name)), Some(UplcType::Bls12_381G2Element) => Term::bls12_381_g2_equal() .apply(clause) .apply(Term::var(subject_name)), None => Term::equals_integer() .apply(clause) .apply(Term::var(subject_name)), }; condition.delay_true_if_then_else(body, other_clauses) }; Some(body) } Air::ListClause { tail_name, next_tail_name, .. } => { // no longer need to pop off discard let body = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); assert!(matches!(term, Term::Delay(_))); term = if let Some((current_tail, next_tail_name)) = next_tail_name { term.force() .lambda(next_tail_name) .apply(Term::tail_list().apply(Term::var(current_tail.clone()))) .delay() } else { term }; term = Term::var(tail_name).delay_empty_choose_list(body, term); Some(term) } Air::WrapClause => { // no longer need to pop off discard let mut term = arg_stack.pop().unwrap(); let arg = arg_stack.pop().unwrap(); term = term.lambda("__other_clauses_delayed").apply(arg.delay()); Some(term) } Air::TupleClause { subject_tipo: tipo, indices, subject_name, complex_clause, .. } => { let mut term = arg_stack.pop().unwrap(); let tuple_types = tipo.get_inner_types(); let next_clause = arg_stack.pop().unwrap(); if complex_clause { term = term .lambda("__other_clauses_delayed") .apply(next_clause.delay()); } for (index, name) in indices.iter() { term = term.lambda(name.clone()).apply(builder::known_data_to_type( Term::head_list() .apply(Term::var(subject_name.clone()).repeat_tail_list(*index)), &tuple_types[*index].clone(), )); } Some(term) } Air::PairClause { subject_tipo: tipo, complex_clause, subject_name, fst_name, snd_name, } => { let mut term = arg_stack.pop().unwrap(); let next_clause = arg_stack.pop().unwrap(); let pair_types = tipo.get_inner_types(); if complex_clause { term = term .lambda("__other_clauses_delayed") .apply(next_clause.delay()); } if let Some(fst) = fst_name { term = term.lambda(fst).apply(builder::known_data_to_type( Term::fst_pair().apply(Term::var(subject_name.clone())), &pair_types[0].clone(), )); } if let Some(snd) = snd_name { term = term.lambda(snd).apply(builder::known_data_to_type( Term::snd_pair().apply(Term::var(subject_name.clone())), &pair_types[1].clone(), )); } Some(term) } Air::ClauseGuard { subject_name, subject_tipo: tipo, } => { let checker = arg_stack.pop().unwrap(); let then = arg_stack.pop().unwrap(); let term = Term::var("__other_clauses_delayed"); if tipo.is_bool() { if matches!(checker, Term::Constant(boolean) if matches!(boolean.as_ref(), UplcConstant::Bool(true))) { Some( Term::var(subject_name) .if_then_else(then.delay(), term) .force(), ) } else { Some( Term::var(subject_name) .if_then_else(term, then.delay()) .force(), ) } } else if tipo.is_void() { Some(then.lambda(DISCARDED).apply(Term::var(subject_name))) } else { let uplc_type = tipo.get_uplc_type(); let condition = match uplc_type { Some( UplcType::Bool | UplcType::Unit | UplcType::List(_) | UplcType::Pair(_, _) | UplcType::Bls12_381MlResult, ) => unreachable!("{:#?}", tipo), Some(UplcType::Data) => unimplemented!(), Some(UplcType::Integer) => Term::equals_integer() .apply(checker) .apply(Term::var(subject_name)), Some(UplcType::String) => Term::equals_string() .apply(checker) .apply(Term::var(subject_name)), Some(UplcType::ByteString) => Term::equals_bytestring() .apply(checker) .apply(Term::var(subject_name)), Some(UplcType::Bls12_381G1Element) => Term::bls12_381_g1_equal() .apply(checker) .apply(Term::var(subject_name)), Some(UplcType::Bls12_381G2Element) => Term::bls12_381_g2_equal() .apply(checker) .apply(Term::var(subject_name)), None => Term::equals_integer().apply(checker).apply( Term::var( self.special_functions .use_function_uplc(CONSTR_INDEX_EXPOSER.to_string()), ) .apply(Term::var(subject_name)), ), }; Some(condition.if_then_else(then.delay(), term).force()) } } Air::ListClauseGuard { tail_name, next_tail_name, inverse, .. } => { // no longer need to pop off discard // the body to be run if the clause matches // the next branch in the when expression let mut term = arg_stack.pop().unwrap(); term = if let Some(next_tail_name) = next_tail_name { term.lambda(next_tail_name) .apply(Term::tail_list().apply(Term::var(tail_name.clone()))) } else { term }; if !inverse { term = Term::var(tail_name) .choose_list(term.delay(), Term::var("__other_clauses_delayed")) .force(); } else { term = Term::var(tail_name) .choose_list(Term::var("__other_clauses_delayed"), term.delay()) .force(); } Some(term) } Air::TupleGuard { subject_tipo: tipo, indices, subject_name, } => { let mut term = arg_stack.pop().unwrap(); let tuple_types = tipo.get_inner_types(); for (index, name) in indices.iter() { term = term.lambda(name.clone()).apply(builder::known_data_to_type( Term::head_list() .apply(Term::var(subject_name.clone()).repeat_tail_list(*index)), &tuple_types[*index].clone(), )); } Some(term) } Air::PairGuard { subject_tipo: tipo, subject_name, fst_name, snd_name, } => { let mut term = arg_stack.pop().unwrap(); let tuple_types = tipo.get_inner_types(); if let Some(fst) = fst_name { term = term.lambda(fst).apply(builder::known_data_to_type( Term::fst_pair().apply(Term::var(subject_name.clone())), &tuple_types[0].clone(), )); } if let Some(snd) = snd_name { term = term.lambda(snd).apply(builder::known_data_to_type( Term::snd_pair().apply(Term::var(subject_name.clone())), &tuple_types[1].clone(), )); } Some(term) } Air::Finally => { let _clause = arg_stack.pop().unwrap(); None } Air::If { .. } => { let condition = arg_stack.pop().unwrap(); let then = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); term = condition.delayed_if_then_else(then, term); Some(term) } Air::Constr { tag: constr_index, count, tipo, } => { let mut arg_vec = vec![]; for _ in 0..count { arg_vec.push(arg_stack.pop().unwrap()); } let mut term = Term::empty_list(); for (index, arg) in arg_vec.iter().enumerate().rev() { term = Term::mk_cons() .apply(builder::convert_type_to_data( arg.clone(), &tipo.arg_types().unwrap()[index], )) .apply(term); } term = Term::constr_data() .apply(Term::integer(constr_index.into())) .apply(term); if arg_vec.iter().all(|item| { let maybe_const = extract_constant(item.pierce_no_inlines()); maybe_const.is_some() }) { let mut program = self.new_program(term); let mut interner = CodeGenInterner::new(); interner.program(&mut program); let eval_program: Program = program.remove_no_inlines().try_into().unwrap(); let evaluated_term: Term = eval_program .eval(ExBudget::default()) .result() .expect("Evaluated a constant record with args and got an error"); term = evaluated_term.try_into().unwrap(); } Some(term) } Air::FieldsExpose { indices, is_expect } => { let mut id_list = vec![]; let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let otherwise = if is_expect { arg_stack.pop().unwrap() } else { Term::Error.delay() }; let list_id = self.id_gen.next(); id_list.push(list_id); indices.iter().for_each(|_| { id_list.push(self.id_gen.next()); }); let names_types = indices .iter() .cloned() .zip(id_list) .map(|(item, id)| (item.1, item.2, id)) .collect_vec(); let named_indices = names_types .iter() .skip_while(|(name, _, _)| name == DISCARDED) .collect_vec(); if !named_indices.is_empty() || is_expect { term = builder::list_access_to_uplc( &names_types, false, term, false, is_expect.into(), otherwise, ); term = term.apply( Term::var( self.special_functions .use_function_uplc(CONSTR_FIELDS_EXPOSER.to_string()), ) .apply(value), ); Some(term) } else { Some(term) } } Air::FieldsEmpty => { let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let otherwise = arg_stack.pop().unwrap(); term = Term::var( self.special_functions .use_function_uplc(CONSTR_FIELDS_EXPOSER.to_string()), ) .apply(value) .choose_list(term.delay(), otherwise) .force(); Some(term) } Air::ListEmpty => { let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let otherwise = arg_stack.pop().unwrap(); term = value.choose_list(term.delay(), otherwise).force(); Some(term) } Air::Tuple { count, tipo } => { let mut args = vec![]; let tuple_sub_types = tipo.get_inner_types(); for _ in 0..count { let arg = arg_stack.pop().unwrap(); args.push(arg); } let mut constants = vec![]; for arg in &args { let maybe_const = extract_constant(arg); if let Some(c) = maybe_const { constants.push(c); } } if constants.len() == args.len() { let data_constants = builder::convert_constants_to_data(constants); let term = Term::Constant( UplcConstant::ProtoList(UplcType::Data, data_constants).into(), ); Some(term) } else { let mut term = Term::empty_list(); for (arg, tipo) in args.into_iter().zip(tuple_sub_types.into_iter()).rev() { term = Term::mk_cons() .apply(builder::convert_type_to_data(arg, &tipo)) .apply(term); } Some(term) } } Air::Pair { tipo } => { let fst = arg_stack.pop().unwrap(); let snd = arg_stack.pop().unwrap(); match (extract_constant(&fst), extract_constant(&snd)) { (Some(fst), Some(snd)) => { let mut pair_fields = builder::convert_constants_to_data(vec![fst, snd]); let term = Term::Constant( UplcConstant::ProtoPair( UplcType::Data, UplcType::Data, pair_fields.remove(0).into(), pair_fields.remove(0).into(), ) .into(), ); Some(term) } _ => { let term = Term::mk_pair_data() .apply(builder::convert_type_to_data( fst, &tipo.get_inner_types()[0], )) .apply(builder::convert_type_to_data( snd, &tipo.get_inner_types()[1], )); Some(term) } } } Air::RecordUpdate { highest_index, indices, tipo, } => { let tail_name_prefix = "__tail_index"; let data_type = lookup_data_type_by_tipo(&self.data_types, &tipo).unwrap_or_else(|| { panic!( "Attempted record update on an unknown type!\ntype: {:#?}", tipo ) }); assert!( !data_type.is_never(), "Attempted record update on a Never type.", ); let constructor_field_count = data_type.constructors[0].arguments.len(); let record = arg_stack.pop().unwrap(); let mut args = IndexMap::new(); let mut unchanged_field_indices = vec![]; // plus 2 so we get one index higher than the record update index // then we add that and any other unchanged fields to an array to later create the // lambda bindings unchanged_field_indices.push(0); let mut prev_index = 0; for (index, tipo) in indices .into_iter() .sorted_by(|(index1, _), (index2, _)| index1.cmp(index2)) { let arg = arg_stack.pop().unwrap(); args.insert(index, (tipo.clone(), arg)); for field_index in (prev_index + 1)..index { unchanged_field_indices.push(field_index); } prev_index = index; } unchanged_field_indices.push(prev_index + 1); let mut term = Term::var(format!("{tail_name_prefix}_{}", highest_index + 1)); for current_index in (0..(highest_index + 1)).rev() { let tail_name = format!("{tail_name_prefix}_{current_index}"); if let Some((tipo, arg)) = args.get(¤t_index) { term = Term::mk_cons() .apply(builder::convert_type_to_data(arg.clone(), tipo)) .apply(term); } else { term = Term::mk_cons() .apply(Term::head_list().apply(Term::var(tail_name))) .apply(term); } } term = Term::constr_data() .apply(Term::integer(0.into())) .apply(term); if unchanged_field_indices.len() > 1 { let (prev_index, rest_list) = unchanged_field_indices .split_last() .unwrap_or_else(|| panic!("WHAT HAPPENED")); let mut prev_index = *prev_index; for index in rest_list.iter().rev() { let index = *index; let suffix_tail = format!("{tail_name_prefix}_{prev_index}"); let tail = format!("{tail_name_prefix}_{index}"); let mut tail_list = Term::var(tail); if index < prev_index { tail_list = tail_list.repeat_tail_list(prev_index - index); if prev_index == constructor_field_count { term = term.lambda(suffix_tail).apply(Term::empty_list()); } else { term = term.lambda(suffix_tail).apply(tail_list); } } prev_index = index; } } term = term.lambda(format!("{tail_name_prefix}_0")).apply( Term::var( self.special_functions .use_function_uplc(CONSTR_FIELDS_EXPOSER.to_string()), ) .apply(record), ); Some(term) } Air::UnOp { op } => { let value = arg_stack.pop().unwrap(); let term = match op { UnOp::Not => value.if_then_else(Term::bool(false), Term::bool(true)), UnOp::Negate => { if let Term::Constant(c) = &value { if let UplcConstant::Integer(i) = c.as_ref() { Term::integer(-i) } else { Term::subtract_integer() .apply(Term::integer(0.into())) .apply(value) } } else { Term::subtract_integer() .apply(Term::integer(0.into())) .apply(value) } } }; Some(term) } Air::TupleAccessor { tipo, names, is_expect, } => { let inner_types = tipo.get_inner_types(); let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let otherwise = if is_expect { arg_stack.pop().unwrap() } else { Term::Error.delay() }; let list_id = self.id_gen.next(); let mut id_list = vec![]; id_list.push(list_id); names.iter().for_each(|_| { id_list.push(self.id_gen.next()); }); let names_types = names .into_iter() .zip(inner_types) .zip(id_list) .map(|((name, tipo), id)| (name, tipo, id)) .collect_vec(); term = builder::list_access_to_uplc( &names_types, false, term, false, is_expect.into(), otherwise, ) .apply(value); Some(term) } Air::PairAccessor { fst, snd, tipo, is_expect, } => { let inner_types = tipo.get_inner_types(); let value = arg_stack.pop().unwrap(); let mut term = arg_stack.pop().unwrap(); let otherwise = if is_expect { arg_stack.pop().unwrap() } else { Term::Error.delay() }; let list_id = self.id_gen.next(); if let Some(name) = snd { let value = Term::snd_pair().apply(Term::var(format!("__pair_{list_id}"))); term = if is_expect { if otherwise == Term::Error.delay() { term.lambda(name) .apply(unknown_data_to_type(value, &inner_types[1])) } else { softcast_data_to_type_otherwise( value, &name, &inner_types[1], term, otherwise.clone(), ) } } else { term.lambda(name) .apply(known_data_to_type(value, &inner_types[1])) } } if let Some(name) = fst { let value = Term::fst_pair().apply(Term::var(format!("__pair_{list_id}"))); term = if is_expect { if otherwise == Term::Error.delay() { term.lambda(name) .apply(unknown_data_to_type(value, &inner_types[0])) } else { softcast_data_to_type_otherwise( value, &name, &inner_types[0], term, otherwise, ) } } else { term.lambda(name) .apply(known_data_to_type(value, &inner_types[0])) } } term = term.lambda(format!("__pair_{list_id}")).apply(value); Some(term) } Air::Trace { .. } => { let text = arg_stack.pop().unwrap(); let term = arg_stack.pop().unwrap(); let term = term.delayed_trace(text); Some(term) } Air::ErrorTerm { validator, .. } => { if validator { Some(Term::Error.apply(Term::Error.force())) } else { Some(Term::Error) } } Air::NoOp => None, Air::SoftCastLet { name, tipo } => { let value = arg_stack.pop().unwrap(); let then = arg_stack.pop().unwrap(); let otherwise = arg_stack.pop().unwrap(); if otherwise == Term::Error.delay() { Some(then.lambda(name).apply(unknown_data_to_type(value, &tipo))) } else { Some(softcast_data_to_type_otherwise( value, &name, &tipo, then, otherwise, )) } } Air::ExtractField { tipo } => { let arg = arg_stack.pop().unwrap(); Some(known_data_to_type(Term::head_list().apply(arg), &tipo)) } } } }