improved error messages for function definitions

This commit is contained in:
Folkert 2020-07-20 21:38:21 +02:00
parent 1d2251b064
commit e93c04a8ce
4 changed files with 267 additions and 79 deletions

View file

@ -307,7 +307,7 @@ pub fn constrain_expr(
}
Var(symbol) => Lookup(*symbol, expected, region),
Closure(fn_var, _symbol, _recursive, args, boxed) => {
let (loc_body_expr, ret_var) = &**boxed;
let (loc_body_expr, ret_var) = boxed.as_ref();
let mut state = PatternState {
headers: SendMap::default(),
vars: Vec::with_capacity(args.len()),
@ -1001,13 +1001,18 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
&mut pattern_state.headers,
);
let env = &Env {
home: env.home,
rigids: ftv,
};
let annotation_expected = FromAnnotation(
def.loc_pattern.clone(),
arity,
AnnotationSource::TypedBody {
region: annotation.region,
},
signature,
signature.clone(),
);
pattern_state.constraints.push(Eq(
@ -1017,15 +1022,118 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
annotation.region,
));
constrain_expr(
&Env {
home: env.home,
rigids: ftv,
},
def.loc_expr.region,
&def.loc_expr.value,
annotation_expected,
)
// when a def is annotated, and it's body is a closure, treat this
// as a named function (in elm terms) for error messages.
//
// This means we get errors like "the first argument of `f` is weird"
// instead of the more generic "something is wrong with the body of `f`"
match (&def.loc_expr.value, &signature) {
(
Closure(fn_var, _symbol, _recursive, args, boxed),
Type::Function(arg_types, _),
) if true => {
let expected = annotation_expected;
let region = def.loc_expr.region;
let (loc_body_expr, ret_var) = boxed.as_ref();
let mut state = PatternState {
headers: SendMap::default(),
vars: Vec::with_capacity(args.len()),
constraints: Vec::with_capacity(1),
};
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
let ret_var = *ret_var;
let ret_type = Type::Variable(ret_var);
vars.push(ret_var);
let it = args.iter().zip(arg_types.iter()).enumerate();
for (index, ((pattern_var, loc_pattern), loc_ann)) in it {
{
// ensure type matches the one in the annotation
let opt_label =
if let Pattern::Identifier(label) = def.loc_pattern.value {
Some(label)
} else {
None
};
let pattern_type: &Type = loc_ann;
let pattern_expected = PExpected::ForReason(
PReason::TypedArg {
index: Index::zero_based(index),
opt_name: opt_label,
},
pattern_type.clone(),
loc_pattern.region,
);
constrain_pattern(
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
&mut state,
);
}
{
// record the correct type in pattern_var
let pattern_type = Type::Variable(*pattern_var);
pattern_types.push(pattern_type.clone());
state.vars.push(*pattern_var);
state.constraints.push(Constraint::Eq(
pattern_type.clone(),
Expected::NoExpectation(loc_ann.clone()),
Category::Storage,
loc_pattern.region,
));
vars.push(*pattern_var);
}
}
let fn_type = Type::Function(pattern_types, Box::new(ret_type.clone()));
let body_type = NoExpectation(ret_type);
let ret_constraint =
constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type);
vars.push(*fn_var);
let defs_constraint = And(state.constraints);
exists(
vars,
And(vec![
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint,
ret_constraint,
})),
// "the closure's type is equal to expected type"
Eq(fn_type.clone(), expected, Category::Lambda, region),
// "fn_var is equal to the closure's type" - fn_var is used in code gen
Eq(
Type::Variable(*fn_var),
NoExpectation(fn_type),
Category::Storage,
region,
),
]),
)
}
_ => constrain_expr(
&env,
def.loc_expr.region,
&def.loc_expr.value,
annotation_expected,
),
}
}
None => constrain_expr(
env,