From 7aefa85de109971c7c73b4f0ffcaf0f21cfe12fb Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 30 Aug 2024 22:43:10 +0200 Subject: [PATCH] Handle importing validator handler into test module. --- crates/aiken-lang/src/tipo/error.rs | 16 +-- crates/aiken-lang/src/tipo/expr.rs | 146 ++++++++++++++++++++++++---- 2 files changed, 134 insertions(+), 28 deletions(-) diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 0abb9a04..2454daa0 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -844,7 +844,7 @@ Perhaps, try the following: } ))] UnknownModuleValue { - #[label("unknown import")] + #[label("not exported by {module_name}?")] location: Span, name: String, module_name: String, @@ -941,16 +941,17 @@ The best thing to do from here is to remove it."#))] }, #[error( - "I discovered an attempt to import a validator module: '{}'\n", + "I discovered an attempt to import a validator module in a library: '{}'\n", name.if_supports_color(Stdout, |s| s.purple()) )] #[diagnostic(code("illegal::import"))] #[diagnostic(help( - "If you are trying to share code defined in a validator then move it to a library module under {}", - "lib/".if_supports_color(Stdout, |s| s.purple())) - )] + "If you are trying to share code defined in a validator then move it to a library module under {}.\nIf, however, you are trying to import a validator for testing, make sure that your test module doesn't export any definition using the {} keyword.", + "lib/".if_supports_color(Stdout, |s| s.purple()), + "pub".if_supports_color(Stdout, |s| s.cyan()) + ))] ValidatorImported { - #[label("validator")] + #[label("imported validator")] location: Span, name: String, }, @@ -1074,7 +1075,8 @@ The best thing to do from here is to remove it."#))] #[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{}", + "When referring to a validator handler via record access, you must refer to one of the declared handlers{}{}", + if available_handlers.is_empty() { "." } else { ":\n" }, available_handlers .iter() .map(|p| format!("-> {}", p.if_supports_color(Stdout, |s| s.green()))) diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index a802ff25..8f8d0e26 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -21,7 +21,7 @@ use crate::{ expr::{FnStyle, TypedExpr, UntypedExpr}, format, parser::token::Base, - tipo::{fields::FieldMap, DefaultFunction, PatternConstructor, TypeVar}, + tipo::{fields::FieldMap, DefaultFunction, ModuleKind, PatternConstructor, TypeVar}, IdGenerator, }; use std::{ @@ -920,32 +920,136 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } + fn infer_validator_handler_access( + &mut self, + container: &UntypedExpr, + label: &str, + access_location: Span, + ) -> Option> { + match container { + UntypedExpr::Var { name, location } => { + if let Some((_, available_handlers)) = self + .environment + .module_validators + .get(name.as_str()) + .cloned() + { + return Some( + self.infer_var( + TypedValidator::handler_name(name.as_str(), label), + *location, + ) + .map_err(|err| match err { + Error::UnknownVariable { .. } => Error::UnknownValidatorHandler { + location: access_location.map(|_start, end| (location.end, end)), + available_handlers, + }, + _ => err, + }), + ); + } + } + UntypedExpr::FieldAccess { + label: name, + container, + location, + } => { + if let UntypedExpr::Var { + name: ref module, + location: module_location, + } = container.as_ref() + { + match self.environment.imported_modules.get(module) { + Some((_, info)) if info.kind == ModuleKind::Validator => { + let has_validator = info + .values + .keys() + .any(|k| k.split(".").next() == Some(name)); + + let value_constructors = info + .values + .keys() + .map(|k| k.split(".").next().unwrap_or(k).to_string()) + .collect::>(); + + return Some( + self.infer_module_access( + module, + TypedValidator::handler_name(name, label), + location, + access_location, + ) + .and_then(|access| { + let export_values = self + .environment + .module_values + .iter() + .any(|(_, constructor)| constructor.public); + let export_functions = self + .environment + .module_functions + .iter() + .any(|(_, function)| function.public); + let export_validators = + !self.environment.module_validators.is_empty(); + + if export_values || export_functions || export_validators { + return Err(Error::ValidatorImported { + location: location + .map(|_start, end| (module_location.end, end)), + name: name.to_string(), + }); + } + + Ok(access) + }) + .map_err(|err| match err { + Error::UnknownModuleValue { .. } => { + if has_validator { + Error::UnknownValidatorHandler { + location: access_location + .map(|_start, end| (location.end, end)), + available_handlers: Vec::new(), + } + } else { + Error::UnknownModuleValue { + location: location + .map(|_start, end| (module_location.end, end)), + name: name.to_string(), + module_name: module.to_string(), + value_constructors, + } + } + } + _ => err, + }), + ); + } + _ => (), + } + } + } + _ => (), + } + + None + } + fn infer_field_access( &mut self, container: UntypedExpr, 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, - }); - } + // NOTE: Before we actually resolve the field access, we try to short-circuit the access if + // we detect a validator handler access. This can happen in two cases: + // + // - Either it is a direct access from a validator in the same module. + // - Or it is an attempt to pull a handler from an imported validator module. + if let Some(shortcircuit) = + self.infer_validator_handler_access(&container, &label, access_location) + { + return shortcircuit; } // Attempt to infer the container as a record access. If that fails, we may be shadowing the name