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;
|
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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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: (),
|
||||||
|
|
|
@ -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 { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
@ -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, .. }) => {
|
||||||
|
|
|
@ -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, .. }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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, ¶ms);
|
put_params_in_scope(&fallback_name, environment, ¶ms);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue