feat: typecheck validators

This commit is contained in:
rvcas 2023-02-14 12:09:12 -05:00 committed by Lucas
parent 2e7fe191db
commit a044c3580e
6 changed files with 133 additions and 16 deletions

View File

@ -265,7 +265,7 @@ pub type UntypedValidator = Validator<(), UntypedExpr>;
pub struct Validator<T, Expr> { pub struct Validator<T, Expr> {
pub doc: Option<String>, pub doc: Option<String>,
pub end_position: usize, pub end_position: usize,
pub function: Function<T, Expr>, pub fun: Function<T, Expr>,
pub location: Span, pub location: Span,
pub params: Vec<Arg<T>>, pub params: Vec<Arg<T>>,
} }

View File

@ -238,7 +238,7 @@ impl<'comments> Formatter<'comments> {
Definition::Validator(Validator { Definition::Validator(Validator {
end_position, end_position,
function, fun: function,
params, params,
.. ..
}) => self.definition_validator(params, function, *end_position), }) => self.definition_validator(params, function, *end_position),
@ -560,26 +560,26 @@ impl<'comments> Formatter<'comments> {
fn definition_validator<'a>( fn definition_validator<'a>(
&mut self, &mut self,
params: &'a [UntypedArg], params: &'a [UntypedArg],
function: &'a UntypedFunction, fun: &'a UntypedFunction,
end_position: usize, end_position: usize,
) -> Document<'a> { ) -> Document<'a> {
// Fn and args // Fn and args
let head = "fn".to_doc().append(wrap_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 // Add return annotation
let head = match &function.return_annotation { let head = match &fun.return_annotation {
Some(anno) => head.append(" -> ").append(self.annotation(anno)), Some(anno) => head.append(" -> ").append(self.annotation(anno)),
None => head, None => head,
} }
.group(); .group();
// Format body // Format body
let body = self.expr(&function.body); let body = self.expr(&fun.body);
// Add any trailing comments // 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), Some(comments) => body.append(line()).append(comments),
None => body, None => body,
}; };
@ -588,7 +588,7 @@ impl<'comments> Formatter<'comments> {
let v_head = "validator" let v_head = "validator"
.to_doc() .to_doc()
.append(" ") .append(" ")
.append(function.name.as_str()) .append(fun.name.as_str())
.append(wrap_args(params.iter().map(|e| (self.fn_arg(e), false)))); .append(wrap_args(params.iter().map(|e| (self.fn_arg(e), false))));
// Stick it all together // Stick it all together

View File

@ -284,12 +284,12 @@ pub fn validator_parser() -> impl Parser<Token, ast::UntypedDefinition, Error =
) )
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
) )
.map_with_span(|((name, (params, params_span)), mut function), span| { .map_with_span(|((name, (params, params_span)), mut fun), span| {
function.name = name; fun.name = name;
ast::UntypedDefinition::Validator(ast::Validator { ast::UntypedDefinition::Validator(ast::Validator {
doc: None, doc: None,
function, fun,
location: Span { location: Span {
start: span.start, start: span.start,
end: params_span.end, end: params_span.end,

View File

@ -10,11 +10,11 @@ use crate::{
ast::{ ast::{
Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, Pattern, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, Pattern,
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition,
UnqualifiedImport, UntypedDefinition, Use, PIPE_VARIABLE, UnqualifiedImport, UntypedDefinition, Use, Validator, PIPE_VARIABLE,
}, },
builtins::{self, function, generic_var, tuple, unbound_var}, builtins::{self, function, generic_var, tuple, unbound_var},
tipo::fields::FieldMap, tipo::fields::FieldMap,
IdGenerator, VALIDATOR_NAMES, IdGenerator,
}; };
use super::{ use super::{
@ -247,6 +247,7 @@ impl<'a> Environment<'a> {
| Definition::DataType { .. } | Definition::DataType { .. }
| Definition::Use { .. } | Definition::Use { .. }
| Definition::Test { .. } | Definition::Test { .. }
| Definition::Validator { .. }
| Definition::ModuleConstant { .. }) => definition, | Definition::ModuleConstant { .. }) => definition,
} }
} }
@ -1070,11 +1071,73 @@ impl<'a> Environment<'a> {
tipo, 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); 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, .. }) => { Definition::Test(Function { name, location, .. }) => {
assert_unique_value_name(names, name, location)?; assert_unique_value_name(names, name, location)?;
hydrators.insert(name.clone(), Hydrator::new()); hydrators.insert(name.clone(), Hydrator::new());

View File

@ -1209,6 +1209,14 @@ pub enum Warning {
location: Span, location: Span,
name: String, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]

View File

@ -4,7 +4,7 @@ use crate::{
ast::{ ast::{
DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor, DataType, Definition, Function, Layer, ModuleConstant, ModuleKind, RecordConstructor,
RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedModule, UntypedDefinition,
UntypedModule, Use, UntypedModule, Use, Validator,
}, },
builtins, builtins,
builtins::function, builtins::function,
@ -72,6 +72,8 @@ impl UntypedModule {
for def in self.definitions().cloned() { for def in self.definitions().cloned() {
match def { match def {
Definition::ModuleConstant { .. } => consts.push(def), Definition::ModuleConstant { .. } => consts.push(def),
Definition::Validator { .. } if kind.is_validator() => not_consts.push(def),
Definition::Validator { .. } => (),
Definition::Fn { .. } Definition::Fn { .. }
| Definition::Test { .. } | Definition::Test { .. }
| Definition::TypeAlias { .. } | 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) => { Definition::Test(f) => {
if let Definition::Fn(f) = if let Definition::Fn(f) =
infer_definition(Definition::Fn(f), module_name, hydrators, environment, kind)? infer_definition(Definition::Fn(f), module_name, hydrators, environment, kind)?
{ {
environment.unify(f.return_type.clone(), builtins::bool(), f.location, false)?; environment.unify(f.return_type.clone(), builtins::bool(), f.location, false)?;
Ok(Definition::Test(f)) Ok(Definition::Test(f))
} else { } else {
unreachable!("test defintion inferred as something else than a function?") unreachable!("test definition inferred as something other than a function?")
} }
} }