Preserve type-aliases during blueprint generation.

This commit is contained in:
KtorZ 2024-09-03 16:39:50 +02:00
parent 007b85b864
commit f60df16bc2
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
7 changed files with 286 additions and 18 deletions

View File

@ -26,6 +26,8 @@
- **aiken-project**: modules starting with `@hidden` in their docs will be skipped from docs generation. @KtorZ - **aiken-project**: modules starting with `@hidden` in their docs will be skipped from docs generation. @KtorZ
- **aiken-project**: preserve type-aliases as titles in blueprint generated schemas. @KtorZ
- **uplc**: support evaluation of Plutus V3 transactions, including new purposes introduced in Conway. @KtorZ - **uplc**: support evaluation of Plutus V3 transactions, including new purposes introduced in Conway. @KtorZ
### Changed ### Changed

View File

@ -214,7 +214,7 @@ fn qualify_type_name(module: &String, typ_name: &str) -> Document<'static> {
} }
} }
fn resolve_alias( pub fn resolve_alias(
parameters: &[String], parameters: &[String],
annotation: &Annotation, annotation: &Annotation,
typ: &Type, typ: &Type,

View File

@ -1,4 +1,5 @@
use aiken_lang::tipo::{Type, TypeVar}; use aiken_lang::tipo::{pretty::resolve_alias, Type, TypeAliasAnnotation, TypeVar};
use itertools::Itertools;
use serde::{ use serde::{
self, self,
de::{self, Deserialize, Deserializer, MapAccess, Visitor}, de::{self, Deserialize, Deserializer, MapAccess, Visitor},
@ -125,6 +126,22 @@ impl Display for Reference {
impl Reference { impl Reference {
pub fn from_type(type_info: &Type, type_parameters: &HashMap<u64, Rc<Type>>) -> Self { pub fn from_type(type_info: &Type, type_parameters: &HashMap<u64, Rc<Type>>) -> Self {
if let Some(TypeAliasAnnotation {
alias,
parameters,
annotation,
}) = type_info.alias().as_deref()
{
if let Some(resolved_parameters) = resolve_alias(parameters, annotation, type_info) {
return Self::from_type_alias(
type_info,
alias.to_string(),
resolved_parameters,
type_parameters,
);
}
}
match type_info { match type_info {
Type::App { Type::App {
module, name, args, .. module, name, args, ..
@ -190,6 +207,41 @@ impl Reference {
} }
} }
} }
fn from_type_alias(
type_info: &Type,
alias: String,
parameters: Vec<Rc<Type>>,
type_parameters: &HashMap<u64, Rc<Type>>,
) -> Self {
if !parameters.is_empty() {
Reference {
inner: format!(
"{alias}${}",
parameters
.iter()
.map(|param| {
// Avoid infinite recursion for recursive types instantiated to
// themselves. For example: type Identity<t> = t
if param.as_ref() == type_info {
Self::from_type(
type_info.clone().set_alias(None).as_ref(),
type_parameters,
)
.inner
} else {
Self::from_type(param, type_parameters).inner
}
})
.join("_"),
),
}
} else {
Reference {
inner: alias.clone(),
}
}
}
} }
impl Serialize for Reference { impl Serialize for Reference {

View File

@ -172,6 +172,24 @@ impl Annotated<Schema> {
type_parameters: &mut HashMap<u64, Rc<Type>>, type_parameters: &mut HashMap<u64, Rc<Type>>,
definitions: &mut Definitions<Self>, definitions: &mut Definitions<Self>,
) -> Result<Reference, Error> { ) -> Result<Reference, Error> {
let title = if type_info.alias().is_some() {
Some(type_info.to_pretty(0))
} else {
None
};
fn with_title(title: Option<&String>, annotated: Schema) -> Annotated<Schema> {
if title.is_some() {
Annotated {
title: title.cloned(),
description: None,
annotated,
}
} else {
annotated.into()
}
}
match type_info { match type_info {
Type::App { Type::App {
module: module_name, module: module_name,
@ -182,20 +200,20 @@ impl Annotated<Schema> {
definitions.register(type_info, &type_parameters.clone(), |definitions| { definitions.register(type_info, &type_parameters.clone(), |definitions| {
match &type_name[..] { match &type_name[..] {
"Data" => Ok(Annotated { "Data" => Ok(Annotated {
title: Some("Data".to_string()), title: title.or(Some("Data".to_string())),
description: Some("Any Plutus data.".to_string()), description: Some("Any Plutus data.".to_string()),
annotated: Schema::Data(Data::Opaque), annotated: Schema::Data(Data::Opaque),
}), }),
"ByteArray" => Ok(Schema::Data(Data::Bytes).into()), "ByteArray" => Ok(with_title(title.as_ref(), Schema::Data(Data::Bytes))),
"Int" => Ok(Schema::Data(Data::Integer).into()), "Int" => Ok(with_title(title.as_ref(), Schema::Data(Data::Integer))),
"String" => Ok(Schema::String.into()), "String" => Ok(with_title(title.as_ref(), Schema::String)),
"Void" => Ok(Annotated { "Void" => Ok(Annotated {
title: Some("Unit".to_string()), title: title.or(Some("Unit".to_string())),
description: Some("The nullary constructor.".to_string()), description: None,
annotated: Schema::Data(Data::AnyOf(vec![Annotated { annotated: Schema::Data(Data::AnyOf(vec![Annotated {
title: None, title: None,
description: None, description: None,
@ -207,7 +225,7 @@ impl Annotated<Schema> {
}), }),
"Bool" => Ok(Annotated { "Bool" => Ok(Annotated {
title: Some("Bool".to_string()), title: title.or(Some("Bool".to_string())),
description: None, description: None,
annotated: Schema::Data(Data::AnyOf(vec![ annotated: Schema::Data(Data::AnyOf(vec![
Annotated { Annotated {
@ -230,7 +248,7 @@ impl Annotated<Schema> {
}), }),
"Ordering" => Ok(Annotated { "Ordering" => Ok(Annotated {
title: Some("Ordering".to_string()), title: title.or(Some("Ordering".to_string())),
description: None, description: None,
annotated: Schema::Data(Data::AnyOf(vec![ annotated: Schema::Data(Data::AnyOf(vec![
Annotated { Annotated {
@ -260,6 +278,23 @@ impl Annotated<Schema> {
])), ])),
}), }),
"Never" => {
Ok(Annotated {
title: title.or(Some("Never".to_string())),
description: None,
annotated: Schema::Data(Data::AnyOf(vec![
Annotated {
title: Some("Never".to_string()),
description: Some("Nothing.".to_string()),
annotated: Constructor {
index: 1,
fields: vec![],
},
},
])),
})
}
"Option" => { "Option" => {
let generic = Annotated::do_from_type( let generic = Annotated::do_from_type(
args.first() args.first()
@ -270,7 +305,7 @@ impl Annotated<Schema> {
)?; )?;
Ok(Annotated { Ok(Annotated {
title: Some("Optional".to_string()), title: title.or(Some("Option".to_string())),
description: None, description: None,
annotated: Schema::Data(Data::AnyOf(vec![ annotated: Schema::Data(Data::AnyOf(vec![
Annotated { Annotated {
@ -329,7 +364,7 @@ impl Annotated<Schema> {
_ => Data::List(Items::One(Declaration::Referenced(generic))), _ => Data::List(Items::One(Declaration::Referenced(generic))),
}; };
Ok(Schema::Data(data).into()) Ok(with_title(title.as_ref(), Schema::Data(data)))
} }
_ => Err(Error::new(ErrorContext::UnsupportedType, type_info)), _ => Err(Error::new(ErrorContext::UnsupportedType, type_info)),
@ -360,7 +395,7 @@ impl Annotated<Schema> {
); );
Ok(Annotated { Ok(Annotated {
title: Some(data_type.name.clone()), title: title.or(Some(data_type.name.clone())),
description: data_type.doc.clone().map(|s| s.trim().to_string()), description: data_type.doc.clone().map(|s| s.trim().to_string()),
annotated, annotated,
}) })
@ -377,7 +412,7 @@ impl Annotated<Schema> {
.map_err(|e| e.backtrack(type_info))?; .map_err(|e| e.backtrack(type_info))?;
Ok(Annotated { Ok(Annotated {
title: Some("Pair".to_owned()), title: title.or(Some("Pair".to_owned())),
description: None, description: None,
annotated: Schema::Pair(left, right), annotated: Schema::Pair(left, right),
}) })
@ -396,7 +431,7 @@ impl Annotated<Schema> {
.map_err(|e| e.backtrack(type_info))?; .map_err(|e| e.backtrack(type_info))?;
Ok(Annotated { Ok(Annotated {
title: Some("Tuple".to_owned()), title: title.or(Some("Tuple".to_owned())),
description: None, description: None,
annotated: Schema::Data(Data::List(Items::Many(elems))), annotated: Schema::Data(Data::List(Items::Many(elems))),
}) })

View File

@ -20,17 +20,22 @@ description: "Code:\n\n/// On-chain state\npub type State {\n /// The contest
"hash": "<redacted>", "hash": "<redacted>",
"definitions": { "definitions": {
"ByteArray": { "ByteArray": {
"title": "ByteArray",
"dataType": "bytes" "dataType": "bytes"
}, },
"Int": { "Int": {
"dataType": "integer" "dataType": "integer"
}, },
"List$ByteArray": { "List$Party": {
"dataType": "list", "dataType": "list",
"items": { "items": {
"$ref": "#/definitions/ByteArray" "$ref": "#/definitions/Party"
} }
}, },
"Party": {
"title": "Party",
"dataType": "bytes"
},
"test_module/ContestationPeriod": { "test_module/ContestationPeriod": {
"title": "ContestationPeriod", "title": "ContestationPeriod",
"description": "Whatever", "description": "Whatever",
@ -89,7 +94,7 @@ description: "Code:\n\n/// On-chain state\npub type State {\n /// The contest
{ {
"title": "parties", "title": "parties",
"description": "List of public key hashes of all participants", "description": "List of public key hashes of all participants",
"$ref": "#/definitions/List$ByteArray" "$ref": "#/definitions/List$Party"
}, },
{ {
"title": "utxoHash", "title": "utxoHash",

View File

@ -0,0 +1,145 @@
---
source: crates/aiken-project/src/blueprint/validator.rs
description: "Code:\n\npub type Asset = (ByteArray, Int)\n\npub type POSIXTime = Int\n\npub type AlwaysNone = Never\n\npub type MyDatum {\n Either(AlwaysNone)\n OrElse(Pair<POSIXTime, Bool>)\n}\n\npub type MyRedeemer<a> {\n fst_field: List<a>,\n snd_field: Pairs<a, POSIXTime>\n}\n\nvalidator recursive_types {\n spend(datum: Option<MyDatum>, redeemer: MyRedeemer<Asset>, output_reference: Data, transaction: Data) {\n True\n }\n}\n"
---
{
"title": "test_module.recursive_types.spend",
"datum": {
"title": "datum",
"schema": {
"$ref": "#/definitions/test_module~1MyDatum"
}
},
"redeemer": {
"title": "redeemer",
"schema": {
"$ref": "#/definitions/test_module~1MyRedeemer$Asset"
}
},
"compiledCode": "<redacted>",
"hash": "<redacted>",
"definitions": {
"AlwaysNone": {
"title": "AlwaysNone",
"anyOf": [
{
"title": "Never",
"description": "Nothing.",
"dataType": "constructor",
"index": 1,
"fields": []
}
]
},
"Asset": {
"title": "Asset",
"dataType": "list",
"items": [
{
"$ref": "#/definitions/ByteArray"
},
{
"$ref": "#/definitions/Int"
}
]
},
"Bool": {
"title": "Bool",
"anyOf": [
{
"title": "False",
"dataType": "constructor",
"index": 0,
"fields": []
},
{
"title": "True",
"dataType": "constructor",
"index": 1,
"fields": []
}
]
},
"ByteArray": {
"dataType": "bytes"
},
"Int": {
"dataType": "integer"
},
"List$Asset": {
"dataType": "list",
"items": {
"$ref": "#/definitions/Asset"
}
},
"POSIXTime": {
"title": "POSIXTime",
"dataType": "integer"
},
"Pair$POSIXTime_Bool": {
"title": "Pair",
"dataType": "#pair",
"left": {
"$ref": "#/definitions/POSIXTime"
},
"right": {
"$ref": "#/definitions/Bool"
}
},
"Pairs$Asset_POSIXTime": {
"title": "Pairs<a, POSIXTime>",
"dataType": "map",
"keys": {
"$ref": "#/definitions/Asset"
},
"values": {
"$ref": "#/definitions/POSIXTime"
}
},
"test_module/MyDatum": {
"title": "MyDatum",
"anyOf": [
{
"title": "Either",
"dataType": "constructor",
"index": 0,
"fields": [
{
"$ref": "#/definitions/AlwaysNone"
}
]
},
{
"title": "OrElse",
"dataType": "constructor",
"index": 1,
"fields": [
{
"$ref": "#/definitions/Pair$POSIXTime_Bool"
}
]
}
]
},
"test_module/MyRedeemer$Asset": {
"title": "MyRedeemer",
"anyOf": [
{
"title": "MyRedeemer",
"dataType": "constructor",
"index": 0,
"fields": [
{
"title": "fst_field",
"$ref": "#/definitions/List$Asset"
},
{
"title": "snd_field",
"$ref": "#/definitions/Pairs$Asset_POSIXTime"
}
]
}
]
}
}
}

View File

@ -695,6 +695,35 @@ mod tests {
); );
} }
#[test]
fn type_aliases() {
assert_validator!(
r#"
pub type Asset = (ByteArray, Int)
pub type POSIXTime = Int
pub type AlwaysNone = Never
pub type MyDatum {
Either(AlwaysNone)
OrElse(Pair<POSIXTime, Bool>)
}
pub type MyRedeemer<a> {
fst_field: List<a>,
snd_field: Pairs<a, POSIXTime>
}
validator recursive_types {
spend(datum: Option<MyDatum>, redeemer: MyRedeemer<Asset>, output_reference: Data, transaction: Data) {
True
}
}
"#
);
}
#[test] #[test]
fn validate_arguments_integer() { fn validate_arguments_integer() {
let definitions = fixture_definitions(); let definitions = fixture_definitions();