feat(validator): parsing and typechecking for double validators

This commit is contained in:
rvcas 2023-03-16 18:33:57 -04:00
parent 3eccc349aa
commit ed92869fb9
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
8 changed files with 259 additions and 94 deletions

View File

@ -322,6 +322,7 @@ pub struct Validator<T, Expr> {
pub doc: Option<String>, pub doc: Option<String>,
pub end_position: usize, pub end_position: usize,
pub fun: Function<T, Expr>, pub fun: Function<T, Expr>,
pub other_fun: Option<Function<T, Expr>>,
pub location: Span, pub location: Span,
pub params: Vec<Arg<T>>, pub params: Vec<Arg<T>>,
} }

View File

@ -504,10 +504,15 @@ impl<'comments> Formatter<'comments> {
&mut self, &mut self,
params: &'a [UntypedArg], params: &'a [UntypedArg],
fun: &'a UntypedFunction, fun: &'a UntypedFunction,
other_fun: &'a Option<UntypedFunction>,
end_position: usize, end_position: usize,
) -> Document<'a> { ) -> Document<'a> {
// Fn and args // Fn and args
let head = "fn".to_doc().append(wrap_args( let head = "fn"
.to_doc()
.append(" ")
.append(fun.name.to_doc())
.append(wrap_args(
fun.arguments.iter().map(|e| (self.fn_arg(e), false)), fun.arguments.iter().map(|e| (self.fn_arg(e), false)),
)); ));
@ -528,11 +533,7 @@ impl<'comments> Formatter<'comments> {
}; };
// validator name(params) // validator name(params)
let v_head = "validator" let v_head = "validator".to_doc().append(if !params.is_empty() {
.to_doc()
.append(" ")
.append(fun.name.as_str())
.append(if !params.is_empty() {
wrap_args(params.iter().map(|e| (self.fn_arg(e), false))) wrap_args(params.iter().map(|e| (self.fn_arg(e), false)))
} else { } else {
"".to_doc() "".to_doc()

View File

@ -242,19 +242,9 @@ pub fn type_alias_parser() -> impl Parser<Token, ast::UntypedDefinition, Error =
} }
pub fn validator_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> { pub fn validator_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
just(Token::Validator) let func_parser = just(Token::Fn)
.ignore_then(select! {Token::Name {name} => name}.map_with_span(|name, span| (name, span))) .ignore_then(select! {Token::Name {name} => name})
.then( .then(
fn_param_parser()
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
.map_with_span(|arguments, span| (arguments, span))
.or_not(),
)
.then(
just(Token::Fn)
.ignore_then(
fn_param_parser() fn_param_parser()
.separated_by(just(Token::Comma)) .separated_by(just(Token::Comma))
.allow_trailing() .allow_trailing()
@ -268,7 +258,7 @@ pub fn validator_parser() -> impl Parser<Token, ast::UntypedDefinition, Error =
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
) )
.map_with_span( .map_with_span(
|(((arguments, args_span), return_annotation), body), span| ast::Function { |(((name, (arguments, args_span)), return_annotation), body), span| ast::Function {
arguments, arguments,
body: body.unwrap_or_else(|| expr::UntypedExpr::todo(span, None)), body: body.unwrap_or_else(|| expr::UntypedExpr::todo(span, None)),
doc: None, doc: None,
@ -280,24 +270,49 @@ pub fn validator_parser() -> impl Parser<Token, ast::UntypedDefinition, Error =
.unwrap_or_else(|| args_span.end), .unwrap_or_else(|| args_span.end),
}, },
end_position: span.end - 1, end_position: span.end - 1,
name: "".to_string(), name,
public: false, public: false,
return_annotation, return_annotation,
return_type: (), return_type: (),
}, },
) );
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
)
.map_with_span(|((name, opt_extra_params), mut fun), span| {
fun.name = name.0;
let (params, params_span) = opt_extra_params.unwrap_or((vec![], name.1)); just(Token::Validator)
.ignore_then(
fn_param_parser()
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
.map_with_span(|arguments, span| (arguments, span))
.or_not(),
)
.then(
func_parser
.repeated()
.at_least(1)
.at_most(2)
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))
.map(IntoIterator::into_iter),
)
.map_with_span(|(opt_extra_params, mut functions), span| {
let (params, params_span) = opt_extra_params.unwrap_or((
vec![],
Span {
start: 0,
// just needs to be the word `validator` at this point
end: span.start + 9,
},
));
ast::UntypedDefinition::Validator(ast::Validator { ast::UntypedDefinition::Validator(ast::Validator {
doc: None, doc: None,
fun, // unwrap is safe to do here because
// above we use `.at_least(1)`
fun: functions.next().unwrap(),
other_fun: functions.next(),
location: Span { location: Span {
start: span.start, start: span.start,
// capture the span from the optional params
end: params_span.end, end: params_span.end,
}, },
params, params,

View File

@ -53,8 +53,8 @@ fn check_validator(
#[test] #[test]
fn validator_illegal_return_type() { fn validator_illegal_return_type() {
let source_code = r#" let source_code = r#"
validator foo { validator {
fn(d, r, c) { fn foo(d, r, c) {
1 1
} }
} }
@ -69,8 +69,8 @@ fn validator_illegal_return_type() {
#[test] #[test]
fn validator_illegal_arity() { fn validator_illegal_arity() {
let source_code = r#" let source_code = r#"
validator foo { validator {
fn(c) { fn foo(c) {
True True
} }
} }
@ -85,8 +85,8 @@ fn validator_illegal_arity() {
#[test] #[test]
fn validator_correct_form() { fn validator_correct_form() {
let source_code = r#" let source_code = r#"
validator foo { validator {
fn(d, r, c) { fn foo(d, r, c) {
True True
} }
} }
@ -98,8 +98,8 @@ fn validator_correct_form() {
#[test] #[test]
fn validator_in_lib_warning() { fn validator_in_lib_warning() {
let source_code = r#" let source_code = r#"
validator foo { validator {
fn(c) { fn foo(c) {
True True
} }
} }

View File

@ -58,16 +58,16 @@ fn test_format_if() {
#[test] #[test]
fn test_format_validator() { fn test_format_validator() {
let src = indoc! {r#" let src = indoc! {r#"
validator foo ( ) { validator ( ) {
fn(d: Datum, r: Redeemer, ctx: ScriptContext) -> Bool { fn foo (d: Datum, r: Redeemer, ctx: ScriptContext) -> Bool {
True True
} }
} }
"#}; "#};
let expected = indoc! {r#" let expected = indoc! {r#"
validator foo { validator {
fn(d: Datum, r: Redeemer, ctx: ScriptContext) -> Bool { fn foo(d: Datum, r: Redeemer, ctx: ScriptContext) -> Bool {
True True
} }
} }

View File

@ -41,8 +41,8 @@ fn windows_newline() {
#[test] #[test]
fn validator() { fn validator() {
let code = indoc! {r#" let code = indoc! {r#"
validator foo { validator {
fn(datum, rdmr, ctx) { fn foo(datum, rdmr, ctx) {
True True
} }
} }
@ -91,14 +91,120 @@ fn validator() {
name: "True".to_string(), name: "True".to_string(),
}, },
doc: None, doc: None,
location: Span::new((), 18..38), location: Span::new((), 14..38),
name: "foo".to_string(), name: "foo".to_string(),
public: false, public: false,
return_annotation: None, return_annotation: None,
return_type: (), return_type: (),
end_position: 52, end_position: 52,
}, },
location: Span::new((), 0..13), other_fun: None,
location: Span::new((), 0..9),
params: vec![],
})],
)
}
#[test]
fn double_validator() {
let code = indoc! {r#"
validator {
fn foo(datum, rdmr, ctx) {
True
}
fn bar(rdmr, ctx) {
True
}
}
"#};
assert_definitions(
code,
vec![ast::UntypedDefinition::Validator(ast::Validator {
doc: None,
end_position: 90,
fun: Function {
arguments: vec![
ast::Arg {
arg_name: ast::ArgName::Named {
name: "datum".to_string(),
label: "datum".to_string(),
location: Span::new((), 21..26),
},
location: Span::new((), 21..26),
annotation: None,
tipo: (),
},
ast::Arg {
arg_name: ast::ArgName::Named {
name: "rdmr".to_string(),
label: "rdmr".to_string(),
location: Span::new((), 28..32),
},
location: Span::new((), 28..32),
annotation: None,
tipo: (),
},
ast::Arg {
arg_name: ast::ArgName::Named {
name: "ctx".to_string(),
label: "ctx".to_string(),
location: Span::new((), 34..37),
},
location: Span::new((), 34..37),
annotation: None,
tipo: (),
},
],
body: expr::UntypedExpr::Var {
location: Span::new((), 45..49),
name: "True".to_string(),
},
doc: None,
location: Span::new((), 14..38),
name: "foo".to_string(),
public: false,
return_annotation: None,
return_type: (),
end_position: 52,
},
other_fun: Some(Function {
arguments: vec![
ast::Arg {
arg_name: ast::ArgName::Named {
name: "rdmr".to_string(),
label: "rdmr".to_string(),
location: Span::new((), 64..68),
},
location: Span::new((), 64..68),
annotation: None,
tipo: (),
},
ast::Arg {
arg_name: ast::ArgName::Named {
name: "ctx".to_string(),
label: "ctx".to_string(),
location: Span::new((), 70..73),
},
location: Span::new((), 70..73),
annotation: None,
tipo: (),
},
],
body: expr::UntypedExpr::Var {
location: Span::new((), 81..85),
name: "True".to_string(),
},
doc: None,
location: Span::new((), 57..74),
name: "bar".to_string(),
public: false,
return_annotation: None,
return_type: (),
end_position: 88,
}),
location: Span::new((), 0..9),
params: vec![], params: vec![],
})], })],
) )

View File

@ -3,8 +3,8 @@ use std::collections::HashMap;
use crate::{ use crate::{
ast::{ ast::{
DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor, DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor,
RecordConstructorArg, Span, Tracing, TypeAlias, TypedDefinition, TypedModule, RecordConstructorArg, Span, Tracing, TypeAlias, TypedDefinition, TypedFunction,
UntypedDefinition, UntypedModule, Use, Validator, TypedModule, UntypedDefinition, UntypedModule, Use, Validator,
}, },
builtins, builtins,
builtins::function, builtins::function,
@ -255,11 +255,12 @@ fn infer_definition(
location, location,
end_position, end_position,
mut fun, mut fun,
mut params, other_fun,
params,
}) => { }) => {
let params_length = params.len(); let params_length = params.len();
params.append(&mut fun.arguments); let temp_params = params.iter().cloned().chain(fun.arguments);
fun.arguments = params; fun.arguments = temp_params.collect();
if let Definition::Fn(mut typed_fun) = infer_definition( if let Definition::Fn(mut typed_fun) = infer_definition(
Definition::Fn(fun), Definition::Fn(fun),
@ -285,10 +286,51 @@ fn infer_definition(
}); });
} }
let typed_other_fun = other_fun
.map(|mut other| -> Result<TypedFunction, Error> {
let params = params.into_iter().chain(other.arguments);
other.arguments = params.collect();
if let Definition::Fn(mut other_typed_fun) = infer_definition(
Definition::Fn(other),
module_name,
hydrators,
environment,
tracing,
kind,
)? {
if !other_typed_fun.return_type.is_bool() {
return Err(Error::ValidatorMustReturnBool {
return_type: other_typed_fun.return_type.clone(),
location: other_typed_fun.location,
});
}
other_typed_fun.arguments.drain(0..params_length);
if other_typed_fun.arguments.len() < 2
|| other_typed_fun.arguments.len() > 3
{
return Err(Error::IncorrectValidatorArity {
count: typed_fun.arguments.len() as u32,
location: typed_fun.location,
});
}
Ok(other_typed_fun)
} else {
unreachable!(
"validator definition inferred as something other than a function?"
)
}
})
.transpose();
Ok(Definition::Validator(Validator { Ok(Definition::Validator(Validator {
doc, doc,
end_position, end_position,
fun: typed_fun, fun: typed_fun,
other_fun: typed_other_fun?,
location, location,
params: typed_params, params: typed_params,
})) }))

View File

@ -272,8 +272,8 @@ mod test {
fn mint_basic() { fn mint_basic() {
assert_validator( assert_validator(
r#" r#"
validator mint { validator {
fn(redeemer: Data, ctx: Data) { fn mint(redeemer: Data, ctx: Data) {
True True
} }
} }
@ -302,8 +302,8 @@ mod test {
fn mint_parameterized() { fn mint_parameterized() {
assert_validator( assert_validator(
r#" r#"
validator mint(utxo_ref: Int) { validator(utxo_ref: Int) {
fn(redeemer: Data, ctx: Data) { fn mint(redeemer: Data, ctx: Data) {
True True
} }
} }
@ -373,8 +373,8 @@ mod test {
Abort Abort
} }
validator simplified_hydra { validator {
fn(datum: State, redeemer: Input, ctx: Data) { fn simplified_hydra(datum: State, redeemer: Input, ctx: Data) {
True True
} }
} }
@ -485,8 +485,8 @@ mod test {
fn tuples() { fn tuples() {
assert_validator( assert_validator(
r#" r#"
validator tuples { validator {
fn(datum: (Int, ByteArray), redeemer: (Int, Int, Int), ctx: Void) { fn tuples(datum: (Int, ByteArray), redeemer: (Int, Int, Int), ctx: Void) {
True True
} }
} }
@ -560,8 +560,8 @@ mod test {
Infinite Infinite
} }
validator generics { validator {
fn(redeemer: Either<ByteArray, Interval<Int>>, ctx: Void) { fn generics(redeemer: Either<ByteArray, Interval<Int>>, ctx: Void) {
True True
} }
} }
@ -644,8 +644,8 @@ mod test {
type UUID { UUID } type UUID { UUID }
validator list_2_tuples_as_map { validator {
fn(redeemer: Dict<UUID, Int>, ctx: Void) { fn list_2_tuples_as_map(redeemer: Dict<UUID, Int>, ctx: Void) {
True True
} }
} }
@ -707,8 +707,8 @@ mod test {
type UUID { UUID } type UUID { UUID }
validator opaque_singleton_variants { validator {
fn(redeemer: Dict<UUID, Int>, ctx: Void) { fn opaque_singleton_variants(redeemer: Dict<UUID, Int>, ctx: Void) {
True True
} }
} }
@ -753,8 +753,8 @@ mod test {
foo: Data foo: Data
} }
validator nested_data { validator {
fn(datum: Foo, redeemer: Int, ctx: Void) { fn nested_data(datum: Foo, redeemer: Int, ctx: Void) {
True True
} }
} }
@ -814,8 +814,8 @@ mod test {
Mul(Expr, Expr) Mul(Expr, Expr)
} }
validator recursive_types { validator {
fn(redeemer: Expr, ctx: Void) { fn recursive_types(redeemer: Expr, ctx: Void) {
True True
} }
} }
@ -899,8 +899,8 @@ mod test {
} }
} }
validator recursive_generic_types { validator {
fn(datum: Foo, redeemer: LinkedList<Int>, ctx: Void) { fn recursive_generic_types(datum: Foo, redeemer: LinkedList<Int>, ctx: Void) {
True True
} }
} }