Skip to content

Commit

Permalink
Merge branch 'fix/blueprint-pairs'
Browse files Browse the repository at this point in the history
Signed-off-by: KtorZ <5680256+KtorZ@users.noreply.github.com>
  • Loading branch information
KtorZ committed Feb 8, 2025
2 parents 3e57109 + 55cc1b9 commit f85d946
Show file tree
Hide file tree
Showing 11 changed files with 734 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions crates/aiken-lang/src/parser/expr/bytearray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,15 @@ mod tests {

#[test]
fn g1_element() {
assert_expr!("#<Bls12_381, G1>\"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5\"");
assert_expr!(
"#<Bls12_381, G1>\"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5\""
);
}

#[test]
fn g2_element() {
assert_expr!("#<Bls12_381, G2>\"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1\"");
assert_expr!(
"#<Bls12_381, G2>\"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1\""
);
}
}
221 changes: 220 additions & 1 deletion crates/aiken-project/src/blueprint/definitions.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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,
Expand Down Expand Up @@ -88,6 +95,218 @@ impl<T> Definitions<T> {
}
}

impl Definitions<Annotated<Schema>> {
/// 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>) -> &mut Self {
fn traverse_schema(
src: Reference,
schema: &Schema,
usage: &mut BTreeMap<Reference, BTreeSet<Reference>>,
) {
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<Reference, BTreeSet<Reference>>,
) {
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<F, T>(
src: Reference,
declaration: &Declaration<T>,
usage: &mut BTreeMap<Reference, BTreeSet<Reference>>,
mut traverse: F,
) where
F: FnMut(Reference, &T, &mut BTreeMap<Reference, BTreeSet<Reference>>),
{
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<Reference, BTreeSet<Reference>> = 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}")),
&param.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();
}
}

self
}

pub fn replace_pairs_with_data_lists(&mut self) {
fn swap_declaration(declaration: &mut Declaration<Schema>) -> Declaration<Data> {
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);
}
}
}
}

// ---------- Reference

/// A URI pointer to an underlying data-type.
Expand Down
76 changes: 46 additions & 30 deletions crates/aiken-project/src/blueprint/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -205,46 +247,22 @@ impl Annotated<Schema> {
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 {
Expand Down Expand Up @@ -346,8 +364,6 @@ impl Annotated<Schema> {
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:#?}"),
Expand Down
Loading

0 comments on commit f85d946

Please sign in to comment.