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