From 9a44cba007bfd942d4a2d84bcca7fe04088745ef Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 28 Jan 2023 18:47:56 +0100 Subject: [PATCH] Add support for generics in the blueprint generation. --- crates/aiken-project/src/blueprint/schema.rs | 66 +++++++++++---- .../aiken-project/src/blueprint/validator.rs | 82 ++++++++++++++++++- 2 files changed, 130 insertions(+), 18 deletions(-) diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 5e23c808..bc6172c1 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -71,6 +71,7 @@ impl Annotated { pub fn from_type( modules: &HashMap, type_info: &Type, + type_parameters: &HashMap>, ) -> Result { match type_info { Type::App { @@ -91,9 +92,6 @@ impl Annotated { "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 { title: Some("Unit".to_string()), description: Some("The nullary constructor.".to_string()), @@ -107,7 +105,6 @@ impl Annotated { }]))), }), - // TODO: Also check here whether this matches with the UPLC code generation. "Bool" => Ok(Annotated { title: Some("Bool".to_string()), description: None, @@ -132,8 +129,9 @@ impl Annotated { }), "Option" => { - let generic = Annotated::from_type(modules, args.get(0).unwrap()) - .and_then(|s| s.into_data(type_info))?; + let generic = + Annotated::from_type(modules, args.get(0).unwrap(), &HashMap::new()) + .and_then(|s| s.into_data(type_info))?; Ok(Annotated { title: Some("Optional".to_string()), description: None, @@ -159,8 +157,9 @@ impl Annotated { } "List" => { - let generic = Annotated::from_type(modules, args.get(0).unwrap()) - .and_then(|s| s.into_data(type_info))?; + let generic = + Annotated::from_type(modules, args.get(0).unwrap(), &HashMap::new()) + .and_then(|s| s.into_data(type_info))?; Ok(Schema::Data(Some(Data::List(Box::new(generic.annotated)))).into()) } @@ -171,11 +170,17 @@ impl Annotated { Type::App { module: module_name, name: type_name, + args, .. } => { let module = modules.get(module_name).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 { title: Some(constructor.name.clone()), @@ -184,23 +189,28 @@ impl Annotated { }) } Type::Var { tipo } => match tipo.borrow().deref() { - TypeVar::Link { tipo } => Annotated::from_type(modules, tipo), - TypeVar::Generic { .. } => todo!(), + TypeVar::Link { tipo } => Annotated::from_type(modules, tipo, type_parameters), + 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 { type_info: type_info.clone(), }), }, Type::Tuple { elems } => match &elems[..] { [left, right] => { - let left = Annotated::from_type(modules, left)?.into_data(left)?; - let right = Annotated::from_type(modules, right)?.into_data(right)?; + let left = + 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()) } _ => { let elems: Result, _> = elems .iter() .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)) }) .collect(); @@ -239,12 +249,14 @@ impl Data { pub fn from_data_type( modules: &HashMap, data_type: &DataType>, + type_parameters: &HashMap>, ) -> Result { let mut variants = vec![]; + for (index, constructor) in data_type.constructors.iter().enumerate() { let mut fields = vec![]; 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))?; if field.label.is_some() { @@ -389,6 +401,30 @@ pub enum Error { UnsupportedType { type_info: Type }, #[error("I had the misfortune to find an invalid type in an interface boundary.")] ExpectedData { got: Type }, + + #[error("I caught a free type-parameter in an interface boundary.")] + FreeParameter, +} + +fn collect_type_parameters<'a>( + generics: &'a [Arc], + applications: &'a [Arc], +) -> HashMap> { + 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>( diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 82af2aad..e4722ca4 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -10,7 +10,10 @@ use serde::{ self, ser::{Serialize, SerializeStruct, Serializer}, }; -use std::fmt::{self, Display}; +use std::{ + collections::HashMap, + fmt::{self, Display}, +}; use uplc::ast::{NamedDeBruijn, Program}; #[derive(Debug, PartialEq, Clone)] @@ -88,10 +91,11 @@ impl Validator { purpose, datum: 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()?, - redeemer: Annotated::from_type(modules.into(), &redeemer.tipo) + redeemer: Annotated::from_type(modules.into(), &redeemer.tipo, &HashMap::new()) .map_err(Error::Schema)?, program: generator .generate(&def.body, &def.arguments, true) @@ -453,4 +457,76 @@ mod test { }), ) } + + #[test] + fn validator_generics() { + assert_validator( + r#" + type Either { + Left(left) + Right(right) + } + + type Interval { + Finite(a) + Infinite + } + + fn withdraw(redeemer: Either>, 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" + } + ), + ) + } }