diff --git a/crates/aiken-project/src/export.rs b/crates/aiken-project/src/export.rs index e40e94eb..f364348d 100644 --- a/crates/aiken-project/src/export.rs +++ b/crates/aiken-project/src/export.rs @@ -38,11 +38,6 @@ impl Export { generator: &mut CodeGenerator, modules: &CheckedModules, ) -> Result { - let program = generator - .generate_raw(&func.body, &func.arguments, &module.name) - .to_debruijn() - .unwrap(); - let mut definitions = Definitions::new(); let parameters = func @@ -69,6 +64,11 @@ impl Export { }) .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(), @@ -78,3 +78,106 @@ impl Export { }) } } + +#[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 + } + "# + ); + } +} diff --git a/crates/aiken-project/src/module.rs b/crates/aiken-project/src/module.rs index ff70e38a..49aad2a5 100644 --- a/crates/aiken-project/src/module.rs +++ b/crates/aiken-project/src/module.rs @@ -477,6 +477,20 @@ impl CheckedModules { items.into_iter() } + pub fn functions(&self) -> impl Iterator { + let mut items = vec![]; + + for module in self.0.values() { + for some_definition in module.ast.definitions() { + if let Definition::Fn(def) = some_definition { + items.push((module, def)); + } + } + } + + items.into_iter() + } + pub fn into_validators(self) -> impl Iterator { self.0 .into_values() diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__basic_export.snap b/crates/aiken-project/src/snapshots/aiken_project__export__tests__basic_export.snap new file mode 100644 index 00000000..e28412b8 --- /dev/null +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__basic_export.snap @@ -0,0 +1,28 @@ +--- +source: crates/aiken-project/src/export.rs +description: "Code:\n\npub fn add(a: Int, b: Int) -> Int {\n a + b\n}\n" +--- +{ + "name": "test_module.add", + "parameters": [ + { + "title": "a", + "schema": { + "$ref": "#/definitions/Int" + } + }, + { + "title": "b", + "schema": { + "$ref": "#/definitions/Int" + } + } + ], + "compiledCode": "500100002322337000046eb4004dd68009", + "hash": "bcd6700b4dba798a7d19c5769ef3deb21423f8809594a6942860dd1f", + "definitions": { + "Int": { + "dataType": "integer" + } + } +} diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap new file mode 100644 index 00000000..a94ac91f --- /dev/null +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap @@ -0,0 +1,24 @@ +--- +source: crates/aiken-project/src/export.rs +description: "Code:\n\npub fn add(_a: a, _b: b) -> Bool {\n True\n}\n" +--- +Schema { + error: Error { + context: FreeTypeVariable, + breadcrumbs: [ + Var { + tipo: RefCell { + value: Generic { + id: 33, + }, + }, + alias: None, + }, + ], + }, + location: 11..16, + source_code: NamedSource { + name: "", + source: "", + , +} diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__illegal_opaque_type.snap b/crates/aiken-project/src/snapshots/aiken_project__export__tests__illegal_opaque_type.snap new file mode 100644 index 00000000..b1f8008f --- /dev/null +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__illegal_opaque_type.snap @@ -0,0 +1,24 @@ +--- +source: crates/aiken-project/src/export.rs +description: "Code:\n\npub opaque type Thing {\n a: Int\n}\n\npub fn add(a: Thing, b: Int) -> Int {\n a.a + b\n}\n" +--- +Schema { + error: Error { + context: IllegalOpaqueType, + breadcrumbs: [ + App { + public: true, + contains_opaque: true, + module: "test_module", + name: "Thing", + args: [], + alias: None, + }, + ], + }, + location: 47..55, + source_code: NamedSource { + name: "", + source: "", + , +} diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__recursive_types.snap b/crates/aiken-project/src/snapshots/aiken_project__export__tests__recursive_types.snap new file mode 100644 index 00000000..126f64f0 --- /dev/null +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__recursive_types.snap @@ -0,0 +1,52 @@ +--- +source: crates/aiken-project/src/export.rs +description: "Code:\n\npub type Foo {\n Empty\n Bar(a, Foo)\n}\n\npub fn add(a: Foo, b: Foo) -> Int {\n when (a, b) is {\n (Empty, Empty) -> 0\n (Bar(x, y), Bar(c, d)) -> x + c + add(y, d)\n (Empty, Bar(c, d)) -> c + add(Empty, d)\n (Bar(x, y), Empty) -> x + add(y, Empty)\n }\n}\n" +--- +{ + "name": "test_module.add", + "parameters": [ + { + "title": "a", + "schema": { + "$ref": "#/definitions/test_module~1Foo$Int" + } + }, + { + "title": "b", + "schema": { + "$ref": "#/definitions/test_module~1Foo$Int" + } + } + ], + "compiledCode": "5901c3010000323232323222323232323253330083002300937540062a666010600460126ea801052000001001132323232533300b3004300c375400c2646464a66601c600e601e6ea80284c8cdc019b80003375a60240026600c0046024602600260206ea8028010c040c044008dd6980780098069baa006001132533300b3005300c375400c2a666016600860186ea801c4c8cdc01bad300f001330034c103d8798000300f3010001300d375400e00200226466e00dd698070009980118071807800a60103d8798000300c375400a600200244464646464a66601e601260206ea800854ccc03cc024c040dd50018a4000002002264a66601e601060206ea80084c8c8c94ccc048c02cc04cdd500309919b80337000066eb4c058004ccc02c02c008c058c05c004c050dd5003002180a180a8011bad301300130113754004002264a66601e601260206ea800854ccc03cc020c040dd500189919b80375a6026002666010010980103d8798000301330140013011375400600200226466e00dd6980900099980380398091809800a60103d879800030103754002601c004601c00266ec0008004dc3a40046e1d20003006002300600133760004002ae6955ceaab9e5742ae89", + "hash": "4ce96c928b3be798496fca0ec3666d15d09004115df638801715b5e8", + "definitions": { + "Int": { + "dataType": "integer" + }, + "test_module/Foo$Int": { + "title": "Foo", + "anyOf": [ + { + "title": "Empty", + "dataType": "constructor", + "index": 0, + "fields": [] + }, + { + "title": "Bar", + "dataType": "constructor", + "index": 1, + "fields": [ + { + "$ref": "#/definitions/Int" + }, + { + "$ref": "#/definitions/test_module~1Foo$Int" + } + ] + } + ] + } + } +}