diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index f71457be..2cc70a0d 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -147,7 +147,7 @@ impl Reference { }, Type::Pair { fst, snd, .. } => Self { inner: format!( - "Pair{fst}{snd}", + "Pair${fst}_{snd}", fst = Self::from_type(fst, type_parameters), snd = Self::from_type(snd, type_parameters) ), diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 206d2891..be762017 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -36,6 +36,18 @@ pub enum Declaration { Inline(Box), } +impl Declaration { + pub fn map(self, transform: F) -> Declaration + where + F: FnOnce(A) -> B, + { + match self { + Declaration::Referenced(reference) => Declaration::Referenced(reference), + Declaration::Inline(inner) => Declaration::Inline(transform(*inner).into()), + } + } +} + impl<'a, T> Declaration { pub fn reference(&'a self) -> Option<&'a Reference> { match self { @@ -297,18 +309,22 @@ impl Annotated { // make all types abide by this convention. let data = match definitions.try_lookup(&generic).cloned() { Some(Annotated { - annotated: Schema::Data(Data::List(Items::Many(xs))), + annotated: Schema::Pair(left, right), .. - }) if xs.len() == 2 => { + }) => { definitions.remove(&generic); - Data::Map( - xs.first() - .expect("length (== 2) checked in pattern clause") - .to_owned(), - xs.last() - .expect("length (== 2) checked in pattern clause") - .to_owned(), - ) + + let left = left.map(|inner| match inner { + Schema::Data(data) => data, + _ => panic!("impossible: left inhabitant of pair isn't Data but: {inner:#?}"), + }); + + let right = right.map(|inner| match inner { + Schema::Data(data) => data, + _ => panic!("impossible: right inhabitant of pair isn't Data but: {inner:#?}"), + }); + + Data::Map(left, right) } _ => Data::List(Items::One(Declaration::Referenced(generic))), @@ -350,6 +366,25 @@ impl Annotated { annotated, }) }), + + Type::Pair { fst, snd, .. } => { + definitions.register(type_info, &type_parameters.clone(), |definitions| { + let left = Annotated::do_from_type(fst, modules, type_parameters, definitions) + .map(Declaration::Referenced) + .map_err(|e| e.backtrack(type_info))?; + + let right = Annotated::do_from_type(snd, modules, type_parameters, definitions) + .map(Declaration::Referenced) + .map_err(|e| e.backtrack(type_info))?; + + Ok(Annotated { + title: Some("Pair".to_owned()), + description: None, + annotated: Schema::Pair(left, right), + }) + }) + } + Type::Tuple { elems, .. } => { definitions.register(type_info, &type_parameters.clone(), |definitions| { let elems = elems @@ -368,6 +403,7 @@ impl Annotated { }) }) } + Type::Var { tipo, .. } => match tipo.borrow().deref() { TypeVar::Link { tipo } => { Annotated::do_from_type(tipo, modules, type_parameters, definitions) @@ -383,8 +419,8 @@ impl Annotated { Err(Error::new(ErrorContext::UnboundTypeVariable, type_info)) } }, + Type::Fn { .. } => unreachable!(), - Type::Pair { .. } => unreachable!(), } } } @@ -922,9 +958,6 @@ pub enum ErrorContext { #[error("I caught a free variable in the contract's interface boundary.")] FreeTypeVariable, - #[error("I had the misfortune to find an invalid type in an interface boundary.")] - ExpectedData, - #[error("I figured you tried to export a function in your contract's binary interface.")] UnexpectedFunction, @@ -1002,18 +1035,6 @@ If your contract doesn't need datum or redeemer, you can always give them the ty .if_supports_color(Stdout, |s| s.bold()) ), - ErrorContext::ExpectedData => format!( - r#"While figuring out the outward-facing specification for your contract, I found a type that cannot actually be represented as valid Untyped Plutus Core (the low-level language Cardano uses to execute smart-contracts. For example, it isn't possible to have a list or a tuple of {type_String} because the underlying execution engine doesn't allow it. - -There are few restrictions like this one. In this instance, here's the types I followed and that led me to this problem: - -╰─▶ {breadcrumbs}"#, - type_String = "String" - .if_supports_color(Stdout, |s| s.bright_blue()) - .if_supports_color(Stdout, |s| s.bold()), - breadcrumbs = Error::fmt_breadcrumbs(&self.breadcrumbs) - ), - ErrorContext::UnexpectedFunction => format!( r#"I can't allow that. Functions aren't serializable as data on-chain and thus cannot be used within your datum and/or redeemer types. diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__list_2_tuples_as_list.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__list_2_tuples_as_list.snap new file mode 100644 index 00000000..ec07e3da --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__list_2_tuples_as_list.snap @@ -0,0 +1,57 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\ntype Dict {\n inner: List<(ByteArray, value)>\n}\n\ntype UUID { UUID }\n\nvalidator {\n fn list_2_tuples_as_list(redeemer: Dict, ctx: Void) {\n True\n }\n}\n" +--- +{ + "title": "test_module.list_2_tuples_as_list", + "redeemer": { + "title": "redeemer", + "schema": { + "$ref": "#/definitions/test_module~1Dict$test_module~1UUID_Int" + } + }, + "compiledCode": "59019c010000323232323232323232232253330054a22930a9980324811856616c696461746f722072657475726e65642066616c736500136563253330043370e900018031baa0011325333009001153300600416132533300a300c002132498c8cc004004008894ccc03000452613233003003300f0023232533300e001153300b00916132325333010001153300d00b1613253330113013002149854cc03803058c94cccccc05000454cc0380305854cc0380305854cc038030584dd68008a998070060b180880098088011929999998090008a998060050b0a998060050b0a998060050b0a998060050b09bae001300f0015333333010001153300a00816153300a00816137580022a660140102c2a660140102c601a0022a6600e00a2c64a66666601a0022a6600e00a2c2a6600e00a2c26eb000454cc01c0145854cc01c01458c028004c01cdd50008a998028018b299999980500088008a998020010b0a998020010b0a998020010b0a998020010b2491972656465656d65723a20446963743c555549442c20496e743e005734ae7155ceaab9e5573eae855d12ba41", + "hash": "6027685dde99d967b45333852fe9f59531237d85fcb6b6feb2890672", + "definitions": { + "ByteArray": { + "dataType": "bytes" + }, + "Int": { + "dataType": "integer" + }, + "List$Tuple$ByteArray_Int": { + "dataType": "list", + "items": { + "$ref": "#/definitions/Tuple$ByteArray_Int" + } + }, + "Tuple$ByteArray_Int": { + "title": "Tuple", + "dataType": "list", + "items": [ + { + "$ref": "#/definitions/ByteArray" + }, + { + "$ref": "#/definitions/Int" + } + ] + }, + "test_module/Dict$test_module/UUID_Int": { + "title": "Dict", + "anyOf": [ + { + "title": "Dict", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "inner", + "$ref": "#/definitions/List$Tuple$ByteArray_Int" + } + ] + } + ] + } + } +} diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__list_pairs_as_map.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__list_pairs_as_map.snap new file mode 100644 index 00000000..6037567e --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__list_pairs_as_map.snap @@ -0,0 +1,48 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\ntype Dict {\n inner: List>\n}\n\ntype UUID { UUID }\n\nvalidator {\n fn list_pairs_as_map(redeemer: Dict, ctx: Void) {\n True\n }\n}\n" +--- +{ + "title": "test_module.list_pairs_as_map", + "redeemer": { + "title": "redeemer", + "schema": { + "$ref": "#/definitions/test_module~1Dict$test_module~1UUID_Int" + } + }, + "compiledCode": "59014e010000323232323232323232232253330054a22930a9980324811856616c696461746f722072657475726e65642066616c736500136563253330043370e900018031baa0011325333009001153300600416132533300a300c002132498c8cc004004008894ccc03000452613233003003300f0023232325333333012001153300c00a16153300c00a16153300c00a161375a0022a660180142c601a00464a6666660220022a660160122c2a660160122c2a660160122c2a660160122c26eb8004c02c004c03400454cc01c01458c94cccccc03400454cc01c014584dd58008a998038028b0a998038028b0a998038028b180500098039baa001153300500316533333300a001100115330040021615330040021615330040021615330040021649011972656465656d65723a20446963743c555549442c20496e743e005734ae7155ceaab9e5573eae855d12ba41", + "hash": "de6d51e2a272ec0ab73566bbb32700ad5864fdd01290dd925e35ebb4", + "definitions": { + "ByteArray": { + "dataType": "bytes" + }, + "Int": { + "dataType": "integer" + }, + "List$Pair$ByteArray_Int": { + "dataType": "map", + "keys": { + "$ref": "#/definitions/ByteArray" + }, + "values": { + "$ref": "#/definitions/Int" + } + }, + "test_module/Dict$test_module/UUID_Int": { + "title": "Dict", + "anyOf": [ + { + "title": "Dict", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "inner", + "$ref": "#/definitions/List$Pair$ByteArray_Int" + } + ] + } + ] + } + } +} diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index dcb029e0..f701d633 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -495,7 +495,7 @@ mod tests { } #[test] - fn list_2_tuples_as_map() { + fn list_2_tuples_as_list() { assert_validator!( r#" type Dict { @@ -505,7 +505,26 @@ mod tests { type UUID { UUID } validator { - fn list_2_tuples_as_map(redeemer: Dict, ctx: Void) { + fn list_2_tuples_as_list(redeemer: Dict, ctx: Void) { + True + } + } + "# + ); + } + + #[test] + fn list_pairs_as_map() { + assert_validator!( + r#" + type Dict { + inner: List> + } + + type UUID { UUID } + + validator { + fn list_pairs_as_map(redeemer: Dict, ctx: Void) { True } }