Add support for generics in the blueprint generation.

This commit is contained in:
KtorZ 2023-01-28 18:47:56 +01:00
parent 0bd9d045b0
commit 9a44cba007
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
2 changed files with 130 additions and 18 deletions

View File

@ -71,6 +71,7 @@ impl Annotated<Schema> {
pub fn from_type( pub fn from_type(
modules: &HashMap<String, CheckedModule>, modules: &HashMap<String, CheckedModule>,
type_info: &Type, type_info: &Type,
type_parameters: &HashMap<u64, &Arc<Type>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
match type_info { match type_info {
Type::App { Type::App {
@ -91,9 +92,6 @@ impl Annotated<Schema> {
"String" => Ok(Schema::String.into()), "String" => Ok(Schema::String.into()),
// TODO: Check whether this matches with the UPLC code generation as there are two
// options here since there's technically speaking a `unit` constant constructor in
// the UPLC primitives.
"Void" => Ok(Annotated { "Void" => Ok(Annotated {
title: Some("Unit".to_string()), title: Some("Unit".to_string()),
description: Some("The nullary constructor.".to_string()), description: Some("The nullary constructor.".to_string()),
@ -107,7 +105,6 @@ impl Annotated<Schema> {
}]))), }]))),
}), }),
// TODO: Also check here whether this matches with the UPLC code generation.
"Bool" => Ok(Annotated { "Bool" => Ok(Annotated {
title: Some("Bool".to_string()), title: Some("Bool".to_string()),
description: None, description: None,
@ -132,7 +129,8 @@ impl Annotated<Schema> {
}), }),
"Option" => { "Option" => {
let generic = Annotated::from_type(modules, args.get(0).unwrap()) let generic =
Annotated::from_type(modules, args.get(0).unwrap(), &HashMap::new())
.and_then(|s| s.into_data(type_info))?; .and_then(|s| s.into_data(type_info))?;
Ok(Annotated { Ok(Annotated {
title: Some("Optional".to_string()), title: Some("Optional".to_string()),
@ -159,7 +157,8 @@ impl Annotated<Schema> {
} }
"List" => { "List" => {
let generic = Annotated::from_type(modules, args.get(0).unwrap()) let generic =
Annotated::from_type(modules, args.get(0).unwrap(), &HashMap::new())
.and_then(|s| s.into_data(type_info))?; .and_then(|s| s.into_data(type_info))?;
Ok(Schema::Data(Some(Data::List(Box::new(generic.annotated)))).into()) Ok(Schema::Data(Some(Data::List(Box::new(generic.annotated)))).into())
} }
@ -171,11 +170,17 @@ impl Annotated<Schema> {
Type::App { Type::App {
module: module_name, module: module_name,
name: type_name, name: type_name,
args,
.. ..
} => { } => {
let module = modules.get(module_name).unwrap(); let module = modules.get(module_name).unwrap();
let constructor = find_definition(type_name, &module.ast.definitions).unwrap(); let constructor = find_definition(type_name, &module.ast.definitions).unwrap();
let annotated = Schema::Data(Some(Data::from_data_type(modules, constructor)?)); let type_parameters = collect_type_parameters(&constructor.typed_parameters, args);
let annotated = Schema::Data(Some(Data::from_data_type(
modules,
constructor,
&type_parameters,
)?));
Ok(Annotated { Ok(Annotated {
title: Some(constructor.name.clone()), title: Some(constructor.name.clone()),
@ -184,23 +189,28 @@ impl Annotated<Schema> {
}) })
} }
Type::Var { tipo } => match tipo.borrow().deref() { Type::Var { tipo } => match tipo.borrow().deref() {
TypeVar::Link { tipo } => Annotated::from_type(modules, tipo), TypeVar::Link { tipo } => Annotated::from_type(modules, tipo, type_parameters),
TypeVar::Generic { .. } => todo!(), TypeVar::Generic { id } => {
let tipo = type_parameters.get(id).ok_or(Error::FreeParameter)?;
Annotated::from_type(modules, tipo, &HashMap::new())
}
TypeVar::Unbound { .. } => Err(Error::UnsupportedType { TypeVar::Unbound { .. } => Err(Error::UnsupportedType {
type_info: type_info.clone(), type_info: type_info.clone(),
}), }),
}, },
Type::Tuple { elems } => match &elems[..] { Type::Tuple { elems } => match &elems[..] {
[left, right] => { [left, right] => {
let left = Annotated::from_type(modules, left)?.into_data(left)?; let left =
let right = Annotated::from_type(modules, right)?.into_data(right)?; Annotated::from_type(modules, left, &HashMap::new())?.into_data(left)?;
let right =
Annotated::from_type(modules, right, &HashMap::new())?.into_data(right)?;
Ok(Schema::Pair(left.annotated, right.annotated).into()) Ok(Schema::Pair(left.annotated, right.annotated).into())
} }
_ => { _ => {
let elems: Result<Vec<Data>, _> = elems let elems: Result<Vec<Data>, _> = elems
.iter() .iter()
.map(|e| { .map(|e| {
Annotated::from_type(modules, e) Annotated::from_type(modules, e, &HashMap::new())
.and_then(|s| s.into_data(e).map(|s| s.annotated)) .and_then(|s| s.into_data(e).map(|s| s.annotated))
}) })
.collect(); .collect();
@ -239,12 +249,14 @@ impl Data {
pub fn from_data_type( pub fn from_data_type(
modules: &HashMap<String, CheckedModule>, modules: &HashMap<String, CheckedModule>,
data_type: &DataType<Arc<Type>>, data_type: &DataType<Arc<Type>>,
type_parameters: &HashMap<u64, &Arc<Type>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut variants = vec![]; let mut variants = vec![];
for (index, constructor) in data_type.constructors.iter().enumerate() { for (index, constructor) in data_type.constructors.iter().enumerate() {
let mut fields = vec![]; let mut fields = vec![];
for field in constructor.arguments.iter() { for field in constructor.arguments.iter() {
let mut schema = Annotated::from_type(modules, &field.tipo) let mut schema = Annotated::from_type(modules, &field.tipo, type_parameters)
.and_then(|t| t.into_data(&field.tipo))?; .and_then(|t| t.into_data(&field.tipo))?;
if field.label.is_some() { if field.label.is_some() {
@ -389,6 +401,30 @@ pub enum Error {
UnsupportedType { type_info: Type }, UnsupportedType { type_info: Type },
#[error("I had the misfortune to find an invalid type in an interface boundary.")] #[error("I had the misfortune to find an invalid type in an interface boundary.")]
ExpectedData { got: Type }, ExpectedData { got: Type },
#[error("I caught a free type-parameter in an interface boundary.")]
FreeParameter,
}
fn collect_type_parameters<'a>(
generics: &'a [Arc<Type>],
applications: &'a [Arc<Type>],
) -> HashMap<u64, &'a Arc<Type>> {
let mut type_parameters = HashMap::new();
for (index, generic) in generics.iter().enumerate() {
match &**generic {
Type::Var { tipo } => match *tipo.borrow() {
TypeVar::Generic { id } => {
type_parameters.insert(id, applications.get(index).unwrap());
}
_ => unreachable!(),
},
_ => unreachable!(),
}
}
type_parameters
} }
fn find_definition<'a>( fn find_definition<'a>(

View File

@ -10,7 +10,10 @@ use serde::{
self, self,
ser::{Serialize, SerializeStruct, Serializer}, ser::{Serialize, SerializeStruct, Serializer},
}; };
use std::fmt::{self, Display}; use std::{
collections::HashMap,
fmt::{self, Display},
};
use uplc::ast::{NamedDeBruijn, Program}; use uplc::ast::{NamedDeBruijn, Program};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -88,10 +91,11 @@ impl Validator {
purpose, purpose,
datum: datum datum: datum
.map(|datum| { .map(|datum| {
Annotated::from_type(modules.into(), &datum.tipo).map_err(Error::Schema) Annotated::from_type(modules.into(), &datum.tipo, &HashMap::new())
.map_err(Error::Schema)
}) })
.transpose()?, .transpose()?,
redeemer: Annotated::from_type(modules.into(), &redeemer.tipo) redeemer: Annotated::from_type(modules.into(), &redeemer.tipo, &HashMap::new())
.map_err(Error::Schema)?, .map_err(Error::Schema)?,
program: generator program: generator
.generate(&def.body, &def.arguments, true) .generate(&def.body, &def.arguments, true)
@ -453,4 +457,76 @@ mod test {
}), }),
) )
} }
#[test]
fn validator_generics() {
assert_validator(
r#"
type Either<left, right> {
Left(left)
Right(right)
}
type Interval<a> {
Finite(a)
Infinite
}
fn withdraw(redeemer: Either<ByteArray, Interval<Int>>, ctx: Void) {
True
}
"#,
json!(
{
"title": "test_module",
"purpose": "withdraw",
"hash": "da4a98cee05a17be402b07c414d59bf894c9ebd0487186417121de8f",
"redeemer": {
"title": "Either",
"anyOf": [
{
"title": "Left",
"dataType": "constructor",
"index": 0,
"fields": [
{
"dataType": "bytes"
}
]
},
{
"title": "Right",
"dataType": "constructor",
"index": 1,
"fields": [
{
"title": "Interval",
"anyOf": [
{
"title": "Finite",
"dataType": "constructor",
"index": 0,
"fields": [
{
"dataType": "integer"
}
]
},
{
"title": "Infinite",
"dataType": "constructor",
"index": 1,
"fields": []
}
]
}
]
}
]
},
"compiledCode": "581d010000210872656465656d657200210363747800533357349445261601"
}
),
)
}
} }