230 lines
6.5 KiB
Rust
230 lines
6.5 KiB
Rust
use crate::{
|
|
blueprint::{
|
|
self,
|
|
definitions::Definitions,
|
|
parameter::Parameter,
|
|
schema::{Annotated, Declaration, Schema},
|
|
},
|
|
module::{CheckedModule, CheckedModules},
|
|
};
|
|
use aiken_lang::{
|
|
ast::{ArgName, Span, TypedArg, TypedFunction},
|
|
gen_uplc::CodeGenerator,
|
|
plutus_version::PlutusVersion,
|
|
};
|
|
use miette::NamedSource;
|
|
use uplc::ast::SerializableProgram;
|
|
|
|
#[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>,
|
|
|
|
pub return_type: Parameter,
|
|
|
|
#[serde(flatten)]
|
|
pub program: SerializableProgram,
|
|
|
|
#[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,
|
|
plutus_version: &PlutusVersion,
|
|
) -> 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: Declaration::Referenced(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 return_type = Annotated::from_type(
|
|
modules.into(),
|
|
blueprint::validator::tipo_or_annotation(
|
|
module,
|
|
&TypedArg {
|
|
arg_name: ArgName::Discarded {
|
|
name: "".to_string(),
|
|
label: "".to_string(),
|
|
location: Span::empty(),
|
|
},
|
|
location: Span::empty(),
|
|
annotation: func.return_annotation.clone(),
|
|
doc: None,
|
|
is_validator_param: false,
|
|
tipo: func.return_type.clone(),
|
|
},
|
|
),
|
|
&mut definitions,
|
|
)
|
|
.map(|schema| Parameter {
|
|
title: Some("return_type".to_string()),
|
|
schema: Declaration::Referenced(schema),
|
|
})
|
|
.map_err(|error| blueprint::Error::Schema {
|
|
error,
|
|
location: func.location,
|
|
source_code: NamedSource::new(
|
|
module.input_path.display().to_string(),
|
|
module.code.clone(),
|
|
),
|
|
})?;
|
|
|
|
let program = generator
|
|
.generate_raw(&func.body, &func.arguments, &module.name)
|
|
.to_debruijn()
|
|
.unwrap();
|
|
|
|
let program = match plutus_version {
|
|
PlutusVersion::V1 => SerializableProgram::PlutusV1Program(program),
|
|
PlutusVersion::V2 => SerializableProgram::PlutusV2Program(program),
|
|
PlutusVersion::V3 => SerializableProgram::PlutusV3Program(program),
|
|
};
|
|
|
|
Ok(Export {
|
|
name: format!("{}.{}", &module.name, &func.name),
|
|
doc: func.doc.clone(),
|
|
parameters,
|
|
return_type,
|
|
program,
|
|
definitions,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{CheckedModules, Export};
|
|
use crate::tests::TestProject;
|
|
use aiken_lang::{
|
|
self,
|
|
ast::{TraceLevel, Tracing},
|
|
plutus_version::PlutusVersion,
|
|
};
|
|
|
|
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, &PlutusVersion::default());
|
|
|
|
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
|
|
}
|
|
"#
|
|
);
|
|
}
|
|
}
|