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;