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:
parent
ee8f608c0b
commit
0510ca58f7
|
@ -1,6 +1,7 @@
|
|||
pub mod well_known;
|
||||
|
||||
use crate::{
|
||||
ast::well_known::VALIDATOR_ELSE,
|
||||
expr::{TypedExpr, UntypedExpr},
|
||||
line_numbers::LineNumbers,
|
||||
parser::token::{Base, Token},
|
||||
|
@ -477,6 +478,12 @@ pub struct Validator<T, Arg, Expr> {
|
|||
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 {
|
||||
pub fn available_handler_names() -> Vec<String> {
|
||||
vec![
|
||||
|
@ -486,6 +493,7 @@ impl TypedValidator {
|
|||
HANDLER_PUBLISH.to_string(),
|
||||
HANDLER_VOTE.to_string(),
|
||||
HANDLER_PROPOSE.to_string(),
|
||||
VALIDATOR_ELSE.to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -669,7 +677,10 @@ impl TypedValidator {
|
|||
(
|
||||
FunctionAccessKey {
|
||||
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,
|
||||
)
|
||||
|
|
|
@ -4029,9 +4029,9 @@ impl<'a> CodeGenerator<'a> {
|
|||
.get(&generic_function_key.function_name)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Missing function definition for {}. Known definitions: {:?}",
|
||||
"Missing function definition for {}. Known functions: {:?}",
|
||||
generic_function_key.function_name,
|
||||
self.code_gen_functions.keys(),
|
||||
self.functions.keys(),
|
||||
)
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::function::param;
|
||||
use crate::{
|
||||
ast::{self, ArgBy, ArgName},
|
||||
ast::{self, well_known, ArgBy, ArgName},
|
||||
expr::UntypedExpr,
|
||||
parser::{annotation, error::ParseError, expr, token::Token},
|
||||
};
|
||||
|
@ -31,7 +31,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
|||
.then(
|
||||
just(Token::Else)
|
||||
.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
|
||||
|
@ -79,7 +79,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
|||
doc: None,
|
||||
location,
|
||||
end_position: location.end - 1,
|
||||
name: "else".to_string(),
|
||||
name: well_known::VALIDATOR_ELSE.to_string(),
|
||||
public: true,
|
||||
return_annotation: Some(ast::Annotation::boolean(location)),
|
||||
return_type: (),
|
||||
|
|
|
@ -3050,3 +3050,100 @@ fn test_return_illegal() {
|
|||
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 { .. }))
|
||||
))
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ use crate::{
|
|||
ast::{
|
||||
self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind,
|
||||
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction,
|
||||
TypedPattern, UnqualifiedImport, UntypedArg, UntypedDefinition, UntypedFunction, Use,
|
||||
Validator, PIPE_VARIABLE,
|
||||
TypedPattern, TypedValidator, UnqualifiedImport, UntypedArg, UntypedDefinition,
|
||||
UntypedFunction, Use, Validator, PIPE_VARIABLE,
|
||||
},
|
||||
tipo::{fields::FieldMap, TypeAliasAnnotation},
|
||||
IdGenerator,
|
||||
|
@ -57,6 +57,9 @@ pub struct Environment<'a> {
|
|||
/// Top-level function definitions from the module
|
||||
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
|
||||
pub inferred_functions: HashMap<String, TypedFunction>,
|
||||
|
||||
|
@ -315,7 +318,7 @@ impl<'a> Environment<'a> {
|
|||
let handlers = handlers
|
||||
.into_iter()
|
||||
.map(|mut fun| {
|
||||
let handler_name = format!("{}_{}", &name, &fun.name);
|
||||
let handler_name = TypedValidator::handler_name(&name, &fun.name);
|
||||
|
||||
let old_name = fun.name;
|
||||
fun.name = handler_name;
|
||||
|
@ -332,7 +335,7 @@ impl<'a> Environment<'a> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let fallback_name = format!("{}_{}", &name, &fallback.name);
|
||||
let fallback_name = TypedValidator::handler_name(&name, &fallback.name);
|
||||
|
||||
let old_name = fallback.name;
|
||||
fallback.name = fallback_name;
|
||||
|
@ -776,6 +779,7 @@ impl<'a> Environment<'a> {
|
|||
module_types_constructors: prelude.types_constructors.clone(),
|
||||
module_values: HashMap::new(),
|
||||
module_functions: HashMap::new(),
|
||||
module_validators: HashMap::new(),
|
||||
imported_modules: HashMap::new(),
|
||||
unused_modules: HashMap::new(),
|
||||
unqualified_imported_names: HashMap::new(),
|
||||
|
@ -833,9 +837,7 @@ impl<'a> Environment<'a> {
|
|||
let module_info = self.find_module(module, *location)?;
|
||||
|
||||
if module_info.kind.is_validator()
|
||||
&& (self.current_kind.is_lib()
|
||||
|| self.current_kind.is_env()
|
||||
|| !self.current_module.starts_with("tests"))
|
||||
&& (self.current_kind.is_lib() || self.current_kind.is_env())
|
||||
{
|
||||
return Err(Error::ValidatorImported {
|
||||
location: *location,
|
||||
|
@ -1270,7 +1272,7 @@ impl<'a> Environment<'a> {
|
|||
params,
|
||||
name,
|
||||
doc: _,
|
||||
location: _,
|
||||
location,
|
||||
end_position: _,
|
||||
}) if kind.is_validator() => {
|
||||
let default_annotation = |mut arg: UntypedArg| {
|
||||
|
@ -1283,6 +1285,8 @@ impl<'a> Environment<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
let mut handler_names = vec![];
|
||||
|
||||
for handler in handlers {
|
||||
let temp_params: Vec<UntypedArg> = params
|
||||
.iter()
|
||||
|
@ -1291,8 +1295,10 @@ impl<'a> Environment<'a> {
|
|||
.map(default_annotation)
|
||||
.collect();
|
||||
|
||||
handler_names.push(handler.name.clone());
|
||||
|
||||
self.register_function(
|
||||
&format!("{}_{}", name, handler.name),
|
||||
&TypedValidator::handler_name(name.as_str(), handler.name.as_str()),
|
||||
&temp_params,
|
||||
&handler.return_annotation,
|
||||
module_name,
|
||||
|
@ -1310,7 +1316,7 @@ impl<'a> Environment<'a> {
|
|||
.collect();
|
||||
|
||||
self.register_function(
|
||||
&format!("{}_{}", name, fallback.name),
|
||||
&TypedValidator::handler_name(name.as_str(), fallback.name.as_str()),
|
||||
&temp_params,
|
||||
&fallback.return_annotation,
|
||||
module_name,
|
||||
|
@ -1318,6 +1324,28 @@ impl<'a> Environment<'a> {
|
|||
names,
|
||||
&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, .. }) => {
|
||||
|
|
|
@ -1086,6 +1086,21 @@ The best thing to do from here is to remove it."#))]
|
|||
location: Span,
|
||||
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 {
|
||||
|
@ -1146,6 +1161,7 @@ impl ExtraData for Error {
|
|||
| Error::ExpectOnOpaqueType { .. }
|
||||
| Error::ValidatorMustReturnBool { .. }
|
||||
| Error::UnknownPurpose { .. }
|
||||
| Error::UnknownValidatorHandler { .. }
|
||||
| Error::MustInferFirst { .. } => None,
|
||||
|
||||
Error::UnknownType { name, .. }
|
||||
|
|
|
@ -14,8 +14,8 @@ use crate::{
|
|||
ByteArrayFormatPreference, CallArg, Constant, Curve, Function, IfBranch,
|
||||
LogicalOpChainKind, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing,
|
||||
TypedArg, TypedCallArg, TypedClause, TypedIfBranch, TypedPattern, TypedRecordUpdateArg,
|
||||
UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction, UntypedIfBranch,
|
||||
UntypedPattern, UntypedRecordUpdateArg,
|
||||
TypedValidator, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction,
|
||||
UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg,
|
||||
},
|
||||
builtins::{from_default_function, BUILTIN},
|
||||
expr::{FnStyle, TypedExpr, UntypedExpr},
|
||||
|
@ -918,6 +918,28 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
label: String,
|
||||
access_location: Span,
|
||||
) -> 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
|
||||
// of an imported module, so attempt to infer the container as a module access.
|
||||
// TODO: Remove this cloning
|
||||
|
|
|
@ -178,7 +178,7 @@ fn infer_definition(
|
|||
let params_length = params.len();
|
||||
|
||||
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, ¶ms);
|
||||
|
||||
|
@ -189,7 +189,7 @@ fn infer_definition(
|
|||
let temp_params = params.iter().cloned().chain(handler.arguments);
|
||||
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;
|
||||
handler.name = handler_name;
|
||||
|
|
Loading…
Reference in New Issue