aiken/crates/aiken-project/src/export.rs

184 lines
4.8 KiB
Rust

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<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub parameters: Vec<Parameter>,
#[serde(flatten)]
pub program: Program<DeBruijn>,
#[serde(skip_serializing_if = "Definitions::is_empty")]
#[serde(default)]
pub definitions: Definitions<Annotated<Schema>>,
}
impl Export {
pub fn from_function(
func: &TypedFunction,
module: &CheckedModule,
generator: &mut CodeGenerator,
modules: &CheckedModules,
) -> Result<Export, blueprint::Error> {
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::<Result<_, _>>()?;
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<a> {
Empty
Bar(a, Foo<a>)
}
pub fn add(a: Foo<Int>, b: Foo<Int>) -> 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
}
"#
);
}
}