From a044c3580eff1d34c0ac05c2f5dc98a88154e2e2 Mon Sep 17 00:00:00 2001 From: rvcas Date: Tue, 14 Feb 2023 12:09:12 -0500 Subject: [PATCH] feat: typecheck validators --- crates/aiken-lang/src/ast.rs | 2 +- crates/aiken-lang/src/format.rs | 14 ++--- crates/aiken-lang/src/parser.rs | 6 +- crates/aiken-lang/src/tipo/environment.rs | 69 ++++++++++++++++++++++- crates/aiken-lang/src/tipo/error.rs | 8 +++ crates/aiken-lang/src/tipo/infer.rs | 50 +++++++++++++++- 6 files changed, 133 insertions(+), 16 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 9dacea2f..5fb86d7b 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -265,7 +265,7 @@ pub type UntypedValidator = Validator<(), UntypedExpr>; pub struct Validator { pub doc: Option, pub end_position: usize, - pub function: Function, + pub fun: Function, pub location: Span, pub params: Vec>, } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 612333d8..a548a1ec 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -238,7 +238,7 @@ impl<'comments> Formatter<'comments> { Definition::Validator(Validator { end_position, - function, + fun: function, params, .. }) => self.definition_validator(params, function, *end_position), @@ -560,26 +560,26 @@ impl<'comments> Formatter<'comments> { fn definition_validator<'a>( &mut self, params: &'a [UntypedArg], - function: &'a UntypedFunction, + fun: &'a UntypedFunction, end_position: usize, ) -> Document<'a> { // Fn and args let head = "fn".to_doc().append(wrap_args( - function.arguments.iter().map(|e| (self.fn_arg(e), false)), + fun.arguments.iter().map(|e| (self.fn_arg(e), false)), )); // Add return annotation - let head = match &function.return_annotation { + let head = match &fun.return_annotation { Some(anno) => head.append(" -> ").append(self.annotation(anno)), None => head, } .group(); // Format body - let body = self.expr(&function.body); + let body = self.expr(&fun.body); // Add any trailing comments - let body = match printed_comments(self.pop_comments(function.end_position), false) { + let body = match printed_comments(self.pop_comments(fun.end_position), false) { Some(comments) => body.append(line()).append(comments), None => body, }; @@ -588,7 +588,7 @@ impl<'comments> Formatter<'comments> { let v_head = "validator" .to_doc() .append(" ") - .append(function.name.as_str()) + .append(fun.name.as_str()) .append(wrap_args(params.iter().map(|e| (self.fn_arg(e), false)))); // Stick it all together diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 17d8ad0f..ad44e384 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -284,12 +284,12 @@ pub fn validator_parser() -> impl Parser Environment<'a> { | Definition::DataType { .. } | Definition::Use { .. } | Definition::Test { .. } + | Definition::Validator { .. } | Definition::ModuleConstant { .. }) => definition, } } @@ -1070,11 +1071,73 @@ impl<'a> Environment<'a> { tipo, ); - if !public && (kind.is_lib() || !VALIDATOR_NAMES.contains(&name.as_str())) { + if !public && kind.is_lib() { self.init_usage(name.clone(), EntityKind::PrivateFunction, *location); } } + Definition::Validator(Validator { + fun, + location, + params, + .. + }) if kind.is_validator() => { + assert_unique_value_name(names, &fun.name, location)?; + + // Create the field map so we can reorder labels for usage of this function + let mut field_map = FieldMap::new(fun.arguments.len() + params.len(), true); + + // Chain together extra params and function.arguments + for (i, arg) in params.iter().chain(fun.arguments.iter()).enumerate() { + field_map.insert(arg.arg_name.get_label().clone(), i, &arg.location)?; + } + + let field_map = field_map.into_option(); + + // Construct type from annotations + let mut hydrator = Hydrator::new(); + + hydrator.permit_holes(false); + + let mut arg_types = Vec::new(); + + for arg in params.iter().chain(fun.arguments.iter()) { + let tipo = hydrator.type_from_option_annotation(&arg.annotation, self)?; + + arg_types.push(tipo); + } + + let return_type = + hydrator.type_from_option_annotation(&fun.return_annotation, self)?; + + let tipo = function(arg_types, return_type); + + // Keep track of which types we create from annotations so we can know + // which generic types not to instantiate later when performing + // inference of the function body. + hydrators.insert(fun.name.clone(), hydrator); + + // Insert the function into the environment + self.insert_variable( + fun.name.clone(), + ValueConstructorVariant::ModuleFn { + name: fun.name.clone(), + field_map, + module: module_name.to_owned(), + arity: params.len() + fun.arguments.len(), + location: fun.location, + builtin: None, + }, + tipo, + ); + } + + Definition::Validator(Validator { location, .. }) => { + self.warnings.push(Warning::ValidatorInLibraryModule { + location: *location, + }) + } + Definition::Test(Function { name, location, .. }) => { assert_unique_value_name(names, name, location)?; hydrators.insert(name.clone(), Hydrator::new()); diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index fdae1c3e..394de649 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -1209,6 +1209,14 @@ pub enum Warning { location: Span, name: String, }, + + #[error("I came across a validator in a {} module\nwhich means I'm going to ignore it.\n", "lib/".purple())] + #[diagnostic(help( + "No big deal, but you might want to move it to a\n{} module or remove it to get rid of that warning.", + "validators/".purple() + ))] + #[diagnostic(code("unused::validator"))] + ValidatorInLibraryModule { location: Span }, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index 447aad18..8b342e64 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -4,7 +4,7 @@ use crate::{ ast::{ DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition, - UntypedModule, Use, + UntypedModule, Use, Validator, }, builtins, builtins::function, @@ -72,6 +72,8 @@ impl UntypedModule { for def in self.definitions().cloned() { match def { Definition::ModuleConstant { .. } => consts.push(def), + Definition::Validator { .. } if kind.is_validator() => not_consts.push(def), + Definition::Validator { .. } => (), Definition::Fn { .. } | Definition::Test { .. } | Definition::TypeAlias { .. } @@ -248,14 +250,58 @@ fn infer_definition( })) } + Definition::Validator(Validator { + doc, + location, + end_position, + mut fun, + mut params, + }) => { + let params_length = params.len(); + params.append(&mut fun.arguments); + fun.arguments = params; + + if let Definition::Fn(mut typed_fun) = infer_definition( + Definition::Fn(fun), + module_name, + hydrators, + environment, + kind, + )? { + // Do we want to do this here? + // or should we remove this and keep the later check + // the later check has a much more specific error message + // we may want to not do this here + environment.unify( + typed_fun.return_type.clone(), + builtins::bool(), + typed_fun.location, + false, + )?; + + let typed_params = typed_fun.arguments.drain(0..params_length).collect(); + + Ok(Definition::Validator(Validator { + doc, + end_position, + fun: typed_fun, + location, + params: typed_params, + })) + } else { + unreachable!("validator definition inferred as something other than a function?") + } + } + Definition::Test(f) => { if let Definition::Fn(f) = infer_definition(Definition::Fn(f), module_name, hydrators, environment, kind)? { environment.unify(f.return_type.clone(), builtins::bool(), f.location, false)?; + Ok(Definition::Test(f)) } else { - unreachable!("test defintion inferred as something else than a function?") + unreachable!("test definition inferred as something other than a function?") } }