From 0510ca58f756734fc66af11fa6d77b946fec239d Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 27 Aug 2024 17:16:15 +0200 Subject: [PATCH] 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. --- crates/aiken-lang/src/ast.rs | 13 ++- crates/aiken-lang/src/gen_uplc.rs | 4 +- .../src/parser/definition/validator.rs | 6 +- crates/aiken-lang/src/tests/check.rs | 97 +++++++++++++++++++ crates/aiken-lang/src/tipo/environment.rs | 48 +++++++-- crates/aiken-lang/src/tipo/error.rs | 16 +++ crates/aiken-lang/src/tipo/expr.rs | 26 ++++- crates/aiken-lang/src/tipo/infer.rs | 4 +- 8 files changed, 194 insertions(+), 20 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index fea4e2f0..2a32a25e 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -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 { pub fallback: Function, } +impl Validator { + pub fn handler_name(validator: &str, handler: &str) -> String { + format!("{}.{}", validator, handler) + } +} + impl TypedValidator { pub fn available_handler_names() -> Vec { 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, ) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index eb9d0d82..5e8e3757 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -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(), ) }); diff --git a/crates/aiken-lang/src/parser/definition/validator.rs b/crates/aiken-lang/src/parser/definition/validator.rs index 57e0d1bf..66e1720b 100644 --- a/crates/aiken-lang/src/parser/definition/validator.rs +++ b/crates/aiken-lang/src/parser/definition/validator.rs @@ -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 impl Parser { /// Top-level function definitions from the module pub module_functions: HashMap, + /// Top-level validator definitions from the module + pub module_validators: HashMap)>, + /// Top-level functions that have been inferred pub inferred_functions: HashMap, @@ -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 = 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, .. }) => { diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 905772b7..58f14d9c 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -1086,6 +1086,21 @@ The best thing to do from here is to remove it."#))] location: Span, available_purposes: Vec, }, + + #[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, + }, } 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, .. } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 816d1888..2e2e16e0 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -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 { + 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 diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 9441407d..a2afb5f8 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -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;