From d3885ac67a119673f285de229587e0ecfdb7a62c Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 30 Jan 2025 15:45:50 +0100 Subject: [PATCH 1/4] prune orphan pair definitions after full blueprint generation. This is to avoid pruning a definition which may end up needed later on. The issue can be seen when definition to a Pair is used *before* another Map definitions that uses this same Pair. Before this commit, the Map definition would simply remove the definition generated for the Pair, since it would be pointless (and it is a lot easier to generate those pointless definition than trying to remember we are currently generating definition for a Map). So now, we defer the removal of the orphan definition to after all defnitions have been generated by basically looking at a dependency graph. I _could have_ used pet-graph on this to solve it similar to how we do package dependencies; but given that we only really need to that for pairs, the problem is relatively simple to solve (though cumbersome since we need to traverse all defintions). Fixes #1086. --- .../src/blueprint/definitions.rs | 155 +++++++++++++++++- crates/aiken-project/src/blueprint/schema.rs | 2 - ...ueprint__validator__tests__map_in_map.snap | 73 +++++++++ ...print__validator__tests__pair_in_pair.snap | 72 ++++++++ ...validator__tests__pair_used_after_map.snap | 75 +++++++++ ...alidator__tests__pair_used_before_map.snap | 75 +++++++++ .../aiken-project/src/blueprint/validator.rs | 71 +++++++- 7 files changed, 519 insertions(+), 4 deletions(-) create mode 100644 crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__map_in_map.snap create mode 100644 crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_in_pair.snap create mode 100644 crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap create mode 100644 crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index efcd8569..7815556d 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -1,3 +1,10 @@ +use crate::{ + blueprint::{ + parameter::Parameter, + schema::{Data, Declaration, Items}, + }, + Annotated, Schema, +}; use aiken_lang::tipo::{pretty::resolve_alias, Type, TypeAliasAnnotation, TypeVar}; use itertools::Itertools; use serde::{ @@ -6,7 +13,7 @@ use serde::{ ser::{Serialize, SerializeStruct, Serializer}, }; use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, BTreeSet, HashMap}, fmt::{self, Display}, ops::Deref, rc::Rc, @@ -88,6 +95,152 @@ impl Definitions { } } +impl Definitions> { + /// Remove orphan definitions. Such definitions can exist due to List of pairs being + /// transformed to Maps. As a consequence, we may generate temporary Pair definitions + /// which needs to get cleaned up later. + /// + /// Initially, we would clean those Pair definitions right-away, but this would cause + /// Pair definitions to be missing in some legit cases when the Pair is also used as a + /// standalone type. + pub fn prune_orphan_pairs(&mut self, parameters: Vec<&Parameter>) { + fn traverse_schema( + src: Reference, + schema: &Schema, + usage: &mut BTreeMap>, + ) { + match schema { + Schema::Unit + | Schema::Boolean + | Schema::Integer + | Schema::Bytes + | Schema::String => (), + Schema::Pair(left, right) => { + mark(src.clone(), left, usage, traverse_schema); + mark(src, right, usage, traverse_schema); + } + Schema::List(Items::One(item)) => { + mark(src, item, usage, traverse_schema); + } + Schema::List(Items::Many(items)) => { + items.iter().for_each(|item| { + mark(src.clone(), item, usage, traverse_schema); + }); + } + Schema::Data(data) => traverse_data(src, data, usage), + } + } + + fn traverse_data( + src: Reference, + data: &Data, + usage: &mut BTreeMap>, + ) { + match data { + Data::Opaque | Data::Integer | Data::Bytes => (), + Data::List(Items::One(item)) => { + mark(src, item, usage, traverse_data); + } + Data::List(Items::Many(items)) => { + items.iter().for_each(|item| { + mark(src.clone(), item, usage, traverse_data); + }); + } + Data::Map(keys, values) => { + mark(src.clone(), keys, usage, traverse_data); + mark(src, values, usage, traverse_data); + } + Data::AnyOf(items) => { + items.iter().for_each(|item| { + item.annotated.fields.iter().for_each(|field| { + mark(src.clone(), &field.annotated, usage, traverse_data); + }) + }); + } + } + } + + /// A mutually recursive function which works with either traverse_data or traverse_schema; + /// it is meant to peel the 'Declaration' and keep traversing if needed (when inline). + fn mark( + src: Reference, + declaration: &Declaration, + usage: &mut BTreeMap>, + mut traverse: F, + ) where + F: FnMut(Reference, &T, &mut BTreeMap>), + { + match declaration { + Declaration::Referenced(reference) => { + if let Some(dependencies) = usage.get_mut(reference) { + dependencies.insert(src); + } + } + Declaration::Inline(ref schema) => traverse(src, schema, usage), + } + } + + let mut usage: BTreeMap> = BTreeMap::new(); + + // 1. List all Pairs definitions + for (src, annotated) in self.inner.iter() { + if let Some(schema) = annotated.as_ref().map(|entry| &entry.annotated) { + if matches!(schema, Schema::Pair(_, _)) { + usage.insert(Reference::new(src), BTreeSet::new()); + } + } + } + + // 2. Mark those used in other definitions + for (src, annotated) in self.inner.iter() { + if let Some(schema) = annotated.as_ref().map(|entry| &entry.annotated) { + traverse_schema(Reference::new(src), schema, &mut usage) + } + } + + // 3. Mark also pairs definitions used in parameters / datums / redeemers + for (ix, param) in parameters.iter().enumerate() { + mark( + // NOTE: The name isn't important, so long as it doesn't clash with other typical + // references. If a definition is used in either of the parameter, it'll appear in + // its dependencies and won't ever be removed because parameters are considered + // always necessary. + Reference::new(&format!("__param^{ix}")), + ¶m.schema, + &mut usage, + traverse_schema, + ); + } + + // 4. Repeatedly remove pairs definitions that aren't used. We need doing this repeatedly + // because a Pair definition may only be used by an unused one; so as we prune the first + // unused definitions, new ones may become unused. + let mut last_len = usage.len(); + loop { + let mut unused = None; + for (k, v) in usage.iter() { + if v.is_empty() { + unused = Some(k.clone()); + } + } + + if let Some(k) = unused { + usage.remove(&k); + self.inner.remove(k.as_key().as_str()); + for (_, v) in usage.iter_mut() { + v.remove(&k); + } + } + + if usage.len() == last_len { + break; + } else { + last_len = usage.len(); + } + } + } +} + // ---------- Reference /// A URI pointer to an underlying data-type. diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index f8b68c2c..52b1b521 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -346,8 +346,6 @@ impl Annotated { annotated: Schema::Pair(left, right), .. }) => { - definitions.remove(&generic); - let left = left.map(|inner| match inner { Schema::Data(data) => data, _ => panic!("impossible: left inhabitant of pair isn't Data but: {inner:#?}"), diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__map_in_map.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__map_in_map.snap new file mode 100644 index 00000000..c87f4655 --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__map_in_map.snap @@ -0,0 +1,73 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\npub type OuterMap =\n List>\n\npub type InnerMap =\n List>\n\nvalidator placeholder {\n spend(_datum: Option, _redeemer: OuterMap, _utxo: Data, _self: Data,) {\n True\n }\n}\n" +--- +{ + "title": "test_module.placeholder.spend", + "datum": { + "title": "_datum", + "schema": { + "$ref": "#/definitions/Void" + } + }, + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/OuterMap" + } + }, + "compiledCode": "", + "hash": "", + "definitions": { + "Bool": { + "title": "Bool", + "anyOf": [ + { + "title": "False", + "dataType": "constructor", + "index": 0, + "fields": [] + }, + { + "title": "True", + "dataType": "constructor", + "index": 1, + "fields": [] + } + ] + }, + "InnerMap": { + "title": "InnerMap", + "dataType": "map", + "keys": { + "$ref": "#/definitions/Int" + }, + "values": { + "$ref": "#/definitions/Bool" + } + }, + "Int": { + "dataType": "integer" + }, + "OuterMap": { + "title": "OuterMap", + "dataType": "map", + "keys": { + "$ref": "#/definitions/Int" + }, + "values": { + "$ref": "#/definitions/InnerMap" + } + }, + "Void": { + "title": "Unit", + "anyOf": [ + { + "dataType": "constructor", + "index": 0, + "fields": [] + } + ] + } + } +} diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_in_pair.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_in_pair.snap new file mode 100644 index 00000000..e5cb9c86 --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_in_pair.snap @@ -0,0 +1,72 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\npub type MyPair =\n Pair\n\npub type InnerPair =\n Pair\n\nvalidator placeholder {\n spend(_datum: Option, _redeemer: List, _utxo: Data, _self: Data,) {\n True\n }\n}\n" +--- +{ + "title": "test_module.placeholder.spend", + "datum": { + "title": "_datum", + "schema": { + "$ref": "#/definitions/Void" + } + }, + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/List$MyPair" + } + }, + "compiledCode": "", + "hash": "", + "definitions": { + "Bool": { + "title": "Bool", + "anyOf": [ + { + "title": "False", + "dataType": "constructor", + "index": 0, + "fields": [] + }, + { + "title": "True", + "dataType": "constructor", + "index": 1, + "fields": [] + } + ] + }, + "InnerPair": { + "title": "InnerPair", + "dataType": "#pair", + "left": { + "$ref": "#/definitions/Int" + }, + "right": { + "$ref": "#/definitions/Bool" + } + }, + "Int": { + "dataType": "integer" + }, + "List$MyPair": { + "dataType": "map", + "keys": { + "$ref": "#/definitions/Int" + }, + "values": { + "$ref": "#/definitions/InnerPair" + } + }, + "Void": { + "title": "Unit", + "anyOf": [ + { + "dataType": "constructor", + "index": 0, + "fields": [] + } + ] + } + } +} diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap new file mode 100644 index 00000000..77ad17ab --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap @@ -0,0 +1,75 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\npub type MyPair =\n Pair\n\npub type MyDatum {\n pairs: List,\n pair: MyPair,\n}\n\nvalidator placeholder {\n spend(_datum: Option, _redeemer: Void, _utxo: Data, _self: Data,) {\n True\n }\n}\n" +--- +{ + "title": "test_module.placeholder.spend", + "datum": { + "title": "_datum", + "schema": { + "$ref": "#/definitions/test_module~1MyDatum" + } + }, + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/Void" + } + }, + "compiledCode": "", + "hash": "", + "definitions": { + "Int": { + "dataType": "integer" + }, + "List$MyPair": { + "dataType": "map", + "keys": { + "$ref": "#/definitions/Int" + }, + "values": { + "$ref": "#/definitions/Int" + } + }, + "MyPair": { + "title": "MyPair", + "dataType": "#pair", + "left": { + "$ref": "#/definitions/Int" + }, + "right": { + "$ref": "#/definitions/Int" + } + }, + "Void": { + "title": "Unit", + "anyOf": [ + { + "dataType": "constructor", + "index": 0, + "fields": [] + } + ] + }, + "test_module/MyDatum": { + "title": "MyDatum", + "anyOf": [ + { + "title": "MyDatum", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "pairs", + "$ref": "#/definitions/List$MyPair" + }, + { + "title": "pair", + "$ref": "#/definitions/MyPair" + } + ] + } + ] + } + } +} diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap new file mode 100644 index 00000000..3bb64dbc --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap @@ -0,0 +1,75 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\npub type MyPair =\n Pair\n\npub type MyDatum {\n pair: MyPair,\n pairs: List,\n}\n\nvalidator placeholder {\n spend(_datum: Option, _redeemer: Void, _utxo: Data, _self: Data,) {\n True\n }\n}\n" +--- +{ + "title": "test_module.placeholder.spend", + "datum": { + "title": "_datum", + "schema": { + "$ref": "#/definitions/test_module~1MyDatum" + } + }, + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/Void" + } + }, + "compiledCode": "", + "hash": "", + "definitions": { + "Int": { + "dataType": "integer" + }, + "List$MyPair": { + "dataType": "map", + "keys": { + "$ref": "#/definitions/Int" + }, + "values": { + "$ref": "#/definitions/Int" + } + }, + "MyPair": { + "title": "MyPair", + "dataType": "#pair", + "left": { + "$ref": "#/definitions/Int" + }, + "right": { + "$ref": "#/definitions/Int" + } + }, + "Void": { + "title": "Unit", + "anyOf": [ + { + "dataType": "constructor", + "index": 0, + "fields": [] + } + ] + }, + "test_module/MyDatum": { + "title": "MyDatum", + "anyOf": [ + { + "title": "MyDatum", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "pair", + "$ref": "#/definitions/MyPair" + }, + { + "title": "pairs", + "$ref": "#/definitions/List$MyPair" + } + ] + } + ] + } + } +} diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index e433df7e..b51665d1 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -122,7 +122,7 @@ impl Validator { ), }) }) - .collect::>()?; + .collect::, _>>()?; let (datum, redeemer) = if func.name == well_known::VALIDATOR_ELSE { (None, None) @@ -202,6 +202,14 @@ impl Validator { schema: Declaration::Inline(Box::new(Schema::Data(Data::Opaque))), })); + definitions.prune_orphan_pairs( + parameters + .iter() + .chain(redeemer.as_ref().map(|x| vec![x]).unwrap_or_default()) + .chain(datum.as_ref().map(|x| vec![x]).unwrap_or_default()) + .collect::>(), + ); + Ok(Validator { title: format!("{}.{}.{}", &module.name, &def.name, &func.name,), description: func.doc.clone(), @@ -736,6 +744,67 @@ mod tests { ); } + #[test] + fn pair_used_after_map() { + assert_validator!( + r#" + pub type MyPair = + Pair + + pub type MyDatum { + pairs: List, + pair: MyPair, + } + + validator placeholder { + spend(_datum: Option, _redeemer: Void, _utxo: Data, _self: Data,) { + True + } + } + "# + ); + } + + #[test] + fn pair_used_before_map() { + assert_validator!( + r#" + pub type MyPair = + Pair + + pub type MyDatum { + pair: MyPair, + pairs: List, + } + + validator placeholder { + spend(_datum: Option, _redeemer: Void, _utxo: Data, _self: Data,) { + True + } + } + "# + ); + } + + #[test] + fn map_in_map() { + assert_validator!( + r#" + pub type OuterMap = + List> + + pub type InnerMap = + List> + + validator placeholder { + spend(_datum: Option, _redeemer: OuterMap, _utxo: Data, _self: Data,) { + True + } + } + "# + ); + } + #[test] fn else_redeemer() { assert_validator!( From 9162df7c6a19706f29bc1f50791f3a10fc728060 Mon Sep 17 00:00:00 2001 From: KtorZ <5680256+KtorZ@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:56:43 +0100 Subject: [PATCH 2/4] fix blueprint schema definitions for pairs. --- .../src/blueprint/definitions.rs | 72 +++++++++++++++++- .../aiken-project/src/blueprint/parameter.rs | 2 +- crates/aiken-project/src/blueprint/schema.rs | 76 ++++++++++++------- ...rint__validator__tests__pair_of_lists.snap | 65 ++++++++++++++++ ...validator__tests__pair_used_after_map.snap | 16 ++-- ...alidator__tests__pair_used_before_map.snap | 16 ++-- ...print__validator__tests__type_aliases.snap | 16 ++-- .../aiken-project/src/blueprint/validator.rs | 32 ++++++-- 8 files changed, 234 insertions(+), 61 deletions(-) create mode 100644 crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_of_lists.snap diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index 7815556d..97b0cffa 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -1,11 +1,11 @@ use crate::{ + Annotated, Schema, blueprint::{ parameter::Parameter, schema::{Data, Declaration, Items}, }, - Annotated, Schema, }; -use aiken_lang::tipo::{pretty::resolve_alias, Type, TypeAliasAnnotation, TypeVar}; +use aiken_lang::tipo::{Type, TypeAliasAnnotation, TypeVar, pretty::resolve_alias}; use itertools::Itertools; use serde::{ self, @@ -103,7 +103,7 @@ impl Definitions> { /// Initially, we would clean those Pair definitions right-away, but this would cause /// Pair definitions to be missing in some legit cases when the Pair is also used as a /// standalone type. - pub fn prune_orphan_pairs(&mut self, parameters: Vec<&Parameter>) { + pub fn prune_orphan_pairs(&mut self, parameters: Vec<&Parameter>) -> &mut Self { fn traverse_schema( src: Reference, schema: &Schema, @@ -238,6 +238,72 @@ impl Definitions> { last_len = usage.len(); } } + + self + } + + pub fn replace_pairs_with_data_lists(&mut self) { + fn swap_declaration(declaration: &mut Declaration) -> Declaration { + match std::mem::replace(declaration, Declaration::Inline(Schema::Unit.into())) { + Declaration::Referenced(reference) => Declaration::Referenced(reference), + Declaration::Inline(mut inline) => { + schema_to_data(&mut inline); + if let Schema::Data(data) = *inline { + Declaration::Inline(data.into()) + } else { + unreachable!() + } + } + } + } + + fn schema_to_data(schema: &mut Schema) { + let items = match schema { + Schema::Data(_) => None, + Schema::Pair(ref mut left, ref mut right) => { + let left = swap_declaration(left); + let right = swap_declaration(right); + Some(Items::Many(vec![left, right])) + } + Schema::List(Items::One(ref mut item)) => { + let item = swap_declaration(item); + Some(Items::One(item)) + } + Schema::List(Items::Many(ref mut items)) => Some(Items::Many( + items.iter_mut().map(swap_declaration).collect(), + )), + Schema::Integer => { + *schema = Schema::Data(Data::Integer); + None + } + Schema::Bytes => { + *schema = Schema::Data(Data::Bytes); + None + } + Schema::String => { + *schema = Schema::Data(Data::Bytes); + None + } + Schema::Unit => { + *schema = Schema::void(); + None + } + Schema::Boolean => { + *schema = Schema::bool(); + None + } + }; + + if let Some(items) = items { + *schema = Schema::Data(Data::List(items)); + } + } + + for (_, entry) in self.inner.iter_mut() { + if let Some(ref mut annotated) = entry { + schema_to_data(&mut annotated.annotated); + } + } } } diff --git a/crates/aiken-project/src/blueprint/parameter.rs b/crates/aiken-project/src/blueprint/parameter.rs index 340eea96..9618c7df 100644 --- a/crates/aiken-project/src/blueprint/parameter.rs +++ b/crates/aiken-project/src/blueprint/parameter.rs @@ -5,8 +5,8 @@ use super::{ }; use std::{iter, ops::Deref}; use uplc::{ - ast::{Constant, Data as UplcData}, PlutusData, + ast::{Constant, Data as UplcData}, }; #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 52b1b521..c5014e60 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -97,6 +97,48 @@ pub enum Schema { Data(Data), } +impl Schema { + pub fn void() -> Self { + Schema::Data(Data::AnyOf(vec![Annotated { + title: None, + description: None, + annotated: Constructor { + index: 0, + fields: vec![], + }, + }])) + } + + pub fn int() -> Self { + Schema::Data(Data::Integer) + } + + pub fn bytes() -> Self { + Schema::Data(Data::Bytes) + } + + pub fn bool() -> Self { + Schema::Data(Data::AnyOf(vec![ + Annotated { + title: Some("False".to_string()), + description: None, + annotated: Constructor { + index: 0, + fields: vec![], + }, + }, + Annotated { + title: Some("True".to_string()), + description: None, + annotated: Constructor { + index: 1, + fields: vec![], + }, + }, + ])) + } +} + /// A schema for Plutus' Data. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Data { @@ -205,46 +247,22 @@ impl Annotated { annotated: Schema::Data(Data::Opaque), }), - "ByteArray" => Ok(with_title(title.as_ref(), Schema::Data(Data::Bytes))), + "ByteArray" => Ok(with_title(title.as_ref(), Schema::bytes())), - "Int" => Ok(with_title(title.as_ref(), Schema::Data(Data::Integer))), + "Int" => Ok(with_title(title.as_ref(), Schema::int())), "String" => Ok(with_title(title.as_ref(), Schema::String)), "Void" => Ok(Annotated { title: title.or(Some("Unit".to_string())), description: None, - annotated: Schema::Data(Data::AnyOf(vec![Annotated { - title: None, - description: None, - annotated: Constructor { - index: 0, - fields: vec![], - }, - }])), + annotated: Schema::void(), }), "Bool" => Ok(Annotated { title: title.or(Some("Bool".to_string())), description: None, - annotated: Schema::Data(Data::AnyOf(vec![ - Annotated { - title: Some("False".to_string()), - description: None, - annotated: Constructor { - index: 0, - fields: vec![], - }, - }, - Annotated { - title: Some("True".to_string()), - description: None, - annotated: Constructor { - index: 1, - fields: vec![], - }, - }, - ])), + annotated: Schema::bool(), }), "Ordering" => Ok(Annotated { @@ -1099,7 +1117,7 @@ Here's the types I followed and that led me to this problem: pub mod tests { use super::*; use proptest::prelude::*; - use serde_json::{self, json, Value}; + use serde_json::{self, Value, json}; pub fn assert_json(schema: &impl Serialize, expected: Value) { assert_eq!(serde_json::to_value(schema).unwrap(), expected); diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_of_lists.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_of_lists.snap new file mode 100644 index 00000000..6f492bd9 --- /dev/null +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_of_lists.snap @@ -0,0 +1,65 @@ +--- +source: crates/aiken-project/src/blueprint/validator.rs +description: "Code:\n\npub type MyPair =\n Pair, Bool>\n\nvalidator placeholder {\n spend(_datum: Option, _redeemer: MyPair, _utxo: Data, _self: Data,) {\n True\n }\n}\n" +--- +{ + "title": "test_module.placeholder.spend", + "datum": { + "title": "_datum", + "schema": { + "$ref": "#/definitions/Data" + } + }, + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/MyPair" + } + }, + "compiledCode": "", + "hash": "", + "definitions": { + "Bool": { + "title": "Bool", + "anyOf": [ + { + "title": "False", + "dataType": "constructor", + "index": 0, + "fields": [] + }, + { + "title": "True", + "dataType": "constructor", + "index": 1, + "fields": [] + } + ] + }, + "Data": { + "title": "Data", + "description": "Any Plutus data." + }, + "Int": { + "dataType": "integer" + }, + "List$Int": { + "dataType": "list", + "items": { + "$ref": "#/definitions/Int" + } + }, + "MyPair": { + "title": "MyPair", + "dataType": "list", + "items": [ + { + "$ref": "#/definitions/List$Int" + }, + { + "$ref": "#/definitions/Bool" + } + ] + } + } +} diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap index 77ad17ab..c1f9ac28 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_after_map.snap @@ -33,13 +33,15 @@ description: "Code:\n\npub type MyPair =\n Pair\n\npub type MyDatum { }, "MyPair": { "title": "MyPair", - "dataType": "#pair", - "left": { - "$ref": "#/definitions/Int" - }, - "right": { - "$ref": "#/definitions/Int" - } + "dataType": "list", + "items": [ + { + "$ref": "#/definitions/Int" + }, + { + "$ref": "#/definitions/Int" + } + ] }, "Void": { "title": "Unit", diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap index 3bb64dbc..1ecdb0d7 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__pair_used_before_map.snap @@ -33,13 +33,15 @@ description: "Code:\n\npub type MyPair =\n Pair\n\npub type MyDatum { }, "MyPair": { "title": "MyPair", - "dataType": "#pair", - "left": { - "$ref": "#/definitions/Int" - }, - "right": { - "$ref": "#/definitions/Int" - } + "dataType": "list", + "items": [ + { + "$ref": "#/definitions/Int" + }, + { + "$ref": "#/definitions/Int" + } + ] }, "Void": { "title": "Unit", diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__type_aliases.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__type_aliases.snap index 16b93cc6..25351dd4 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__type_aliases.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__type_aliases.snap @@ -78,13 +78,15 @@ description: "Code:\n\npub type Asset = (ByteArray, Int)\n\npub type POSIXTime = }, "Pair$POSIXTime_Bool": { "title": "Pair", - "dataType": "#pair", - "left": { - "$ref": "#/definitions/POSIXTime" - }, - "right": { - "$ref": "#/definitions/Bool" - } + "dataType": "list", + "items": [ + { + "$ref": "#/definitions/POSIXTime" + }, + { + "$ref": "#/definitions/Bool" + } + ] }, "Pairs$Asset_POSIXTime": { "title": "Pairs", diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index b51665d1..36b476b8 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -202,13 +202,15 @@ impl Validator { schema: Declaration::Inline(Box::new(Schema::Data(Data::Opaque))), })); - definitions.prune_orphan_pairs( - parameters - .iter() - .chain(redeemer.as_ref().map(|x| vec![x]).unwrap_or_default()) - .chain(datum.as_ref().map(|x| vec![x]).unwrap_or_default()) - .collect::>(), - ); + definitions + .prune_orphan_pairs( + parameters + .iter() + .chain(redeemer.as_ref().map(|x| vec![x]).unwrap_or_default()) + .chain(datum.as_ref().map(|x| vec![x]).unwrap_or_default()) + .collect::>(), + ) + .replace_pairs_with_data_lists(); Ok(Validator { title: format!("{}.{}.{}", &module.name, &def.name, &func.name,), @@ -805,6 +807,22 @@ mod tests { ); } + #[test] + fn pair_of_lists() { + assert_validator!( + r#" + pub type MyPair = + Pair, Bool> + + validator placeholder { + spend(_datum: Option, _redeemer: MyPair, _utxo: Data, _self: Data,) { + True + } + } + "# + ); + } + #[test] fn else_redeemer() { assert_validator!( From 774a249cd6e8db89794512b27e5be9c2624f099b Mon Sep 17 00:00:00 2001 From: KtorZ <5680256+KtorZ@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:59:41 +0100 Subject: [PATCH 3/4] Fill-in CHANGELOG. Signed-off-by: KtorZ <5680256+KtorZ@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba4c268c..202d09c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - **aiken**: support for `bench` keyword to define benchmarks. @Riley-Kilgore - **aiken-lang**: The compiler now raises a warning when attempting to destructure a record constructor without using named fields. See [#1084](https://github.com/aiken-lang/aiken/issues/1084). @KtorZ +- **aiken-lang**: Fix blueprint schema definitions related to pairs (no longer omit (sometimes) Pairs definitions, and generate them as data List). See [#1086](https://github.com/aiken-lang/aiken/issues/1086) and [#970](https://github.com/aiken-lang/aiken/issues/970). @KtorZ ## v1.1.10 - 2025-01-21 From 55cc1b932ee3c4807fca848095205809381809fc Mon Sep 17 00:00:00 2001 From: KtorZ <5680256+KtorZ@users.noreply.github.com> Date: Sat, 8 Feb 2025 15:00:55 +0100 Subject: [PATCH 4/4] Run formatter across the codebase (rust 1.84.1) Signed-off-by: KtorZ <5680256+KtorZ@users.noreply.github.com> --- crates/aiken-lang/src/parser/expr/bytearray.rs | 8 ++++++-- crates/aiken-project/src/blueprint/definitions.rs | 4 ++-- crates/aiken-project/src/blueprint/parameter.rs | 2 +- crates/aiken-project/src/blueprint/schema.rs | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/aiken-lang/src/parser/expr/bytearray.rs b/crates/aiken-lang/src/parser/expr/bytearray.rs index 376f3eeb..d5f5ce29 100644 --- a/crates/aiken-lang/src/parser/expr/bytearray.rs +++ b/crates/aiken-lang/src/parser/expr/bytearray.rs @@ -66,11 +66,15 @@ mod tests { #[test] fn g1_element() { - assert_expr!("#\"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5\""); + assert_expr!( + "#\"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5\"" + ); } #[test] fn g2_element() { - assert_expr!("#\"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1\""); + assert_expr!( + "#\"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1\"" + ); } } diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index 97b0cffa..360a11dd 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -1,11 +1,11 @@ use crate::{ - Annotated, Schema, blueprint::{ parameter::Parameter, schema::{Data, Declaration, Items}, }, + Annotated, Schema, }; -use aiken_lang::tipo::{Type, TypeAliasAnnotation, TypeVar, pretty::resolve_alias}; +use aiken_lang::tipo::{pretty::resolve_alias, Type, TypeAliasAnnotation, TypeVar}; use itertools::Itertools; use serde::{ self, diff --git a/crates/aiken-project/src/blueprint/parameter.rs b/crates/aiken-project/src/blueprint/parameter.rs index 9618c7df..340eea96 100644 --- a/crates/aiken-project/src/blueprint/parameter.rs +++ b/crates/aiken-project/src/blueprint/parameter.rs @@ -5,8 +5,8 @@ use super::{ }; use std::{iter, ops::Deref}; use uplc::{ - PlutusData, ast::{Constant, Data as UplcData}, + PlutusData, }; #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index c5014e60..ea9f7471 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -1117,7 +1117,7 @@ Here's the types I followed and that led me to this problem: pub mod tests { use super::*; use proptest::prelude::*; - use serde_json::{self, Value, json}; + use serde_json::{self, json, Value}; pub fn assert_json(schema: &impl Serialize, expected: Value) { assert_eq!(serde_json::to_value(schema).unwrap(), expected);