Allow annotating Data for blueprint
This commit allows Data to be optionally annotated with a phantom-type. This doesn't change anything in codegen but we can now leverage this information to generate better blueprint schemas.
This commit is contained in:
		
							parent
							
								
									20ce19dfb1
								
							
						
					
					
						commit
						3c8460e6af
					
				|  | @ -684,7 +684,7 @@ impl UnqualifiedImport { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TypeAst
 | // TypeAst
 | ||||||
| #[derive(Debug, Clone, PartialEq)] | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||||||
| pub enum Annotation { | pub enum Annotation { | ||||||
|     Constructor { |     Constructor { | ||||||
|         location: Span, |         location: Span, | ||||||
|  | @ -1411,7 +1411,7 @@ impl Display for TraceLevel { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Copy, Clone, PartialEq, Eq)] | #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||||||
| pub struct Span { | pub struct Span { | ||||||
|     pub start: usize, |     pub start: usize, | ||||||
|     pub end: usize, |     pub end: usize, | ||||||
|  |  | ||||||
|  | @ -38,6 +38,7 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { | ||||||
|         types_constructors: HashMap::new(), |         types_constructors: HashMap::new(), | ||||||
|         values: HashMap::new(), |         values: HashMap::new(), | ||||||
|         accessors: HashMap::new(), |         accessors: HashMap::new(), | ||||||
|  |         annotations: HashMap::new(), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // Int
 |     // Int
 | ||||||
|  | @ -423,6 +424,7 @@ pub fn plutus(id_gen: &IdGenerator) -> TypeInfo { | ||||||
|         types_constructors: HashMap::new(), |         types_constructors: HashMap::new(), | ||||||
|         values: HashMap::new(), |         values: HashMap::new(), | ||||||
|         accessors: HashMap::new(), |         accessors: HashMap::new(), | ||||||
|  |         annotations: HashMap::new(), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     for builtin in DefaultFunction::iter() { |     for builtin in DefaultFunction::iter() { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| use self::{environment::Environment, pretty::Printer}; | use self::{environment::Environment, pretty::Printer}; | ||||||
| use crate::{ | use crate::{ | ||||||
|     ast::{Constant, DefinitionLocation, ModuleKind, Span}, |     ast::{Annotation, Constant, DefinitionLocation, ModuleKind, Span}, | ||||||
|     builtins::{G1_ELEMENT, G2_ELEMENT, MILLER_LOOP_RESULT}, |     builtins::{G1_ELEMENT, G2_ELEMENT, MILLER_LOOP_RESULT}, | ||||||
|     tipo::fields::FieldMap, |     tipo::fields::FieldMap, | ||||||
| }; | }; | ||||||
|  | @ -755,6 +755,7 @@ pub struct TypeInfo { | ||||||
|     pub types_constructors: HashMap<String, Vec<String>>, |     pub types_constructors: HashMap<String, Vec<String>>, | ||||||
|     pub values: HashMap<String, ValueConstructor>, |     pub values: HashMap<String, ValueConstructor>, | ||||||
|     pub accessors: HashMap<String, AccessorsMap>, |     pub accessors: HashMap<String, AccessorsMap>, | ||||||
|  |     pub annotations: HashMap<Annotation, Rc<Type>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
|  |  | ||||||
|  | @ -72,6 +72,9 @@ pub struct Environment<'a> { | ||||||
| 
 | 
 | ||||||
|     pub unused_modules: HashMap<String, Span>, |     pub unused_modules: HashMap<String, Span>, | ||||||
| 
 | 
 | ||||||
|  |     /// A mapping from known annotations to their resolved type.
 | ||||||
|  |     pub annotations: HashMap<Annotation, Rc<Type>>, | ||||||
|  | 
 | ||||||
|     /// Warnings
 |     /// Warnings
 | ||||||
|     pub warnings: &'a mut Vec<Warning>, |     pub warnings: &'a mut Vec<Warning>, | ||||||
| } | } | ||||||
|  | @ -88,6 +91,12 @@ impl<'a> Environment<'a> { | ||||||
|         self.scope = data.local_values; |         self.scope = data.local_values; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn annotate(&mut self, return_type: Rc<Type>, annotation: &Annotation) -> Rc<Type> { | ||||||
|  |         self.annotations | ||||||
|  |             .insert(annotation.clone(), return_type.clone()); | ||||||
|  |         return_type | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Converts entities with a usage count of 0 to warnings
 |     /// Converts entities with a usage count of 0 to warnings
 | ||||||
|     pub fn convert_unused_to_warnings(&mut self) { |     pub fn convert_unused_to_warnings(&mut self) { | ||||||
|         let unused = self |         let unused = self | ||||||
|  | @ -657,6 +666,7 @@ impl<'a> Environment<'a> { | ||||||
|             imported_types: HashSet::new(), |             imported_types: HashSet::new(), | ||||||
|             current_module, |             current_module, | ||||||
|             current_kind, |             current_kind, | ||||||
|  |             annotations: HashMap::new(), | ||||||
|             warnings, |             warnings, | ||||||
|             entity_usages: vec![HashMap::new()], |             entity_usages: vec![HashMap::new()], | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -123,7 +123,7 @@ impl Hydrator { | ||||||
|         environment: &mut Environment, |         environment: &mut Environment, | ||||||
|         unbounds: &mut Vec<&'a Span>, |         unbounds: &mut Vec<&'a Span>, | ||||||
|     ) -> Result<Rc<Type>, Error> { |     ) -> Result<Rc<Type>, Error> { | ||||||
|         match annotation { |         let return_type = match annotation { | ||||||
|             Annotation::Constructor { |             Annotation::Constructor { | ||||||
|                 location, |                 location, | ||||||
|                 module, |                 module, | ||||||
|  | @ -153,8 +153,16 @@ impl Hydrator { | ||||||
|                     environment.increment_usage(name); |                     environment.increment_usage(name); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // Ensure that the correct number of arguments have been given to the constructor
 |                 // Ensure that the correct number of arguments have been given to the constructor.
 | ||||||
|                 if args.len() != parameters.len() { |                 //
 | ||||||
|  |                 // NOTE:
 | ||||||
|  |                 // We do consider a special case for 'Data', where we allow them to optionally
 | ||||||
|  |                 // carry a phantom type. That type has no effect whatsoever on the semantic (since
 | ||||||
|  |                 // anything can be cast to `Data` anyway) but it does provide some nice context for
 | ||||||
|  |                 // blueprint schema generation.
 | ||||||
|  |                 if args.len() != parameters.len() && !return_type.is_data() | ||||||
|  |                     || args.len() > 1 && return_type.is_data() | ||||||
|  |                 { | ||||||
|                     return Err(Error::IncorrectTypeArity { |                     return Err(Error::IncorrectTypeArity { | ||||||
|                         location: *location, |                         location: *location, | ||||||
|                         name: name.to_string(), |                         name: name.to_string(), | ||||||
|  | @ -240,6 +248,8 @@ impl Hydrator { | ||||||
| 
 | 
 | ||||||
|                 Ok(tuple(typed_elems)) |                 Ok(tuple(typed_elems)) | ||||||
|             } |             } | ||||||
|         } |         }?; | ||||||
|  | 
 | ||||||
|  |         Ok(environment.annotate(return_type, annotation)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -126,6 +126,7 @@ impl UntypedModule { | ||||||
|             module_types_constructors: types_constructors, |             module_types_constructors: types_constructors, | ||||||
|             module_values: values, |             module_values: values, | ||||||
|             accessors, |             accessors, | ||||||
|  |             annotations, | ||||||
|             .. |             .. | ||||||
|         } = environment; |         } = environment; | ||||||
| 
 | 
 | ||||||
|  | @ -141,6 +142,7 @@ impl UntypedModule { | ||||||
|                 types_constructors, |                 types_constructors, | ||||||
|                 values, |                 values, | ||||||
|                 accessors, |                 accessors, | ||||||
|  |                 annotations, | ||||||
|                 kind, |                 kind, | ||||||
|                 package: package.to_string(), |                 package: package.to_string(), | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,46 @@ | ||||||
|  | --- | ||||||
|  | source: crates/aiken-project/src/blueprint/validator.rs | ||||||
|  | description: "Code:\n\npub type Foo {\n    foo: Int\n}\n\nvalidator {\n    fn annotated_data(datum: Data<Foo>, redeemer: Data, ctx: Void) {\n        True\n    }\n}\n" | ||||||
|  | --- | ||||||
|  | { | ||||||
|  |   "title": "test_module.annotated_data", | ||||||
|  |   "datum": { | ||||||
|  |     "title": "datum", | ||||||
|  |     "schema": { | ||||||
|  |       "$ref": "#/definitions/test_module~1Foo" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "redeemer": { | ||||||
|  |     "title": "redeemer", | ||||||
|  |     "schema": { | ||||||
|  |       "$ref": "#/definitions/Data" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "compiledCode": "5833010000323222253330044a22930a99802a491856616c696461746f722072657475726e65642066616c736500136565734ae701", | ||||||
|  |   "hash": "52a21f2b4f282074cb6c5aefef20d18c25f3657ca348c73875810c37", | ||||||
|  |   "definitions": { | ||||||
|  |     "Data": { | ||||||
|  |       "title": "Data", | ||||||
|  |       "description": "Any Plutus data." | ||||||
|  |     }, | ||||||
|  |     "Int": { | ||||||
|  |       "dataType": "integer" | ||||||
|  |     }, | ||||||
|  |     "test_module/Foo": { | ||||||
|  |       "title": "Foo", | ||||||
|  |       "anyOf": [ | ||||||
|  |         { | ||||||
|  |           "title": "Foo", | ||||||
|  |           "dataType": "constructor", | ||||||
|  |           "index": 0, | ||||||
|  |           "fields": [ | ||||||
|  |             { | ||||||
|  |               "title": "foo", | ||||||
|  |               "$ref": "#/definitions/Int" | ||||||
|  |             } | ||||||
|  |           ] | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -5,14 +5,15 @@ use super::{ | ||||||
|     schema::{Annotated, Schema}, |     schema::{Annotated, Schema}, | ||||||
| }; | }; | ||||||
| use crate::module::{CheckedModule, CheckedModules}; | use crate::module::{CheckedModule, CheckedModules}; | ||||||
| use std::rc::Rc; |  | ||||||
| 
 |  | ||||||
| use aiken_lang::{ | use aiken_lang::{ | ||||||
|     ast::{TypedArg, TypedFunction, TypedValidator}, |     ast::{Annotation, TypedArg, TypedFunction, TypedValidator}, | ||||||
|     gen_uplc::CodeGenerator, |     gen_uplc::CodeGenerator, | ||||||
|  |     tipo::Type, | ||||||
| }; | }; | ||||||
| use miette::NamedSource; | use miette::NamedSource; | ||||||
| use serde; | use serde; | ||||||
|  | use std::borrow::Borrow; | ||||||
|  | use std::rc::Rc; | ||||||
| use uplc::{ | use uplc::{ | ||||||
|     ast::{Constant, DeBruijn, Program, Term}, |     ast::{Constant, DeBruijn, Program, Term}, | ||||||
|     PlutusData, |     PlutusData, | ||||||
|  | @ -96,7 +97,11 @@ impl Validator { | ||||||
|         let parameters = params |         let parameters = params | ||||||
|             .iter() |             .iter() | ||||||
|             .map(|param| { |             .map(|param| { | ||||||
|                 Annotated::from_type(modules.into(), ¶m.tipo, &mut definitions) |                 Annotated::from_type( | ||||||
|  |                     modules.into(), | ||||||
|  |                     tipo_or_annotation(module, param), | ||||||
|  |                     &mut definitions, | ||||||
|  |                 ) | ||||||
|                 .map(|schema| Parameter { |                 .map(|schema| Parameter { | ||||||
|                     title: Some(param.arg_name.get_label()), |                     title: Some(param.arg_name.get_label()), | ||||||
|                     schema, |                     schema, | ||||||
|  | @ -118,23 +123,30 @@ impl Validator { | ||||||
|             parameters, |             parameters, | ||||||
|             datum: datum |             datum: datum | ||||||
|                 .map(|datum| { |                 .map(|datum| { | ||||||
|                     Annotated::from_type(modules.into(), &datum.tipo, &mut definitions).map_err( |                     Annotated::from_type( | ||||||
|                         |error| Error::Schema { |                         modules.into(), | ||||||
|  |                         tipo_or_annotation(module, datum), | ||||||
|  |                         &mut definitions, | ||||||
|  |                     ) | ||||||
|  |                     .map_err(|error| Error::Schema { | ||||||
|                         error, |                         error, | ||||||
|                         location: datum.location, |                         location: datum.location, | ||||||
|                         source_code: NamedSource::new( |                         source_code: NamedSource::new( | ||||||
|                             module.input_path.display().to_string(), |                             module.input_path.display().to_string(), | ||||||
|                             module.code.clone(), |                             module.code.clone(), | ||||||
|                         ), |                         ), | ||||||
|                         }, |                     }) | ||||||
|                     ) |  | ||||||
|                 }) |                 }) | ||||||
|                 .transpose()? |                 .transpose()? | ||||||
|                 .map(|schema| Parameter { |                 .map(|schema| Parameter { | ||||||
|                     title: datum.map(|datum| datum.arg_name.get_label()), |                     title: datum.map(|datum| datum.arg_name.get_label()), | ||||||
|                     schema, |                     schema, | ||||||
|                 }), |                 }), | ||||||
|             redeemer: Annotated::from_type(modules.into(), &redeemer.tipo, &mut definitions) |             redeemer: Annotated::from_type( | ||||||
|  |                 modules.into(), | ||||||
|  |                 tipo_or_annotation(module, redeemer), | ||||||
|  |                 &mut definitions, | ||||||
|  |             ) | ||||||
|             .map_err(|error| Error::Schema { |             .map_err(|error| Error::Schema { | ||||||
|                 error, |                 error, | ||||||
|                 location: redeemer.location, |                 location: redeemer.location, | ||||||
|  | @ -160,6 +172,29 @@ impl Validator { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | fn tipo_or_annotation<'a>(module: &'a CheckedModule, arg: &'a TypedArg) -> &'a Type { | ||||||
|  |     match *arg.tipo.borrow() { | ||||||
|  |         Type::App { | ||||||
|  |             module: ref module_name, | ||||||
|  |             name: ref type_name, | ||||||
|  |             .. | ||||||
|  |         } if module_name.is_empty() && &type_name[..] == "Data" => match arg.annotation { | ||||||
|  |             Some(Annotation::Constructor { ref arguments, .. }) if !arguments.is_empty() => module | ||||||
|  |                 .ast | ||||||
|  |                 .type_info | ||||||
|  |                 .annotations | ||||||
|  |                 .get( | ||||||
|  |                     arguments | ||||||
|  |                         .first() | ||||||
|  |                         .expect("guard ensures at least one element"), | ||||||
|  |                 ) | ||||||
|  |                 .unwrap_or(&arg.tipo), | ||||||
|  |             _ => &arg.tipo, | ||||||
|  |         }, | ||||||
|  |         _ => &arg.tipo, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl Validator { | impl Validator { | ||||||
|     pub fn apply( |     pub fn apply( | ||||||
|         self, |         self, | ||||||
|  | @ -543,6 +578,23 @@ mod tests { | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn annotated_data() { | ||||||
|  |         assert_validator!( | ||||||
|  |             r#" | ||||||
|  |             pub type Foo { | ||||||
|  |                 foo: Int | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             validator { | ||||||
|  |                 fn annotated_data(datum: Data<Foo>, redeemer: Data, ctx: Void) { | ||||||
|  |                     True | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             "#
 | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn validate_arguments_integer() { |     fn validate_arguments_integer() { | ||||||
|         let definitions = fixture_definitions(); |         let definitions = fixture_definitions(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 KtorZ
						KtorZ