feat: typecheck validators
This commit is contained in:
parent
2e7fe191db
commit
a044c3580e
|
@ -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>>,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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?")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue