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.
This commit is contained in:
parent
3e57109c35
commit
d3885ac67a
|
@ -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<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>) {
|
||||
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}")),
|
||||
¶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.
|
||||
|
|
|
@ -346,8 +346,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:#?}"),
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
source: crates/aiken-project/src/blueprint/validator.rs
|
||||
description: "Code:\n\npub type OuterMap =\n List<Pair<Int, InnerMap>>\n\npub type InnerMap =\n List<Pair<Int, Bool>>\n\nvalidator placeholder {\n spend(_datum: Option<Void>, _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": "<redacted>",
|
||||
"hash": "<redacted>",
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
source: crates/aiken-project/src/blueprint/validator.rs
|
||||
description: "Code:\n\npub type MyPair =\n Pair<Int, InnerPair>\n\npub type InnerPair =\n Pair<Int, Bool>\n\nvalidator placeholder {\n spend(_datum: Option<Void>, _redeemer: List<MyPair>, _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": "<redacted>",
|
||||
"hash": "<redacted>",
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
source: crates/aiken-project/src/blueprint/validator.rs
|
||||
description: "Code:\n\npub type MyPair =\n Pair<Int, Int>\n\npub type MyDatum {\n pairs: List<MyPair>,\n pair: MyPair,\n}\n\nvalidator placeholder {\n spend(_datum: Option<MyDatum>, _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": "<redacted>",
|
||||
"hash": "<redacted>",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
source: crates/aiken-project/src/blueprint/validator.rs
|
||||
description: "Code:\n\npub type MyPair =\n Pair<Int, Int>\n\npub type MyDatum {\n pair: MyPair,\n pairs: List<MyPair>,\n}\n\nvalidator placeholder {\n spend(_datum: Option<MyDatum>, _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": "<redacted>",
|
||||
"hash": "<redacted>",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -122,7 +122,7 @@ impl Validator {
|
|||
),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
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::<Vec<&Parameter>>(),
|
||||
);
|
||||
|
||||
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<Int, Int>
|
||||
|
||||
pub type MyDatum {
|
||||
pairs: List<MyPair>,
|
||||
pair: MyPair,
|
||||
}
|
||||
|
||||
validator placeholder {
|
||||
spend(_datum: Option<MyDatum>, _redeemer: Void, _utxo: Data, _self: Data,) {
|
||||
True
|
||||
}
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pair_used_before_map() {
|
||||
assert_validator!(
|
||||
r#"
|
||||
pub type MyPair =
|
||||
Pair<Int, Int>
|
||||
|
||||
pub type MyDatum {
|
||||
pair: MyPair,
|
||||
pairs: List<MyPair>,
|
||||
}
|
||||
|
||||
validator placeholder {
|
||||
spend(_datum: Option<MyDatum>, _redeemer: Void, _utxo: Data, _self: Data,) {
|
||||
True
|
||||
}
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_in_map() {
|
||||
assert_validator!(
|
||||
r#"
|
||||
pub type OuterMap =
|
||||
List<Pair<Int, InnerMap>>
|
||||
|
||||
pub type InnerMap =
|
||||
List<Pair<Int, Bool>>
|
||||
|
||||
validator placeholder {
|
||||
spend(_datum: Option<Void>, _redeemer: OuterMap, _utxo: Data, _self: Data,) {
|
||||
True
|
||||
}
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn else_redeemer() {
|
||||
assert_validator!(
|
||||
|
|
Loading…
Reference in New Issue