Implement record access syntax for validator handlers.

This is a little trick which detects record access and replace them
  with a simple var. The var itself is the validator handler name,
  though since it contains dots, it cannot be referred to by users
  explicitly. Yet fundamentally, it is semantically equivalent to just
  calling the function by its name.

  Note that this commit also removes the weird backdoor for allowing
  importing validators in modules starting with `tests`. Allowing
  validators handler to be used in importable module requires more work
  and is arguably useful; so we will wait until someone complain and
  reconsider the proper way to do it.
This commit is contained in:
KtorZ 2024-08-27 17:16:15 +02:00
parent ee8f608c0b
commit 0510ca58f7
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
8 changed files with 194 additions and 20 deletions

View File

@ -1,6 +1,7 @@
pub mod well_known; pub mod well_known;
use crate::{ use crate::{
ast::well_known::VALIDATOR_ELSE,
expr::{TypedExpr, UntypedExpr}, expr::{TypedExpr, UntypedExpr},
line_numbers::LineNumbers, line_numbers::LineNumbers,
parser::token::{Base, Token}, parser::token::{Base, Token},
@ -477,6 +478,12 @@ pub struct Validator<T, Arg, Expr> {
pub fallback: Function<T, Expr, Arg>, pub fallback: Function<T, Expr, Arg>,
} }
impl<T, Arg, Expr> Validator<T, Arg, Expr> {
pub fn handler_name(validator: &str, handler: &str) -> String {
format!("{}.{}", validator, handler)
}
}
impl TypedValidator { impl TypedValidator {
pub fn available_handler_names() -> Vec<String> { pub fn available_handler_names() -> Vec<String> {
vec![ vec![
@ -486,6 +493,7 @@ impl TypedValidator {
HANDLER_PUBLISH.to_string(), HANDLER_PUBLISH.to_string(),
HANDLER_VOTE.to_string(), HANDLER_VOTE.to_string(),
HANDLER_PROPOSE.to_string(), HANDLER_PROPOSE.to_string(),
VALIDATOR_ELSE.to_string(),
] ]
} }
@ -669,7 +677,10 @@ impl TypedValidator {
( (
FunctionAccessKey { FunctionAccessKey {
module_name: module_name.to_string(), module_name: module_name.to_string(),
function_name: handler.name.clone(), function_name: TypedValidator::handler_name(
self.name.as_str(),
handler.name.as_str(),
),
}, },
handler, handler,
) )

View File

@ -4029,9 +4029,9 @@ impl<'a> CodeGenerator<'a> {
.get(&generic_function_key.function_name) .get(&generic_function_key.function_name)
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"Missing function definition for {}. Known definitions: {:?}", "Missing function definition for {}. Known functions: {:?}",
generic_function_key.function_name, generic_function_key.function_name,
self.code_gen_functions.keys(), self.functions.keys(),
) )
}); });

View File

@ -1,6 +1,6 @@
use super::function::param; use super::function::param;
use crate::{ use crate::{
ast::{self, ArgBy, ArgName}, ast::{self, well_known, ArgBy, ArgName},
expr::UntypedExpr, expr::UntypedExpr,
parser::{annotation, error::ParseError, expr, token::Token}, parser::{annotation, error::ParseError, expr, token::Token},
}; };
@ -31,7 +31,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
.then( .then(
just(Token::Else) just(Token::Else)
.ignore_then(args_and_body().map_with_span(|mut function, span| { .ignore_then(args_and_body().map_with_span(|mut function, span| {
function.name = "else".to_string(); function.name = well_known::VALIDATOR_ELSE.to_string();
function.location.start = span.start; function.location.start = span.start;
function function
@ -79,7 +79,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
doc: None, doc: None,
location, location,
end_position: location.end - 1, end_position: location.end - 1,
name: "else".to_string(), name: well_known::VALIDATOR_ELSE.to_string(),
public: true, public: true,
return_annotation: Some(ast::Annotation::boolean(location)), return_annotation: Some(ast::Annotation::boolean(location)),
return_type: (), return_type: (),

View File

@ -3050,3 +3050,100 @@ fn test_return_illegal() {
Err((_, Error::IllegalTestType { .. })) Err((_, Error::IllegalTestType { .. }))
)) ))
} }
#[test]
fn validator_by_name() {
let source_code = r#"
validator foo {
mint(_redeemer: Data, policy_id: ByteArray, _self: Data) {
policy_id == "foo"
}
}
test test_1() {
foo.mint(Void, "foo", Void)
}
"#;
assert!(check_validator(parse(source_code)).is_ok())
}
#[test]
fn validator_by_name_unknown_handler() {
let source_code = r#"
validator foo {
mint(_redeemer: Data, policy_id: ByteArray, _self: Data) {
policy_id == "foo"
}
}
test foo() {
foo.bar(Void, "foo", Void)
}
"#;
assert!(matches!(
check_validator(parse(source_code)),
Err((_, Error::UnknownValidatorHandler { .. }))
))
}
#[test]
fn validator_by_name_module_duplicate() {
let source_code = r#"
use aiken/builtin
validator builtin {
mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) {
True
}
}
"#;
assert!(matches!(
check_validator(parse(source_code)),
Err((_, Error::DuplicateName { .. }))
))
}
#[test]
fn validator_by_name_validator_duplicate_1() {
let source_code = r#"
validator foo {
mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) {
True
}
}
validator foo {
mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) {
True
}
}
"#;
assert!(matches!(
check_validator(parse(source_code)),
Err((_, Error::DuplicateName { .. }))
))
}
#[test]
fn validator_by_name_validator_duplicate_2() {
let source_code = r#"
validator foo {
mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) {
True
}
mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) {
True
}
}
"#;
assert!(matches!(
check_validator(parse(source_code)),
Err((_, Error::DuplicateName { .. }))
))
}

