use aiken_lang::{ast::TypedFunction, gen_uplc::CodeGenerator}; use miette::NamedSource; use uplc::ast::{DeBruijn, Program}; use crate::{ blueprint::{ self, definitions::Definitions, parameter::Parameter, schema::{Annotated, Schema}, }, module::{CheckedModule, CheckedModules}, }; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct Export { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub doc: Option, #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(default)] pub parameters: Vec, #[serde(flatten)] pub program: Program, #[serde(skip_serializing_if = "Definitions::is_empty")] #[serde(default)] pub definitions: Definitions>, } impl Export { pub fn from_function( func: &TypedFunction, module: &CheckedModule, generator: &mut CodeGenerator, modules: &CheckedModules, ) -> Result { let mut definitions = Definitions::new(); let parameters = func .arguments .iter() .map(|param| { Annotated::from_type( modules.into(), blueprint::validator::tipo_or_annotation(module, param), &mut definitions, ) .map(|schema| Parameter { title: Some(param.arg_name.get_label()), schema, }) .map_err(|error| blueprint::Error::Schema { error, location: param.location, source_code: NamedSource::new( module.input_path.display().to_string(), module.code.clone(), ), }) }) .collect::>()?; let program = generator .generate_raw(&func.body, &func.arguments, &module.name) .to_debruijn() .unwrap(); Ok(Export { name: format!("{}.{}", &module.name, &func.name), doc: func.doc.clone(), parameters, program, definitions, }) } } #[cfg(test)] mod tests { use super::{CheckedModules, Export}; use crate::tests::TestProject; use aiken_lang::{ self, ast::{TraceLevel, Tracing}, }; macro_rules! assert_export { ($code:expr) => { let mut project = TestProject::new(); let modules = CheckedModules::singleton(project.check(project.parse(indoc::indoc! { $code }))); let mut generator = project.new_generator( Tracing::All(TraceLevel::Verbose), ); let (module, func) = modules .functions() .next() .expect("source code did no yield any exports"); let export = Export::from_function(func, module, &mut generator, &modules); match export { Err(e) => insta::with_settings!({ description => concat!("Code:\n\n", indoc::indoc! { $code }), omit_expression => true }, { insta::assert_debug_snapshot!(e); }), Ok(validator) => insta::with_settings!({ description => concat!("Code:\n\n", indoc::indoc! { $code }), omit_expression => true }, { insta::assert_json_snapshot!(validator); }), }; }; } #[test] fn basic_export() { assert_export!( r#" pub fn add(a: Int, b: Int) -> Int { a + b } "# ); } #[test] fn illegal_opaque_type() { assert_export!( r#" pub opaque type Thing { a: Int } pub fn add(a: Thing, b: Int) -> Int { a.a + b } "# ); } #[test] fn recursive_types() { assert_export!( r#" pub type Foo { Empty Bar(a, Foo) } pub fn add(a: Foo, b: Foo) -> Int { when (a, b) is { (Empty, Empty) -> 0 (Bar(x, y), Bar(c, d)) -> x + c + add(y, d) (Empty, Bar(c, d)) -> c + add(Empty, d) (Bar(x, y), Empty) -> x + add(y, Empty) } } "# ); } #[test] fn cannot_export_generics() { assert_export!( r#" pub fn add(_a: a, _b: b) -> Bool { True } "# ); } }