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 doc: Option<String>,
pub end_position: usize,
pub function: Function<T, Expr>,
pub fun: Function<T, Expr>,
pub location: Span,
pub params: Vec<Arg<T>>,
}

View File

@ -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

View File

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

View File

@ -10,11 +10,11 @@ use crate::{
ast::{
Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, Pattern,
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition,
UnqualifiedImport, UntypedDefinition, Use, PIPE_VARIABLE,
UnqualifiedImport, UntypedDefinition, Use, Validator, PIPE_VARIABLE,
},
builtins::{self, function, generic_var, tuple, unbound_var},
tipo::fields::FieldMap,
IdGenerator, VALIDATOR_NAMES,
IdGenerator,
};
use super::{
@ -247,6 +247,7 @@ impl<'a> 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());

View File

@ -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)]

View File

@ -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?")
}
}