View File

@ -9,8 +9,8 @@ use crate::{
ast::{ ast::{
self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind,
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction,
TypedPattern, UnqualifiedImport, UntypedArg, UntypedDefinition, UntypedFunction, Use, TypedPattern, TypedValidator, UnqualifiedImport, UntypedArg, UntypedDefinition,
Validator, PIPE_VARIABLE, UntypedFunction, Use, Validator, PIPE_VARIABLE,
}, },
tipo::{fields::FieldMap, TypeAliasAnnotation}, tipo::{fields::FieldMap, TypeAliasAnnotation},
IdGenerator, IdGenerator,
@ -57,6 +57,9 @@ pub struct Environment<'a> {
/// Top-level function definitions from the module /// Top-level function definitions from the module
pub module_functions: HashMap<String, &'a UntypedFunction>, pub module_functions: HashMap<String, &'a UntypedFunction>,
/// Top-level validator definitions from the module
pub module_validators: HashMap<String, (Span, Vec<String>)>,
/// Top-level functions that have been inferred /// Top-level functions that have been inferred
pub inferred_functions: HashMap<String, TypedFunction>, pub inferred_functions: HashMap<String, TypedFunction>,
@ -315,7 +318,7 @@ impl<'a> Environment<'a> {
let handlers = handlers let handlers = handlers
.into_iter() .into_iter()
.map(|mut fun| { .map(|mut fun| {
let handler_name = format!("{}_{}", &name, &fun.name); let handler_name = TypedValidator::handler_name(&name, &fun.name);
let old_name = fun.name; let old_name = fun.name;
fun.name = handler_name; fun.name = handler_name;
@ -332,7 +335,7 @@ impl<'a> Environment<'a> {
}) })
.collect(); .collect();
let fallback_name = format!("{}_{}", &name, &fallback.name); let fallback_name = TypedValidator::handler_name(&name, &fallback.name);
let old_name = fallback.name; let old_name = fallback.name;
fallback.name = fallback_name; fallback.name = fallback_name;
@ -776,6 +779,7 @@ impl<'a> Environment<'a> {
module_types_constructors: prelude.types_constructors.clone(), module_types_constructors: prelude.types_constructors.clone(),
module_values: HashMap::new(), module_values: HashMap::new(),
module_functions: HashMap::new(), module_functions: HashMap::new(),
module_validators: HashMap::new(),
imported_modules: HashMap::new(), imported_modules: HashMap::new(),
unused_modules: HashMap::new(), unused_modules: HashMap::new(),
unqualified_imported_names: HashMap::new(), unqualified_imported_names: HashMap::new(),
@ -833,9 +837,7 @@ impl<'a> Environment<'a> {
let module_info = self.find_module(module, *location)?; let module_info = self.find_module(module, *location)?;
if module_info.kind.is_validator() if module_info.kind.is_validator()
&& (self.current_kind.is_lib() && (self.current_kind.is_lib() || self.current_kind.is_env())
|| self.current_kind.is_env()
|| !self.current_module.starts_with("tests"))
{ {
return Err(Error::ValidatorImported { return Err(Error::ValidatorImported {
location: *location, location: *location,
@ -1270,7 +1272,7 @@ impl<'a> Environment<'a> {
params, params,
name, name,
doc: _, doc: _,
location: _, location,
end_position: _, end_position: _,
}) if kind.is_validator() => { }) if kind.is_validator() => {
let default_annotation = |mut arg: UntypedArg| { let default_annotation = |mut arg: UntypedArg| {
@ -1283,6 +1285,8 @@ impl<'a> Environment<'a> {
} }
}; };
let mut handler_names = vec![];
for handler in handlers { for handler in handlers {
let temp_params: Vec<UntypedArg> = params let temp_params: Vec<UntypedArg> = params
.iter() .iter()
@ -1291,8 +1295,10 @@ impl<'a> Environment<'a> {
.map(default_annotation) .map(default_annotation)
.collect(); .collect();
handler_names.push(handler.name.clone());
self.register_function( self.register_function(
&format!("{}_{}", name, handler.name), &TypedValidator::handler_name(name.as_str(), handler.name.as_str()),
&temp_params, &temp_params,
&handler.return_annotation, &handler.return_annotation,
module_name, module_name,
@ -1310,7 +1316,7 @@ impl<'a> Environment<'a> {
.collect(); .collect();
self.register_function( self.register_function(
&format!("{}_{}", name, fallback.name), &TypedValidator::handler_name(name.as_str(), fallback.name.as_str()),
&temp_params, &temp_params,
&fallback.return_annotation, &fallback.return_annotation,
module_name, module_name,
@ -1318,6 +1324,28 @@ impl<'a> Environment<'a> {
names, names,
&fallback.location, &fallback.location,
)?; )?;
handler_names.push(fallback.name.clone());
let err_duplicate_name = |previous_location: Span| {
Err(Error::DuplicateName {
name: name.to_string(),
previous_location,
location: location.map_end(|end| end + 1 + name.len()),
})
};
if let Some((previous_location, _)) = self.imported_modules.get(name) {
return err_duplicate_name(*previous_location);
}
match self
.module_validators
.insert(name.to_string(), (*location, handler_names))
{
Some((previous_location, _)) => err_duplicate_name(previous_location),
None => Ok(()),
}?
} }
Definition::Validator(Validator { location, .. }) => { Definition::Validator(Validator { location, .. }) => {

View File

@ -1086,6 +1086,21 @@ The best thing to do from here is to remove it."#))]
location: Span, location: Span,
available_purposes: Vec<String>, available_purposes: Vec<String>,
}, },
#[error("I could not find an appropriate handler in the validator definition\n")]
#[diagnostic(code("unknown::handler"))]
#[diagnostic(help(
"When referring to a validator handler via record access, you must refer to one of the declared handlers:\n{}",
available_handlers
.iter()
.map(|p| format!("-> {}", p.if_supports_color(Stdout, |s| s.green())))
.join("\n")
))]
UnknownValidatorHandler {
#[label("unknown validator handler")]
location: Span,
available_handlers: Vec<String>,
},
} }
impl ExtraData for Error { impl ExtraData for Error {
@ -1146,6 +1161,7 @@ impl ExtraData for Error {
| Error::ExpectOnOpaqueType { .. } | Error::ExpectOnOpaqueType { .. }
| Error::ValidatorMustReturnBool { .. } | Error::ValidatorMustReturnBool { .. }
| Error::UnknownPurpose { .. } | Error::UnknownPurpose { .. }
| Error::UnknownValidatorHandler { .. }
| Error::MustInferFirst { .. } => None, | Error::MustInferFirst { .. } => None,
Error::UnknownType { name, .. } Error::UnknownType { name, .. }

View File

@ -14,8 +14,8 @@ use crate::{
ByteArrayFormatPreference, CallArg, Constant, Curve, Function, IfBranch, ByteArrayFormatPreference, CallArg, Constant, Curve, Function, IfBranch,
LogicalOpChainKind, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, LogicalOpChainKind, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing,
TypedArg, TypedCallArg, TypedClause, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, TypedArg, TypedCallArg, TypedClause, TypedIfBranch, TypedPattern, TypedRecordUpdateArg,
UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction, UntypedIfBranch, TypedValidator, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction,
UntypedPattern, UntypedRecordUpdateArg, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg,
}, },
builtins::{from_default_function, BUILTIN}, builtins::{from_default_function, BUILTIN},
expr::{FnStyle, TypedExpr, UntypedExpr}, expr::{FnStyle, TypedExpr, UntypedExpr},
@ -918,6 +918,28 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
label: String, label: String,
access_location: Span, access_location: Span,
) -> Result<TypedExpr, Error> { ) -> Result<TypedExpr, Error> {
if let UntypedExpr::Var { ref name, location } = container {
if let Some((_, available_handlers)) = self
.environment
.module_validators
.get(name.as_str())
.cloned()
{
return self
.infer_var(
TypedValidator::handler_name(name.as_str(), label.as_str()),
location,
)
.map_err(|err| match err {
Error::UnknownVariable { .. } => Error::UnknownValidatorHandler {
location: access_location.map(|_start, end| (location.end, end)),
available_handlers,
},
_ => err,
});
}
}
// Attempt to infer the container as a record access. If that fails, we may be shadowing the name // Attempt to infer the container as a record access. If that fails, we may be shadowing the name
// of an imported module, so attempt to infer the container as a module access. // of an imported module, so attempt to infer the container as a module access.
// TODO: Remove this cloning // TODO: Remove this cloning

View File

@ -178,7 +178,7 @@ fn infer_definition(
let params_length = params.len(); let params_length = params.len();
environment.in_new_scope(|environment| { environment.in_new_scope(|environment| {
let fallback_name = format!("{}_{}", &name, &fallback.name); let fallback_name = TypedValidator::handler_name(&name, &fallback.name);
put_params_in_scope(&fallback_name, environment, &params); put_params_in_scope(&fallback_name, environment, &params);
@ -189,7 +189,7 @@ fn infer_definition(
let temp_params = params.iter().cloned().chain(handler.arguments); let temp_params = params.iter().cloned().chain(handler.arguments);
handler.arguments = temp_params.collect(); handler.arguments = temp_params.collect();
let handler_name = format!("{}_{}", &name, &handler.name); let handler_name = TypedValidator::handler_name(&name, &handler.name);
let old_name = handler.name; let old_name = handler.name;
handler.name = handler_name; handler.name = handler_name;