Add support for generics in the blueprint generation.
This commit is contained in:
parent
0bd9d045b0
commit
9a44cba007
|
@ -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>(
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue