mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-23 22:45:14 +00:00
4250 lines
153 KiB
Rust
4250 lines
153 KiB
Rust
#![allow(clippy::too_many_arguments)]
|
|
|
|
use std::ops::Range;
|
|
|
|
use crate::builtins::{
|
|
empty_list_type, float_literal, int_literal, list_type, num_literal, single_quote_literal,
|
|
};
|
|
use crate::pattern::{constrain_pattern, PatternState};
|
|
use roc_can::annotation::IntroducedVariables;
|
|
use roc_can::constraint::{
|
|
Constraint, Constraints, ExpectedTypeIndex, Generalizable, OpportunisticResolve, TypeOrVar,
|
|
};
|
|
use roc_can::def::Def;
|
|
use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext};
|
|
use roc_can::expected::Expected::{self, *};
|
|
use roc_can::expected::PExpected;
|
|
use roc_can::expr::Expr::{self, *};
|
|
use roc_can::expr::{
|
|
AccessorData, AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef,
|
|
ExpectLookup, Field, FunctionDef, OpaqueWrapFunctionData, WhenBranch,
|
|
};
|
|
use roc_can::pattern::Pattern;
|
|
use roc_can::traverse::symbols_introduced_from_pattern;
|
|
use roc_collections::all::{HumanIndex, MutMap, SendMap};
|
|
use roc_collections::soa::{Index, Slice};
|
|
use roc_collections::VecMap;
|
|
use roc_module::ident::Lowercase;
|
|
use roc_module::symbol::{ModuleId, Symbol};
|
|
use roc_region::all::{Loc, Region};
|
|
use roc_types::subs::{IllegalCycleMark, Variable};
|
|
use roc_types::types::Type::{self, *};
|
|
use roc_types::types::{
|
|
AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField,
|
|
TypeExtension, TypeTag, Types,
|
|
};
|
|
|
|
/// This is for constraining Defs
|
|
#[derive(Default, Debug)]
|
|
pub struct Info {
|
|
pub vars: Vec<Variable>,
|
|
pub constraints: Vec<Constraint>,
|
|
pub def_types: VecMap<Symbol, Loc<TypeOrVar>>,
|
|
}
|
|
|
|
impl Info {
|
|
pub fn with_capacity(capacity: usize) -> Self {
|
|
Info {
|
|
vars: Vec::with_capacity(capacity),
|
|
constraints: Vec::with_capacity(capacity),
|
|
def_types: VecMap::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Env {
|
|
/// for example `a` in the annotation `identity : a -> a`, we add it to this
|
|
/// map so that expressions within that annotation can share these vars.
|
|
pub rigids: MutMap<Lowercase, Variable>,
|
|
pub resolutions_to_make: Vec<OpportunisticResolve>,
|
|
pub home: ModuleId,
|
|
}
|
|
|
|
fn constrain_untyped_args(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
|
|
closure_type: Type,
|
|
return_type: Type,
|
|
) -> (Vec<Variable>, PatternState, Type) {
|
|
let mut vars = Vec::with_capacity(arguments.len());
|
|
let mut pattern_types = Vec::with_capacity(arguments.len());
|
|
|
|
let mut pattern_state = PatternState::default();
|
|
|
|
for (pattern_var, annotated_mark, loc_pattern) in arguments {
|
|
// Untyped args don't need exhaustiveness checking because they are the source of truth!
|
|
let _ = annotated_mark;
|
|
|
|
let pattern_type = Variable(*pattern_var);
|
|
let pattern_type_index = constraints.push_variable(*pattern_var);
|
|
let pattern_expected =
|
|
constraints.push_pat_expected_type(PExpected::NoExpectation(pattern_type_index));
|
|
|
|
pattern_types.push(pattern_type);
|
|
|
|
constrain_pattern(
|
|
types,
|
|
constraints,
|
|
env,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
pattern_expected,
|
|
&mut pattern_state,
|
|
);
|
|
|
|
vars.push(*pattern_var);
|
|
}
|
|
|
|
let function_type =
|
|
Type::Function(pattern_types, Box::new(closure_type), Box::new(return_type));
|
|
|
|
(vars, pattern_state, function_type)
|
|
}
|
|
|
|
fn constrain_untyped_closure(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
region: Region,
|
|
expected: ExpectedTypeIndex,
|
|
|
|
fn_var: Variable,
|
|
closure_var: Variable,
|
|
ret_var: Variable,
|
|
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
|
|
loc_body_expr: &Loc<Expr>,
|
|
captured_symbols: &[(Symbol, Variable)],
|
|
name: Symbol,
|
|
) -> Constraint {
|
|
let closure_type = Type::Variable(closure_var);
|
|
let return_type = Type::Variable(ret_var);
|
|
let return_type_index = constraints.push_variable(ret_var);
|
|
let (mut vars, pattern_state, function_type) = constrain_untyped_args(
|
|
types,
|
|
constraints,
|
|
env,
|
|
arguments,
|
|
closure_type,
|
|
return_type,
|
|
);
|
|
|
|
vars.push(ret_var);
|
|
vars.push(closure_var);
|
|
vars.push(fn_var);
|
|
|
|
let body_type = constraints.push_expected_type(NoExpectation(return_type_index));
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_body_expr.region,
|
|
&loc_body_expr.value,
|
|
body_type,
|
|
);
|
|
|
|
// make sure the captured symbols are sorted!
|
|
debug_assert_eq!(captured_symbols.to_vec(), {
|
|
let mut copy = captured_symbols.to_vec();
|
|
copy.sort();
|
|
copy
|
|
});
|
|
|
|
let closure_constraint = constrain_closure_size(
|
|
types,
|
|
constraints,
|
|
name,
|
|
region,
|
|
fn_var,
|
|
captured_symbols,
|
|
closure_var,
|
|
&mut vars,
|
|
);
|
|
|
|
let pattern_state_constraints = constraints.and_constraint(pattern_state.constraints);
|
|
|
|
let function_type = {
|
|
let typ = types.from_old_type(&function_type);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
let cons = [
|
|
constraints.let_constraint(
|
|
[],
|
|
pattern_state.vars,
|
|
pattern_state.headers,
|
|
pattern_state_constraints,
|
|
ret_constraint,
|
|
Generalizable(true),
|
|
),
|
|
constraints.equal_types_with_storage(
|
|
function_type,
|
|
expected,
|
|
Category::Lambda,
|
|
region,
|
|
fn_var,
|
|
),
|
|
closure_constraint,
|
|
];
|
|
|
|
constraints.exists_many(vars, cons)
|
|
}
|
|
|
|
pub fn constrain_expr(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
region: Region,
|
|
expr: &Expr,
|
|
expected: ExpectedTypeIndex,
|
|
) -> Constraint {
|
|
match expr {
|
|
&Int(var, precision, _, _, bound) => {
|
|
int_literal(types, constraints, var, precision, expected, region, bound)
|
|
}
|
|
&Num(var, _, _, bound) => num_literal(types, constraints, var, expected, region, bound),
|
|
&Float(var, precision, _, _, bound) => {
|
|
float_literal(types, constraints, var, precision, expected, region, bound)
|
|
}
|
|
EmptyRecord => constrain_empty_record(types, constraints, region, expected),
|
|
Expr::Record { record_var, fields } => {
|
|
if fields.is_empty() {
|
|
constrain_empty_record(types, constraints, region, expected)
|
|
} else {
|
|
let mut field_types = SendMap::default();
|
|
let mut field_vars = Vec::with_capacity(fields.len());
|
|
|
|
// Constraints need capacity for each field
|
|
// + 1 for the record itself + 1 for record var
|
|
let mut rec_constraints = Vec::with_capacity(2 + fields.len());
|
|
|
|
for (label, field) in fields {
|
|
let field_var = field.var;
|
|
let loc_field_expr = &field.loc_expr;
|
|
let (field_type, field_con) =
|
|
constrain_field(types, constraints, env, field_var, loc_field_expr);
|
|
|
|
field_vars.push(field_var);
|
|
field_types.insert(label.clone(), RecordField::Required(field_type));
|
|
|
|
rec_constraints.push(field_con);
|
|
}
|
|
|
|
let record_type = {
|
|
let typ =
|
|
types.from_old_type(&Type::Record(field_types, TypeExtension::Closed));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
let record_con = constraints.equal_types_with_storage(
|
|
record_type,
|
|
expected,
|
|
Category::Record,
|
|
region,
|
|
*record_var,
|
|
);
|
|
|
|
rec_constraints.push(record_con);
|
|
field_vars.push(*record_var);
|
|
|
|
let and_constraint = constraints.and_constraint(rec_constraints);
|
|
constraints.exists(field_vars, and_constraint)
|
|
}
|
|
}
|
|
Update {
|
|
record_var,
|
|
ext_var,
|
|
symbol,
|
|
updates,
|
|
} => {
|
|
let mut fields: SendMap<Lowercase, RecordField<Type>> = SendMap::default();
|
|
let mut vars = Vec::with_capacity(updates.len() + 2);
|
|
let mut cons = Vec::with_capacity(updates.len() + 1);
|
|
for (field_name, Field { var, loc_expr, .. }) in updates.clone() {
|
|
let (var, tipe, con) = constrain_field_update(
|
|
types,
|
|
constraints,
|
|
env,
|
|
var,
|
|
loc_expr.region,
|
|
field_name.clone(),
|
|
&loc_expr,
|
|
);
|
|
fields.insert(field_name, RecordField::Required(tipe));
|
|
vars.push(var);
|
|
cons.push(con);
|
|
}
|
|
|
|
let fields_type = {
|
|
let typ = types.from_old_type(&Type::Record(
|
|
fields,
|
|
TypeExtension::from_type(Type::Variable(*ext_var)),
|
|
));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let record_type = {
|
|
let typ = types.from_old_type(&Type::Variable(*record_var));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
// NOTE from elm compiler: fields_type is separate so that Error propagates better
|
|
let fields_type_expected = constraints.push_expected_type(NoExpectation(fields_type));
|
|
let fields_con = constraints.equal_types_var(
|
|
*record_var,
|
|
fields_type_expected,
|
|
Category::Record,
|
|
region,
|
|
);
|
|
let expected_record = expected;
|
|
let record_con =
|
|
constraints.equal_types_var(*record_var, expected_record, Category::Record, region);
|
|
|
|
vars.push(*record_var);
|
|
vars.push(*ext_var);
|
|
|
|
let record_being_updated_expectation = constraints.push_expected_type(ForReason(
|
|
Reason::RecordUpdateKeys(
|
|
*symbol,
|
|
updates
|
|
.iter()
|
|
.map(|(key, field)| (key.clone(), field.region))
|
|
.collect(),
|
|
),
|
|
record_type,
|
|
region,
|
|
));
|
|
|
|
let con = constraints.lookup(*symbol, record_being_updated_expectation, region);
|
|
|
|
// ensure constraints are solved in this order, gives better errors
|
|
cons.insert(0, fields_con);
|
|
cons.insert(1, con);
|
|
cons.insert(2, record_con);
|
|
|
|
let and_constraint = constraints.and_constraint(cons);
|
|
constraints.exists(vars, and_constraint)
|
|
}
|
|
Str(_) => {
|
|
let str_index = constraints.push_type(types, Types::STR);
|
|
let expected_index = expected;
|
|
constraints.equal_types(str_index, expected_index, Category::Str, region)
|
|
}
|
|
SingleQuote(num_var, precision_var, _, bound) => single_quote_literal(
|
|
types,
|
|
constraints,
|
|
*num_var,
|
|
*precision_var,
|
|
expected,
|
|
region,
|
|
*bound,
|
|
),
|
|
List {
|
|
elem_var,
|
|
loc_elems,
|
|
} => {
|
|
if loc_elems.is_empty() {
|
|
let elem_type_index = {
|
|
let typ = types.from_old_type(&empty_list_type(*elem_var));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let eq = constraints.equal_types(elem_type_index, expected, Category::List, region);
|
|
constraints.exists(vec![*elem_var], eq)
|
|
} else {
|
|
let list_elem_type = Type::Variable(*elem_var);
|
|
let list_elem_type_index = constraints.push_variable(*elem_var);
|
|
let mut list_constraints = Vec::with_capacity(1 + loc_elems.len());
|
|
|
|
for (index, loc_elem) in loc_elems.iter().enumerate() {
|
|
let elem_expected = constraints.push_expected_type(ForReason(
|
|
Reason::ElemInList {
|
|
index: HumanIndex::zero_based(index),
|
|
},
|
|
list_elem_type_index,
|
|
loc_elem.region,
|
|
));
|
|
let constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_elem.region,
|
|
&loc_elem.value,
|
|
elem_expected,
|
|
);
|
|
|
|
list_constraints.push(constraint);
|
|
}
|
|
|
|
let elem_type_index = {
|
|
let typ = types.from_old_type(&list_type(list_elem_type));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
list_constraints.push(constraints.equal_types(
|
|
elem_type_index,
|
|
expected,
|
|
Category::List,
|
|
region,
|
|
));
|
|
|
|
let and_constraint = constraints.and_constraint(list_constraints);
|
|
constraints.exists([*elem_var], and_constraint)
|
|
}
|
|
}
|
|
Call(boxed, loc_args, called_via) => {
|
|
let (fn_var, loc_fn, closure_var, ret_var) = &**boxed;
|
|
// The expression that evaluates to the function being called, e.g. `foo` in
|
|
// (foo) bar baz
|
|
let opt_symbol = if let Var(symbol, _) | AbilityMember(symbol, _, _) = loc_fn.value {
|
|
Some(symbol)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let fn_type_index = constraints.push_variable(*fn_var);
|
|
let fn_region = loc_fn.region;
|
|
let fn_expected = constraints.push_expected_type(NoExpectation(fn_type_index));
|
|
|
|
let fn_reason = Reason::FnCall {
|
|
name: opt_symbol,
|
|
arity: loc_args.len() as u8,
|
|
};
|
|
|
|
let fn_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_fn.region,
|
|
&loc_fn.value,
|
|
fn_expected,
|
|
);
|
|
|
|
// The function's return type
|
|
let ret_type = Variable(*ret_var);
|
|
|
|
// type of values captured in the closure
|
|
let closure_type = Variable(*closure_var);
|
|
|
|
// This will be used in the occurs check
|
|
let mut vars = Vec::with_capacity(2 + loc_args.len());
|
|
|
|
vars.push(*fn_var);
|
|
vars.push(*ret_var);
|
|
vars.push(*closure_var);
|
|
|
|
let mut arg_types = Vec::with_capacity(loc_args.len());
|
|
let mut arg_cons = Vec::with_capacity(loc_args.len());
|
|
|
|
for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() {
|
|
let region = loc_arg.region;
|
|
let arg_type = Variable(*arg_var);
|
|
let arg_type_index = constraints.push_variable(*arg_var);
|
|
|
|
let reason = Reason::FnArg {
|
|
name: opt_symbol,
|
|
arg_index: HumanIndex::zero_based(index),
|
|
};
|
|
let expected_arg =
|
|
constraints.push_expected_type(ForReason(reason, arg_type_index, region));
|
|
let arg_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_arg.region,
|
|
&loc_arg.value,
|
|
expected_arg,
|
|
);
|
|
|
|
vars.push(*arg_var);
|
|
arg_types.push(arg_type);
|
|
arg_cons.push(arg_con);
|
|
}
|
|
|
|
let expected_fn_index = {
|
|
let arguments = types.from_old_type_slice(arg_types.iter());
|
|
let lambda_set = types.from_old_type(&closure_type);
|
|
let ret = types.from_old_type(&ret_type);
|
|
let typ = types.function(arguments, lambda_set, ret);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let expected_fn_type =
|
|
constraints.push_expected_type(ForReason(fn_reason, expected_fn_index, region));
|
|
|
|
let expected_final_type = expected;
|
|
|
|
let category = Category::CallResult(opt_symbol, *called_via);
|
|
|
|
let and_cons = [
|
|
fn_con,
|
|
constraints.equal_types_var(*fn_var, expected_fn_type, category.clone(), fn_region),
|
|
constraints.and_constraint(arg_cons),
|
|
constraints.equal_types_var(*ret_var, expected_final_type, category, region),
|
|
];
|
|
|
|
let and_constraint = constraints.and_constraint(and_cons);
|
|
constraints.exists(vars, and_constraint)
|
|
}
|
|
Expr::Crash { msg, ret_var } => {
|
|
let str_index = constraints.push_type(types, Types::STR);
|
|
let expected_msg = constraints.push_expected_type(Expected::ForReason(
|
|
Reason::CrashArg,
|
|
str_index,
|
|
msg.region,
|
|
));
|
|
|
|
let msg_is_str = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
msg.region,
|
|
&msg.value,
|
|
expected_msg,
|
|
);
|
|
let magic = constraints.equal_types_var(*ret_var, expected, Category::Crash, region);
|
|
|
|
let and = constraints.and_constraint([msg_is_str, magic]);
|
|
|
|
constraints.exists([*ret_var], and)
|
|
}
|
|
Var(symbol, variable) => {
|
|
// Save the expectation in the variable, then lookup the symbol's type in the environment
|
|
let expected_type = *constraints[expected].get_type_ref();
|
|
let store_expected = constraints.store(expected_type, *variable, file!(), line!());
|
|
|
|
let lookup_constr = constraints.lookup(*symbol, expected, region);
|
|
|
|
constraints.and_constraint([store_expected, lookup_constr])
|
|
}
|
|
&AbilityMember(symbol, specialization_id, specialization_var) => {
|
|
// Save the expectation in the `specialization_var` so we know what to specialize, then
|
|
// lookup the member in the environment.
|
|
let expected_type = *constraints[expected].get_type_ref();
|
|
let store_expected =
|
|
constraints.store(expected_type, specialization_var, file!(), line!());
|
|
|
|
let stored_index = constraints.push_variable(specialization_var);
|
|
let stored_specialization_var =
|
|
constraints.push_expected_type(Expected::NoExpectation(stored_index));
|
|
|
|
let lookup_constr = constraints.lookup(symbol, stored_specialization_var, region);
|
|
|
|
// Make sure we attempt to resolve the specialization, if we can.
|
|
if let Some(specialization_id) = specialization_id {
|
|
env.resolutions_to_make.push(OpportunisticResolve {
|
|
specialization_variable: specialization_var,
|
|
member: symbol,
|
|
specialization_id,
|
|
});
|
|
}
|
|
|
|
constraints.and_constraint([store_expected, lookup_constr])
|
|
}
|
|
Closure(ClosureData {
|
|
function_type: fn_var,
|
|
closure_type: closure_var,
|
|
return_type: ret_var,
|
|
arguments,
|
|
loc_body: boxed,
|
|
captured_symbols,
|
|
name,
|
|
..
|
|
}) => {
|
|
// shared code with function defs without an annotation
|
|
constrain_untyped_closure(
|
|
types,
|
|
constraints,
|
|
env,
|
|
region,
|
|
expected,
|
|
*fn_var,
|
|
*closure_var,
|
|
*ret_var,
|
|
arguments,
|
|
boxed,
|
|
captured_symbols,
|
|
*name,
|
|
)
|
|
}
|
|
|
|
Expect {
|
|
loc_condition,
|
|
loc_continuation,
|
|
lookups_in_cond,
|
|
} => {
|
|
let expected_bool = {
|
|
let bool_type = constraints.push_variable(Variable::BOOL);
|
|
constraints.push_expected_type(Expected::ForReason(
|
|
Reason::ExpectCondition,
|
|
bool_type,
|
|
loc_condition.region,
|
|
))
|
|
};
|
|
|
|
let cond_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_condition.region,
|
|
&loc_condition.value,
|
|
expected_bool,
|
|
);
|
|
|
|
let continuation_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_continuation.region,
|
|
&loc_continuation.value,
|
|
expected,
|
|
);
|
|
|
|
// + 2 for cond_con and continuation_con
|
|
let mut all_constraints = Vec::with_capacity(lookups_in_cond.len() + 2);
|
|
|
|
all_constraints.push(cond_con);
|
|
all_constraints.push(continuation_con);
|
|
|
|
let mut vars = Vec::with_capacity(lookups_in_cond.len());
|
|
|
|
for ExpectLookup {
|
|
symbol,
|
|
var,
|
|
ability_info: _,
|
|
} in lookups_in_cond.iter()
|
|
{
|
|
vars.push(*var);
|
|
|
|
let var_index = constraints.push_variable(*var);
|
|
let store_into = constraints.push_expected_type(NoExpectation(var_index));
|
|
|
|
all_constraints.push(constraints.lookup(*symbol, store_into, Region::zero()));
|
|
}
|
|
|
|
constraints.exists_many(vars, all_constraints)
|
|
}
|
|
|
|
ExpectFx {
|
|
loc_condition,
|
|
loc_continuation,
|
|
lookups_in_cond,
|
|
} => {
|
|
let expected_bool = {
|
|
let bool_type = constraints.push_variable(Variable::BOOL);
|
|
constraints.push_expected_type(Expected::ForReason(
|
|
Reason::ExpectCondition,
|
|
bool_type,
|
|
loc_condition.region,
|
|
))
|
|
};
|
|
|
|
let cond_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_condition.region,
|
|
&loc_condition.value,
|
|
expected_bool,
|
|
);
|
|
|
|
let continuation_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_continuation.region,
|
|
&loc_continuation.value,
|
|
expected,
|
|
);
|
|
|
|
// + 2 for cond_con and continuation_con
|
|
let mut all_constraints = Vec::with_capacity(lookups_in_cond.len() + 2);
|
|
|
|
all_constraints.push(cond_con);
|
|
all_constraints.push(continuation_con);
|
|
|
|
let mut vars = Vec::with_capacity(lookups_in_cond.len());
|
|
|
|
for ExpectLookup {
|
|
symbol,
|
|
var,
|
|
ability_info: _,
|
|
} in lookups_in_cond.iter()
|
|
{
|
|
vars.push(*var);
|
|
|
|
let var_index = constraints.push_variable(*var);
|
|
let store_into = constraints.push_expected_type(NoExpectation(var_index));
|
|
|
|
all_constraints.push(constraints.lookup(*symbol, store_into, Region::zero()));
|
|
}
|
|
|
|
constraints.exists_many(vars, all_constraints)
|
|
}
|
|
|
|
Dbg {
|
|
loc_condition,
|
|
loc_continuation,
|
|
variable,
|
|
symbol: _,
|
|
} => {
|
|
let dbg_type = constraints.push_variable(*variable);
|
|
let expected_dbg = constraints.push_expected_type(Expected::NoExpectation(dbg_type));
|
|
|
|
let cond_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_condition.region,
|
|
&loc_condition.value,
|
|
expected_dbg,
|
|
);
|
|
|
|
let continuation_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_continuation.region,
|
|
&loc_continuation.value,
|
|
expected,
|
|
);
|
|
|
|
constraints.exists_many([*variable], [cond_con, continuation_con])
|
|
}
|
|
|
|
If {
|
|
cond_var,
|
|
branch_var,
|
|
branches,
|
|
final_else,
|
|
} => {
|
|
let expect_bool = |constraints: &mut Constraints, region| {
|
|
let bool_type = constraints.push_variable(Variable::BOOL);
|
|
constraints.push_expected_type(Expected::ForReason(
|
|
Reason::IfCondition,
|
|
bool_type,
|
|
region,
|
|
))
|
|
};
|
|
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3);
|
|
|
|
// TODO why does this cond var exist? is it for error messages?
|
|
let first_cond_region = branches[0].0.region;
|
|
let expected_bool = expect_bool(constraints, first_cond_region);
|
|
let cond_var_is_bool_con = constraints.equal_types_var(
|
|
*cond_var,
|
|
expected_bool,
|
|
Category::If,
|
|
first_cond_region,
|
|
);
|
|
|
|
branch_cons.push(cond_var_is_bool_con);
|
|
|
|
let expected = constraints[expected].clone();
|
|
match expected {
|
|
FromAnnotation(name, arity, ann_source, tipe) => {
|
|
let num_branches = branches.len() + 1;
|
|
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
|
let expected_bool = expect_bool(constraints, loc_cond.region);
|
|
let cond_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_cond.region,
|
|
&loc_cond.value,
|
|
expected_bool,
|
|
);
|
|
|
|
let expected_then = constraints.push_expected_type(FromAnnotation(
|
|
name.clone(),
|
|
arity,
|
|
AnnotationSource::TypedIfBranch {
|
|
index: HumanIndex::zero_based(index),
|
|
num_branches,
|
|
region: ann_source.region(),
|
|
},
|
|
tipe,
|
|
));
|
|
|
|
let then_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_body.region,
|
|
&loc_body.value,
|
|
expected_then,
|
|
);
|
|
|
|
branch_cons.push(cond_con);
|
|
branch_cons.push(then_con);
|
|
}
|
|
|
|
let expected_else = constraints.push_expected_type(FromAnnotation(
|
|
name,
|
|
arity,
|
|
AnnotationSource::TypedIfBranch {
|
|
index: HumanIndex::zero_based(branches.len()),
|
|
num_branches,
|
|
region: ann_source.region(),
|
|
},
|
|
tipe,
|
|
));
|
|
let else_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
final_else.region,
|
|
&final_else.value,
|
|
expected_else,
|
|
);
|
|
|
|
let expected_result_type = constraints.push_expected_type(NoExpectation(tipe));
|
|
|
|
let ast_con = constraints.equal_types_var(
|
|
*branch_var,
|
|
expected_result_type,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
);
|
|
|
|
branch_cons.push(ast_con);
|
|
branch_cons.push(else_con);
|
|
|
|
constraints.exists_many([*cond_var, *branch_var], branch_cons)
|
|
}
|
|
_ => {
|
|
let branch_var_index = constraints.push_variable(*branch_var);
|
|
|
|
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
|
let expected_bool = expect_bool(constraints, loc_cond.region);
|
|
let cond_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_cond.region,
|
|
&loc_cond.value,
|
|
expected_bool,
|
|
);
|
|
|
|
let expected_then = constraints.push_expected_type(ForReason(
|
|
Reason::IfBranch {
|
|
index: HumanIndex::zero_based(index),
|
|
total_branches: branches.len(),
|
|
},
|
|
branch_var_index,
|
|
loc_body.region,
|
|
));
|
|
let then_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_body.region,
|
|
&loc_body.value,
|
|
expected_then,
|
|
);
|
|
|
|
branch_cons.push(cond_con);
|
|
branch_cons.push(then_con);
|
|
}
|
|
let expected_else = constraints.push_expected_type(ForReason(
|
|
Reason::IfBranch {
|
|
index: HumanIndex::zero_based(branches.len()),
|
|
total_branches: branches.len() + 1,
|
|
},
|
|
branch_var_index,
|
|
final_else.region,
|
|
));
|
|
let else_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
final_else.region,
|
|
&final_else.value,
|
|
expected_else,
|
|
);
|
|
|
|
let expected = constraints.push_expected_type(expected);
|
|
|
|
branch_cons.push(constraints.equal_types_var(
|
|
*branch_var,
|
|
expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
));
|
|
branch_cons.push(else_con);
|
|
|
|
constraints.exists_many([*cond_var, *branch_var], branch_cons)
|
|
}
|
|
}
|
|
}
|
|
When {
|
|
cond_var: real_cond_var,
|
|
expr_var,
|
|
loc_cond,
|
|
branches,
|
|
branches_cond_var,
|
|
exhaustive,
|
|
..
|
|
} => {
|
|
let branches_cond_var = *branches_cond_var;
|
|
let branches_cond_index = constraints.push_variable(branches_cond_var);
|
|
|
|
let body_var = *expr_var;
|
|
let body_type_index = constraints.push_variable(body_var);
|
|
|
|
let branches_region = {
|
|
debug_assert!(!branches.is_empty());
|
|
Region::span_across(&loc_cond.region, &branches.last().unwrap().value.region)
|
|
};
|
|
|
|
let branch_expr_reason =
|
|
|expected: &Expected<TypeOrVar>, index, branch_region| match expected {
|
|
FromAnnotation(name, arity, ann_source, _typ) => {
|
|
// NOTE deviation from elm.
|
|
//
|
|
// in elm, `_typ` is used, but because we have this `expr_var` too
|
|
// and need to constrain it, this is what works and gives better error messages
|
|
FromAnnotation(
|
|
name.clone(),
|
|
*arity,
|
|
AnnotationSource::TypedWhenBranch {
|
|
index,
|
|
region: ann_source.region(),
|
|
},
|
|
body_type_index,
|
|
)
|
|
}
|
|
|
|
_ => ForReason(Reason::WhenBranch { index }, body_type_index, branch_region),
|
|
};
|
|
|
|
// Our goal is to constrain and introduce variables in all pattern when branch patterns before
|
|
// looking at their bodies.
|
|
//
|
|
// pat1 -> body1
|
|
// *^^^ +~~~~
|
|
// pat2 -> body2
|
|
// *^^^ +~~~~
|
|
//
|
|
// * solve first
|
|
// + solve second
|
|
//
|
|
// For a single pattern/body pair, we must introduce variables and symbols defined in the
|
|
// pattern before solving the body, since those definitions are effectively let-bound.
|
|
//
|
|
// But also, we'd like to solve all branch pattern constraints in one swoop before looking at
|
|
// the bodies, because the patterns may have presence constraints that expect to be built up
|
|
// together.
|
|
//
|
|
// For this reason, we distinguish the two - and introduce variables in the branch patterns
|
|
// as part of the pattern constraint, solving all of those at once, and then solving the body
|
|
// constraints.
|
|
let mut pattern_vars = Vec::with_capacity(branches.len());
|
|
let mut pattern_headers = SendMap::default();
|
|
let mut pattern_cons = Vec::with_capacity(branches.len() + 2);
|
|
let mut delayed_is_open_constraints = Vec::with_capacity(2);
|
|
let mut body_cons = Vec::with_capacity(branches.len());
|
|
|
|
for (index, when_branch) in branches.iter().enumerate() {
|
|
let expected_pattern = |sub_pattern, sub_region| {
|
|
PExpected::ForReason(
|
|
PReason::WhenMatch {
|
|
index: HumanIndex::zero_based(index),
|
|
sub_pattern,
|
|
},
|
|
branches_cond_index,
|
|
sub_region,
|
|
)
|
|
};
|
|
|
|
let ConstrainedBranch {
|
|
vars: new_pattern_vars,
|
|
headers: new_pattern_headers,
|
|
pattern_constraints,
|
|
is_open_constrains,
|
|
body_constraints,
|
|
} = constrain_when_branch_help(
|
|
types,
|
|
constraints,
|
|
env,
|
|
region,
|
|
when_branch,
|
|
expected_pattern,
|
|
branch_expr_reason(
|
|
&constraints[expected],
|
|
HumanIndex::zero_based(index),
|
|
when_branch.value.region,
|
|
),
|
|
);
|
|
|
|
pattern_vars.extend(new_pattern_vars);
|
|
|
|
if cfg!(debug_assertions) {
|
|
let intersection: Vec<_> = pattern_headers
|
|
.keys()
|
|
.filter(|k| new_pattern_headers.contains_key(k))
|
|
.collect();
|
|
|
|
debug_assert!(
|
|
intersection.is_empty(),
|
|
"Two patterns introduce the same symbols - that's a bug!\n{:?}",
|
|
intersection
|
|
);
|
|
}
|
|
|
|
pattern_headers.extend(new_pattern_headers);
|
|
pattern_cons.push(pattern_constraints);
|
|
delayed_is_open_constraints.extend(is_open_constrains);
|
|
|
|
body_cons.push(body_constraints);
|
|
}
|
|
|
|
// Deviation: elm adds another layer of And nesting
|
|
//
|
|
// Record the original conditional expression's constraint.
|
|
// Each branch's pattern must have the same type
|
|
// as the condition expression did.
|
|
//
|
|
// The return type of each branch must equal the return type of
|
|
// the entire when-expression.
|
|
|
|
// Layer on the "is-open" constraints at the very end, after we know what the branch
|
|
// types are supposed to look like without open-ness.
|
|
let is_open_constr = constraints.and_constraint(delayed_is_open_constraints);
|
|
pattern_cons.push(is_open_constr);
|
|
|
|
// After solving the condition variable with what's expected from the branch patterns,
|
|
// check it against the condition expression.
|
|
//
|
|
// First, solve the condition type.
|
|
let real_cond_var = *real_cond_var;
|
|
let real_cond_type = constraints.push_variable(real_cond_var);
|
|
let expected_real_cond =
|
|
constraints.push_expected_type(Expected::NoExpectation(real_cond_type));
|
|
let cond_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_cond.region,
|
|
&loc_cond.value,
|
|
expected_real_cond,
|
|
);
|
|
pattern_cons.push(cond_constraint);
|
|
|
|
// Now check the condition against the type expected by the branches.
|
|
let sketched_rows = sketch_when_branches(branches_region, branches);
|
|
let expected_by_branches = constraints.push_expected_type(Expected::ForReason(
|
|
Reason::WhenBranches,
|
|
branches_cond_index,
|
|
branches_region,
|
|
));
|
|
let cond_matches_branches_constraint = constraints.exhaustive(
|
|
real_cond_var,
|
|
loc_cond.region,
|
|
Ok((loc_cond.value.category(), expected_by_branches)),
|
|
sketched_rows,
|
|
ExhaustiveContext::BadCase,
|
|
*exhaustive,
|
|
);
|
|
pattern_cons.push(cond_matches_branches_constraint);
|
|
|
|
// Solve all the pattern constraints together, introducing variables in the pattern as
|
|
// need be before solving the bodies.
|
|
let pattern_constraints = constraints.and_constraint(pattern_cons);
|
|
let body_constraints = constraints.and_constraint(body_cons);
|
|
let when_body_con = constraints.let_constraint(
|
|
[],
|
|
pattern_vars,
|
|
pattern_headers,
|
|
pattern_constraints,
|
|
body_constraints,
|
|
// Never generalize identifiers introduced in branch-patterns
|
|
Generalizable(false),
|
|
);
|
|
|
|
let result_con =
|
|
constraints.equal_types_var(body_var, expected, Category::When, region);
|
|
|
|
let total_cons = [when_body_con, result_con];
|
|
let branch_constraints = constraints.and_constraint(total_cons);
|
|
|
|
constraints.exists(
|
|
[branches_cond_var, real_cond_var, *expr_var],
|
|
branch_constraints,
|
|
)
|
|
}
|
|
Access {
|
|
record_var,
|
|
ext_var,
|
|
field_var,
|
|
loc_expr,
|
|
field,
|
|
} => {
|
|
let ext_var = *ext_var;
|
|
let ext_type = Type::Variable(ext_var);
|
|
let field_var = *field_var;
|
|
let field_type = Type::Variable(field_var);
|
|
|
|
let mut rec_field_types = SendMap::default();
|
|
|
|
let label = field.clone();
|
|
rec_field_types.insert(label, RecordField::Demanded(field_type));
|
|
|
|
let record_type = {
|
|
let typ = types.from_old_type(&Type::Record(
|
|
rec_field_types,
|
|
TypeExtension::from_type(ext_type),
|
|
));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let record_expected = constraints.push_expected_type(NoExpectation(record_type));
|
|
|
|
let category = Category::Access(field.clone());
|
|
|
|
let record_con =
|
|
constraints.equal_types_var(*record_var, record_expected, category.clone(), region);
|
|
|
|
let expected_record = constraints.push_expected_type(NoExpectation(record_type));
|
|
let constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
region,
|
|
&loc_expr.value,
|
|
expected_record,
|
|
);
|
|
|
|
let eq = constraints.equal_types_var(field_var, expected, category, region);
|
|
constraints.exists_many(
|
|
[*record_var, field_var, ext_var],
|
|
[constraint, eq, record_con],
|
|
)
|
|
}
|
|
Accessor(AccessorData {
|
|
name: closure_name,
|
|
function_var,
|
|
field,
|
|
record_var,
|
|
closure_var,
|
|
ext_var,
|
|
field_var,
|
|
}) => {
|
|
let ext_var = *ext_var;
|
|
let ext_type = Variable(ext_var);
|
|
let field_var = *field_var;
|
|
let field_type = Variable(field_var);
|
|
|
|
let mut field_types = SendMap::default();
|
|
let label = field.clone();
|
|
field_types.insert(label, RecordField::Demanded(field_type.clone()));
|
|
let record_type = Type::Record(field_types, TypeExtension::from_type(ext_type));
|
|
let record_type_index = {
|
|
let typ = types.from_old_type(&record_type);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
let category = Category::Accessor(field.clone());
|
|
|
|
let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
|
|
let record_con =
|
|
constraints.equal_types_var(*record_var, record_expected, category.clone(), region);
|
|
|
|
let expected_lambda_set = {
|
|
let lambda_set_ty = {
|
|
let typ = types.from_old_type(&Type::ClosureTag {
|
|
name: *closure_name,
|
|
captures: vec![],
|
|
ambient_function: *function_var,
|
|
});
|
|
constraints.push_type(types, typ)
|
|
};
|
|
constraints.push_expected_type(NoExpectation(lambda_set_ty))
|
|
};
|
|
|
|
let closure_type = Type::Variable(*closure_var);
|
|
|
|
let function_type_index = {
|
|
let typ = types.from_old_type(&Type::Function(
|
|
vec![record_type],
|
|
Box::new(closure_type),
|
|
Box::new(field_type),
|
|
));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
let cons = [
|
|
constraints.equal_types_var(
|
|
*closure_var,
|
|
expected_lambda_set,
|
|
category.clone(),
|
|
region,
|
|
),
|
|
constraints.equal_types(function_type_index, expected, category.clone(), region),
|
|
{
|
|
let store_fn_var_index = constraints.push_variable(*function_var);
|
|
let store_fn_var_expected =
|
|
constraints.push_expected_type(NoExpectation(store_fn_var_index));
|
|
constraints.equal_types(
|
|
function_type_index,
|
|
store_fn_var_expected,
|
|
category,
|
|
region,
|
|
)
|
|
},
|
|
record_con,
|
|
];
|
|
|
|
constraints.exists_many(
|
|
[*record_var, *function_var, *closure_var, field_var, ext_var],
|
|
cons,
|
|
)
|
|
}
|
|
LetRec(defs, loc_ret, cycle_mark) => {
|
|
let body_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_ret.region,
|
|
&loc_ret.value,
|
|
expected,
|
|
);
|
|
|
|
constrain_recursive_defs(types, constraints, env, defs, body_con, *cycle_mark)
|
|
}
|
|
LetNonRec(def, loc_ret) => {
|
|
let mut stack = Vec::with_capacity(1);
|
|
|
|
let mut loc_ret = loc_ret;
|
|
|
|
stack.push(def);
|
|
|
|
while let LetNonRec(def, new_loc_ret) = &loc_ret.value {
|
|
stack.push(def);
|
|
loc_ret = new_loc_ret;
|
|
}
|
|
|
|
let mut body_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_ret.region,
|
|
&loc_ret.value,
|
|
expected,
|
|
);
|
|
|
|
while let Some(def) = stack.pop() {
|
|
body_con = constrain_def(types, constraints, env, def, body_con)
|
|
}
|
|
|
|
body_con
|
|
}
|
|
Tag {
|
|
tag_union_var: variant_var,
|
|
ext_var,
|
|
name,
|
|
arguments,
|
|
} => {
|
|
// +2 because we push all the arguments, plus variant_var and ext_var
|
|
let num_vars = arguments.len() + 2;
|
|
let mut vars = Vec::with_capacity(num_vars);
|
|
let mut payload_types = Vec::with_capacity(arguments.len());
|
|
let mut arg_cons = Vec::with_capacity(arguments.len());
|
|
|
|
for (var, loc_expr) in arguments {
|
|
let var_index = constraints.push_variable(*var);
|
|
let expected_arg = constraints.push_expected_type(NoExpectation(var_index));
|
|
let arg_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected_arg,
|
|
);
|
|
|
|
arg_cons.push(arg_con);
|
|
vars.push(*var);
|
|
payload_types.push(Type::Variable(*var));
|
|
}
|
|
|
|
let tag_union_type = {
|
|
let typ = types.from_old_type(&Type::TagUnion(
|
|
vec![(name.clone(), payload_types)],
|
|
TypeExtension::from_type(Type::Variable(*ext_var)),
|
|
));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
let union_con = constraints.equal_types_with_storage(
|
|
tag_union_type,
|
|
expected,
|
|
Category::TagApply {
|
|
tag_name: name.clone(),
|
|
args_count: arguments.len(),
|
|
},
|
|
region,
|
|
*variant_var,
|
|
);
|
|
|
|
vars.push(*variant_var);
|
|
vars.push(*ext_var);
|
|
arg_cons.push(union_con);
|
|
|
|
constraints.exists_many(vars, arg_cons)
|
|
}
|
|
ZeroArgumentTag {
|
|
variant_var,
|
|
ext_var,
|
|
name,
|
|
closure_name,
|
|
} => {
|
|
let function_or_tag_union = {
|
|
let typ = types.from_old_type(&Type::FunctionOrTagUnion(
|
|
name.clone(),
|
|
*closure_name,
|
|
TypeExtension::from_type(Type::Variable(*ext_var)),
|
|
));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let union_con = constraints.equal_types_with_storage(
|
|
function_or_tag_union,
|
|
expected,
|
|
Category::TagApply {
|
|
tag_name: name.clone(),
|
|
args_count: 0,
|
|
},
|
|
region,
|
|
*variant_var,
|
|
);
|
|
|
|
constraints.exists_many([*variant_var, *ext_var], [union_con])
|
|
}
|
|
OpaqueRef {
|
|
opaque_var,
|
|
name,
|
|
argument,
|
|
specialized_def_type,
|
|
type_arguments,
|
|
lambda_set_variables,
|
|
} => {
|
|
let (arg_var, arg_loc_expr) = &**argument;
|
|
let arg_type = Type::Variable(*arg_var);
|
|
let arg_type_index = constraints.push_variable(*arg_var);
|
|
|
|
let opaque_type = {
|
|
let typ = types.from_old_type(&Type::Alias {
|
|
symbol: *name,
|
|
type_arguments: type_arguments
|
|
.iter()
|
|
.map(|v| OptAbleType {
|
|
typ: Type::Variable(v.var),
|
|
opt_abilities: v.opt_abilities.clone(),
|
|
})
|
|
.collect(),
|
|
lambda_set_variables: lambda_set_variables.clone(),
|
|
infer_ext_in_output_types: vec![],
|
|
actual: Box::new(arg_type),
|
|
kind: AliasKind::Opaque,
|
|
});
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
// Constrain the argument
|
|
let expected_arg =
|
|
constraints.push_expected_type(Expected::NoExpectation(arg_type_index));
|
|
let arg_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
arg_loc_expr.region,
|
|
&arg_loc_expr.value,
|
|
expected_arg,
|
|
);
|
|
|
|
// Link the entire wrapped opaque type (with the now-constrained argument) to the
|
|
// expected type
|
|
let opaque_con = constraints.equal_types_with_storage(
|
|
opaque_type,
|
|
expected,
|
|
Category::OpaqueWrap(*name),
|
|
region,
|
|
*opaque_var,
|
|
);
|
|
|
|
// Link the entire wrapped opaque type (with the now-constrained argument) to the type
|
|
// variables of the opaque type
|
|
// TODO: better expectation here
|
|
let link_type_variables_con = {
|
|
let specialized_type_index = {
|
|
let typ = types.from_old_type(&**specialized_def_type);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let expected_index =
|
|
constraints.push_expected_type(Expected::NoExpectation(specialized_type_index));
|
|
constraints.equal_types(
|
|
arg_type_index,
|
|
expected_index,
|
|
Category::OpaqueArg,
|
|
arg_loc_expr.region,
|
|
)
|
|
};
|
|
|
|
let mut vars = vec![*arg_var, *opaque_var];
|
|
// Also add the fresh variables we created for the type argument and lambda sets
|
|
vars.extend(type_arguments.iter().map(|v| v.var));
|
|
vars.extend(lambda_set_variables.iter().map(|v| {
|
|
v.0.expect_variable("all lambda sets should be fresh variables here")
|
|
}));
|
|
|
|
constraints.exists_many(vars, [arg_con, opaque_con, link_type_variables_con])
|
|
}
|
|
OpaqueWrapFunction(OpaqueWrapFunctionData {
|
|
opaque_name,
|
|
opaque_var,
|
|
specialized_def_type,
|
|
type_arguments,
|
|
lambda_set_variables,
|
|
function_name,
|
|
function_var,
|
|
argument_var,
|
|
closure_var,
|
|
}) => {
|
|
let argument_type = Type::Variable(*argument_var);
|
|
|
|
let opaque_type = {
|
|
let typ = types.from_old_type(&Type::Alias {
|
|
symbol: *opaque_name,
|
|
type_arguments: type_arguments
|
|
.iter()
|
|
.map(|v| OptAbleType {
|
|
typ: Type::Variable(v.var),
|
|
opt_abilities: v.opt_abilities.clone(),
|
|
})
|
|
.collect(),
|
|
lambda_set_variables: lambda_set_variables.clone(),
|
|
infer_ext_in_output_types: vec![],
|
|
actual: Box::new(argument_type.clone()),
|
|
kind: AliasKind::Opaque,
|
|
});
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
let expected_opaque_type = constraints.push_expected_type(NoExpectation(opaque_type));
|
|
|
|
// Tie the opaque type to the opaque_var
|
|
let opaque_con = constraints.equal_types_var(
|
|
*opaque_var,
|
|
expected_opaque_type,
|
|
Category::OpaqueWrap(*opaque_name),
|
|
region,
|
|
);
|
|
|
|
// Tie the type of the value wrapped by the opaque to the opaque's type variables.
|
|
let link_type_variables_con = {
|
|
let arg_type_index = {
|
|
let typ = types.from_old_type(&argument_type);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let specialized_type_index = {
|
|
let typ = types.from_old_type(specialized_def_type);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let expected_specialized =
|
|
constraints.push_expected_type(Expected::NoExpectation(specialized_type_index));
|
|
constraints.equal_types(
|
|
arg_type_index,
|
|
expected_specialized,
|
|
Category::OpaqueArg,
|
|
region,
|
|
)
|
|
};
|
|
|
|
let lambda_set = {
|
|
let lambda_set_index = {
|
|
let typ = types.from_old_type(&Type::ClosureTag {
|
|
name: *function_name,
|
|
captures: vec![],
|
|
ambient_function: *function_var,
|
|
});
|
|
constraints.push_type(types, typ)
|
|
};
|
|
constraints.push_expected_type(NoExpectation(lambda_set_index))
|
|
};
|
|
|
|
let closure_type = Type::Variable(*closure_var);
|
|
|
|
let opaque_type = Type::Variable(*opaque_var);
|
|
|
|
let expected_function_type = {
|
|
let fn_type = {
|
|
let typ = types.from_old_type(&Type::Function(
|
|
vec![argument_type],
|
|
Box::new(closure_type),
|
|
Box::new(opaque_type),
|
|
));
|
|
constraints.push_type(types, typ)
|
|
};
|
|
constraints.push_expected_type(NoExpectation(fn_type))
|
|
};
|
|
|
|
let cons = [
|
|
opaque_con,
|
|
link_type_variables_con,
|
|
constraints.equal_types_var(
|
|
*closure_var,
|
|
lambda_set,
|
|
Category::OpaqueWrap(*opaque_name),
|
|
region,
|
|
),
|
|
constraints.equal_types_var(
|
|
*function_var,
|
|
expected_function_type,
|
|
Category::OpaqueWrap(*opaque_name),
|
|
region,
|
|
),
|
|
constraints.equal_types_var(
|
|
*function_var,
|
|
expected,
|
|
Category::OpaqueWrap(*opaque_name),
|
|
region,
|
|
),
|
|
];
|
|
|
|
let mut vars = vec![*argument_var, *opaque_var];
|
|
|
|
// Also add the fresh variables we created for the type argument and lambda sets
|
|
vars.extend(type_arguments.iter().map(|v| v.var));
|
|
vars.extend(lambda_set_variables.iter().map(|v| {
|
|
v.0.expect_variable("all lambda sets should be fresh variables here")
|
|
}));
|
|
|
|
vars.extend([*function_var, *closure_var]);
|
|
|
|
constraints.exists_many(vars, cons)
|
|
}
|
|
|
|
RunLowLevel { args, ret_var, op } => {
|
|
// This is a modified version of what we do for function calls.
|
|
|
|
// This will be used in the occurs check
|
|
let mut vars = Vec::with_capacity(1 + args.len());
|
|
|
|
vars.push(*ret_var);
|
|
|
|
let mut arg_types = Vec::with_capacity(args.len());
|
|
let mut arg_cons = Vec::with_capacity(args.len());
|
|
|
|
let mut add_arg = |constraints: &mut Constraints, index, arg_type: TypeOrVar, arg| {
|
|
let reason = Reason::LowLevelOpArg {
|
|
op: *op,
|
|
arg_index: HumanIndex::zero_based(index),
|
|
};
|
|
let expected_arg =
|
|
constraints.push_expected_type(ForReason(reason, arg_type, Region::zero()));
|
|
let arg_con =
|
|
constrain_expr(types, constraints, env, Region::zero(), arg, expected_arg);
|
|
|
|
arg_types.push(arg_type);
|
|
arg_cons.push(arg_con);
|
|
};
|
|
|
|
for (index, (arg_var, arg)) in args.iter().enumerate() {
|
|
vars.push(*arg_var);
|
|
let arg_var_index = constraints.push_variable(*arg_var);
|
|
|
|
add_arg(constraints, index, arg_var_index, arg);
|
|
}
|
|
|
|
let category = Category::LowLevelOpResult(*op);
|
|
|
|
// Deviation: elm uses an additional And here
|
|
let eq = constraints.equal_types_var(*ret_var, expected, category, region);
|
|
arg_cons.push(eq);
|
|
constraints.exists_many(vars, arg_cons)
|
|
}
|
|
ForeignCall {
|
|
args,
|
|
ret_var,
|
|
foreign_symbol,
|
|
} => {
|
|
// This is a modified version of what we do for function calls.
|
|
|
|
// This will be used in the occurs check
|
|
let mut vars = Vec::with_capacity(1 + args.len());
|
|
|
|
vars.push(*ret_var);
|
|
|
|
let mut arg_types = Vec::with_capacity(args.len());
|
|
let mut arg_cons = Vec::with_capacity(args.len());
|
|
|
|
let mut add_arg = |constraints: &mut Constraints, index, arg_type: TypeOrVar, arg| {
|
|
let reason = Reason::ForeignCallArg {
|
|
foreign_symbol: foreign_symbol.clone(),
|
|
arg_index: HumanIndex::zero_based(index),
|
|
};
|
|
let expected_arg =
|
|
constraints.push_expected_type(ForReason(reason, arg_type, Region::zero()));
|
|
let arg_con =
|
|
constrain_expr(types, constraints, env, Region::zero(), arg, expected_arg);
|
|
|
|
arg_types.push(arg_type);
|
|
arg_cons.push(arg_con);
|
|
};
|
|
|
|
for (index, (arg_var, arg)) in args.iter().enumerate() {
|
|
vars.push(*arg_var);
|
|
let arg_var_index = constraints.push_variable(*arg_var);
|
|
|
|
add_arg(constraints, index, arg_var_index, arg);
|
|
}
|
|
|
|
let category = Category::ForeignCall;
|
|
|
|
// Deviation: elm uses an additional And here
|
|
let eq = constraints.equal_types_var(*ret_var, expected, category, region);
|
|
arg_cons.push(eq);
|
|
constraints.exists_many(vars, arg_cons)
|
|
}
|
|
TypedHole(var) => {
|
|
// store the expected type for this position
|
|
constraints.equal_types_var(
|
|
*var,
|
|
expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
)
|
|
}
|
|
RuntimeError(_) => {
|
|
// Runtime Errors are always going to crash, so they don't introduce any new
|
|
// constraints.
|
|
// Instead, trivially equate the expected type to itself. This will never yield
|
|
// unification errors but it will catch errors in type translation, including ability
|
|
// obligations.
|
|
let trivial_type = *constraints[expected].get_type_ref();
|
|
constraints.equal_types(trivial_type, expected, Category::Unknown, region)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn constrain_function_def(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
declarations: &Declarations,
|
|
index: usize,
|
|
function_def_index: Index<Loc<FunctionDef>>,
|
|
body_con: Constraint,
|
|
) -> Constraint {
|
|
let loc_expr = &declarations.expressions[index];
|
|
let loc_symbol = declarations.symbols[index];
|
|
let expr_var = declarations.variables[index];
|
|
let expr_var_index = constraints.push_variable(expr_var);
|
|
let opt_annotation = &declarations.annotations[index];
|
|
|
|
let loc_function_def = &declarations.function_bodies[function_def_index.index()];
|
|
let function_def = &loc_function_def.value;
|
|
|
|
match opt_annotation {
|
|
Some(annotation) => {
|
|
let loc_pattern = Loc::at(loc_symbol.region, Pattern::Identifier(loc_symbol.value));
|
|
let loc_body_expr = loc_expr;
|
|
|
|
let arity = annotation.signature.arity();
|
|
let rigids = &env.rigids;
|
|
let mut ftv = rigids.clone();
|
|
|
|
let InstantiateRigids {
|
|
signature,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
} = instantiate_rigids_simple(
|
|
types,
|
|
&annotation.signature,
|
|
&annotation.introduced_variables,
|
|
&mut ftv,
|
|
);
|
|
|
|
let signature_index = constraints.push_type(types, signature);
|
|
|
|
let (arg_types, signature_closure_type, ret_type) = match types[signature] {
|
|
TypeTag::Function(signature_closure_type, ret_type) => (
|
|
types.get_type_arguments(signature),
|
|
signature_closure_type,
|
|
ret_type,
|
|
),
|
|
_ => {
|
|
// aliases, or just something weird
|
|
|
|
let def_pattern_state = {
|
|
let mut def_pattern_state = PatternState::default();
|
|
|
|
def_pattern_state.headers.insert(
|
|
loc_symbol.value,
|
|
Loc {
|
|
region: loc_function_def.region,
|
|
// todo can we use Type::Variable(expr_var) here?
|
|
value: signature_index,
|
|
},
|
|
);
|
|
|
|
// TODO see if we can get away with not adding this constraint at all
|
|
def_pattern_state.vars.push(expr_var);
|
|
let annotation_expected = FromAnnotation(
|
|
loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
);
|
|
|
|
{
|
|
let expected_index =
|
|
constraints.push_expected_type(annotation_expected);
|
|
def_pattern_state.constraints.push(constraints.equal_types(
|
|
expr_var_index,
|
|
expected_index,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
Region::span_across(&annotation.region, &loc_body_expr.region),
|
|
));
|
|
}
|
|
|
|
def_pattern_state
|
|
};
|
|
|
|
let annotation_expected = constraints.push_expected_type(FromAnnotation(
|
|
loc_pattern,
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
));
|
|
|
|
let ret_constraint = constrain_untyped_closure(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_function_def.region,
|
|
annotation_expected,
|
|
expr_var,
|
|
function_def.closure_type,
|
|
function_def.return_type,
|
|
&function_def.arguments,
|
|
loc_body_expr,
|
|
&function_def.captured_symbols,
|
|
loc_symbol.value,
|
|
);
|
|
|
|
let ret_constraint =
|
|
attach_resolution_constraints(constraints, env, ret_constraint);
|
|
|
|
let cons = [
|
|
ret_constraint,
|
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
|
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
|
|
];
|
|
let expr_con = constraints.and_constraint(cons);
|
|
|
|
return constrain_function_def_make_constraint(
|
|
constraints,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
expr_con,
|
|
body_con,
|
|
def_pattern_state,
|
|
);
|
|
}
|
|
};
|
|
|
|
let env = &mut Env {
|
|
home: env.home,
|
|
rigids: ftv,
|
|
resolutions_to_make: vec![],
|
|
};
|
|
|
|
let region = loc_function_def.region;
|
|
|
|
let mut argument_pattern_state = PatternState {
|
|
headers: VecMap::default(),
|
|
vars: Vec::with_capacity(function_def.arguments.len()),
|
|
constraints: Vec::with_capacity(1),
|
|
delayed_is_open_constraints: vec![],
|
|
};
|
|
let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
|
|
let ret_var = function_def.return_type;
|
|
let closure_var = function_def.closure_type;
|
|
|
|
let ret_type_index = constraints.push_type(types, ret_type);
|
|
|
|
vars.push(ret_var);
|
|
vars.push(closure_var);
|
|
|
|
let mut def_pattern_state = PatternState::default();
|
|
|
|
def_pattern_state.headers.insert(
|
|
loc_symbol.value,
|
|
Loc {
|
|
region: loc_function_def.region,
|
|
// NOTE: we MUST use `expr_var` here so that the correct type variable is
|
|
// associated with the function. We prefer this to the annotation type, because the
|
|
// annotation type may be instantiated into a fresh type variable that is
|
|
// disassociated fromt the rest of the program.
|
|
// Below, we'll check that the function actually matches the annotation.
|
|
value: expr_var_index,
|
|
},
|
|
);
|
|
|
|
// TODO see if we can get away with not adding this constraint at all
|
|
def_pattern_state.vars.push(expr_var);
|
|
let annotation_expected = FromAnnotation(
|
|
loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
);
|
|
|
|
{
|
|
let expr_type_index = constraints.push_variable(expr_var);
|
|
let expected_index = constraints.push_expected_type(annotation_expected);
|
|
def_pattern_state.constraints.push(constraints.equal_types(
|
|
expr_type_index,
|
|
expected_index,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
Region::span_across(&annotation.region, &loc_body_expr.region),
|
|
));
|
|
}
|
|
|
|
constrain_typed_function_arguments_simple(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_symbol.value,
|
|
&mut def_pattern_state,
|
|
&mut argument_pattern_state,
|
|
&function_def.arguments,
|
|
arg_types,
|
|
);
|
|
|
|
let closure_constraint = constrain_closure_size(
|
|
types,
|
|
constraints,
|
|
loc_symbol.value,
|
|
region,
|
|
expr_var,
|
|
&function_def.captured_symbols,
|
|
closure_var,
|
|
&mut vars,
|
|
);
|
|
|
|
let annotation_expected = constraints.push_expected_type(FromAnnotation(
|
|
loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
ret_type_index,
|
|
));
|
|
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_body_expr.region,
|
|
&loc_body_expr.value,
|
|
annotation_expected,
|
|
);
|
|
let ret_constraint = attach_resolution_constraints(constraints, env, ret_constraint);
|
|
|
|
vars.push(expr_var);
|
|
let defs_constraint = constraints.and_constraint(argument_pattern_state.constraints);
|
|
|
|
let signature_closure_type = {
|
|
let signature_closure_type_index =
|
|
constraints.push_type(types, signature_closure_type);
|
|
constraints.push_expected_type(Expected::FromAnnotation(
|
|
loc_pattern,
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_closure_type_index,
|
|
))
|
|
};
|
|
let cons = [
|
|
constraints.let_constraint(
|
|
[],
|
|
argument_pattern_state.vars,
|
|
argument_pattern_state.headers,
|
|
defs_constraint,
|
|
ret_constraint,
|
|
// This is a syntactic function, it can be generalized
|
|
Generalizable(true),
|
|
),
|
|
constraints.equal_types_var(
|
|
closure_var,
|
|
signature_closure_type,
|
|
Category::ClosureSize,
|
|
region,
|
|
),
|
|
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
|
|
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
|
|
closure_constraint,
|
|
];
|
|
|
|
let expr_con = constraints.exists_many(vars, cons);
|
|
|
|
constrain_function_def_make_constraint(
|
|
constraints,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
expr_con,
|
|
body_con,
|
|
def_pattern_state,
|
|
)
|
|
}
|
|
None => {
|
|
let expr_type = constraints.push_variable(expr_var);
|
|
|
|
let expected_expr = constraints.push_expected_type(NoExpectation(expr_type));
|
|
let expr_con = constrain_untyped_closure(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_function_def.region,
|
|
expected_expr,
|
|
expr_var,
|
|
function_def.closure_type,
|
|
function_def.return_type,
|
|
&function_def.arguments,
|
|
loc_expr,
|
|
&function_def.captured_symbols,
|
|
loc_symbol.value,
|
|
);
|
|
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
|
|
constrain_value_def_make_constraint(
|
|
constraints,
|
|
vec![],
|
|
vec![],
|
|
expr_con,
|
|
body_con,
|
|
loc_symbol,
|
|
expr_var,
|
|
expr_type,
|
|
Generalizable(true), // this is a syntactic function
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn constrain_destructure_def(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
declarations: &Declarations,
|
|
index: usize,
|
|
destructure_def_index: Index<DestructureDef>,
|
|
body_con: Constraint,
|
|
) -> Constraint {
|
|
let loc_expr = &declarations.expressions[index];
|
|
let expr_var = declarations.variables[index];
|
|
let expr_var_index = constraints.push_variable(expr_var);
|
|
let opt_annotation = &declarations.annotations[index];
|
|
|
|
let destructure_def = &declarations.destructs[destructure_def_index.index()];
|
|
let loc_pattern = &destructure_def.loc_pattern;
|
|
|
|
let mut def_pattern_state =
|
|
constrain_def_pattern(types, constraints, env, loc_pattern, expr_var_index);
|
|
|
|
def_pattern_state.vars.push(expr_var);
|
|
|
|
match opt_annotation {
|
|
Some(annotation) => {
|
|
let arity = 1;
|
|
let rigids = &env.rigids;
|
|
let mut ftv = rigids.clone();
|
|
|
|
let InstantiateRigids {
|
|
signature,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
} = instantiate_rigids(
|
|
types,
|
|
constraints,
|
|
&annotation.signature,
|
|
&annotation.introduced_variables,
|
|
loc_pattern,
|
|
&mut ftv,
|
|
&mut def_pattern_state.headers,
|
|
);
|
|
|
|
let env = &mut Env {
|
|
home: env.home,
|
|
rigids: ftv,
|
|
resolutions_to_make: vec![],
|
|
};
|
|
|
|
let signature_index = constraints.push_type(types, signature);
|
|
|
|
let annotation_expected = constraints.push_expected_type(FromAnnotation(
|
|
loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
));
|
|
|
|
// This will fill in inference variables in the `signature` as well, so that we can
|
|
// then take the signature as the source-of-truth without having to worry about
|
|
// incompleteness.
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
annotation_expected,
|
|
);
|
|
|
|
let cons = [
|
|
ret_constraint,
|
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
|
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
|
|
];
|
|
let expr_con = constraints.and_constraint(cons);
|
|
|
|
constrain_function_def_make_constraint(
|
|
constraints,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
expr_con,
|
|
body_con,
|
|
def_pattern_state,
|
|
)
|
|
}
|
|
None => {
|
|
let expr_type = constraints.push_variable(expr_var);
|
|
|
|
let expected_type = constraints.push_expected_type(NoExpectation(expr_type));
|
|
let expr_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected_type,
|
|
);
|
|
|
|
constrain_function_def_make_constraint(
|
|
constraints,
|
|
vec![],
|
|
vec![],
|
|
expr_con,
|
|
body_con,
|
|
def_pattern_state,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn constrain_value_def(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
declarations: &Declarations,
|
|
index: usize,
|
|
body_con: Constraint,
|
|
) -> Constraint {
|
|
let loc_expr = &declarations.expressions[index];
|
|
let loc_symbol = declarations.symbols[index];
|
|
let expr_var = declarations.variables[index];
|
|
let opt_annotation = &declarations.annotations[index];
|
|
|
|
let generalizable = Generalizable(is_generalizable_expr(&loc_expr.value));
|
|
|
|
match opt_annotation {
|
|
Some(annotation) => {
|
|
let arity = 1;
|
|
let rigids = &env.rigids;
|
|
let mut ftv = rigids.clone();
|
|
|
|
let InstantiateRigids {
|
|
signature,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
} = instantiate_rigids_simple(
|
|
types,
|
|
&annotation.signature,
|
|
&annotation.introduced_variables,
|
|
&mut ftv,
|
|
);
|
|
|
|
let env = &mut Env {
|
|
home: env.home,
|
|
rigids: ftv,
|
|
resolutions_to_make: vec![],
|
|
};
|
|
|
|
let loc_pattern = Loc::at(loc_symbol.region, Pattern::Identifier(loc_symbol.value));
|
|
|
|
let signature_index = constraints.push_type(types, signature);
|
|
|
|
let annotation_expected = constraints.push_expected_type(FromAnnotation(
|
|
loc_pattern,
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
));
|
|
|
|
// This will fill in inference variables in the `signature` as well, so that we can
|
|
// then take the signature as the source-of-truth without having to worry about
|
|
// incompleteness.
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
annotation_expected,
|
|
);
|
|
let ret_constraint = attach_resolution_constraints(constraints, env, ret_constraint);
|
|
|
|
let cons = [
|
|
ret_constraint,
|
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
|
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
|
|
];
|
|
let expr_con = constraints.and_constraint(cons);
|
|
|
|
constrain_value_def_make_constraint(
|
|
constraints,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
expr_con,
|
|
body_con,
|
|
loc_symbol,
|
|
expr_var,
|
|
signature_index,
|
|
generalizable,
|
|
)
|
|
}
|
|
None => {
|
|
let expr_type = constraints.push_variable(expr_var);
|
|
|
|
let expected_type = constraints.push_expected_type(NoExpectation(expr_type));
|
|
let expr_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected_type,
|
|
);
|
|
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
|
|
constrain_value_def_make_constraint(
|
|
constraints,
|
|
vec![],
|
|
vec![],
|
|
expr_con,
|
|
body_con,
|
|
loc_symbol,
|
|
expr_var,
|
|
expr_type,
|
|
generalizable,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ConstrainedBranch {
|
|
vars: Vec<Variable>,
|
|
headers: VecMap<Symbol, Loc<TypeOrVar>>,
|
|
pattern_constraints: Constraint,
|
|
is_open_constrains: Vec<Constraint>,
|
|
body_constraints: Constraint,
|
|
}
|
|
|
|
/// Constrain a when branch, returning (variables in pattern, symbols introduced in pattern, pattern constraint, body constraint).
|
|
/// We want to constraint all pattern constraints in a "when" before body constraints.
|
|
#[inline(always)]
|
|
fn constrain_when_branch_help(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
region: Region,
|
|
when_branch: &WhenBranch,
|
|
pattern_expected: impl Fn(HumanIndex, Region) -> PExpected<TypeOrVar>,
|
|
expr_expected: Expected<TypeOrVar>,
|
|
) -> ConstrainedBranch {
|
|
let expr_expected = constraints.push_expected_type(expr_expected);
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
region,
|
|
&when_branch.value.value,
|
|
expr_expected,
|
|
);
|
|
|
|
let mut state = PatternState {
|
|
headers: VecMap::default(),
|
|
vars: Vec::with_capacity(2),
|
|
constraints: Vec::with_capacity(2),
|
|
delayed_is_open_constraints: Vec::new(),
|
|
};
|
|
|
|
for (i, loc_pattern) in when_branch.patterns.iter().enumerate() {
|
|
let pattern_expected = constraints.push_pat_expected_type(pattern_expected(
|
|
HumanIndex::zero_based(i),
|
|
loc_pattern.pattern.region,
|
|
));
|
|
|
|
let mut partial_state = PatternState::default();
|
|
constrain_pattern(
|
|
types,
|
|
constraints,
|
|
env,
|
|
&loc_pattern.pattern.value,
|
|
loc_pattern.pattern.region,
|
|
pattern_expected,
|
|
&mut partial_state,
|
|
);
|
|
|
|
state.vars.extend(partial_state.vars);
|
|
state.constraints.extend(partial_state.constraints);
|
|
state
|
|
.delayed_is_open_constraints
|
|
.extend(partial_state.delayed_is_open_constraints);
|
|
|
|
if i == 0 {
|
|
state.headers.extend(partial_state.headers);
|
|
} else {
|
|
// Make sure the bound variables in the patterns on the same branch agree in their types.
|
|
for (sym, all_branches_bound_typ) in state.headers.iter() {
|
|
if let Some(this_bound_typ) = partial_state.headers.get(sym) {
|
|
let whole_typ = all_branches_bound_typ.value;
|
|
let this_typ = constraints
|
|
.push_expected_type(Expected::NoExpectation(this_bound_typ.value));
|
|
|
|
state.constraints.push(constraints.equal_types(
|
|
whole_typ,
|
|
this_typ,
|
|
Category::When,
|
|
this_bound_typ.region,
|
|
));
|
|
}
|
|
|
|
// If the pattern doesn't bind all symbols introduced in the branch we'll have
|
|
// reported a canonicalization error, but still might reach here; that's okay.
|
|
}
|
|
|
|
// Add any variables this pattern binds that the other patterns don't bind.
|
|
// This will already have been reported as an error, but we still might be able to
|
|
// solve their types.
|
|
for (sym, ty) in partial_state.headers {
|
|
if !state.headers.contains_key(&sym) {
|
|
state.headers.insert(sym, ty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let (pattern_constraints, delayed_is_open_constraints, body_constraints) =
|
|
if let Some(loc_guard) = &when_branch.guard {
|
|
let bool_index = constraints.push_variable(Variable::BOOL);
|
|
let expected_guard = constraints.push_expected_type(Expected::ForReason(
|
|
Reason::WhenGuard,
|
|
bool_index,
|
|
loc_guard.region,
|
|
));
|
|
|
|
let guard_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
region,
|
|
&loc_guard.value,
|
|
expected_guard,
|
|
);
|
|
|
|
// must introduce the headers from the pattern before constraining the guard
|
|
let delayed_is_open_constraints = state.delayed_is_open_constraints;
|
|
let state_constraints = constraints.and_constraint(state.constraints);
|
|
let inner = constraints.let_constraint(
|
|
[],
|
|
[],
|
|
[],
|
|
guard_constraint,
|
|
ret_constraint,
|
|
// Never generalize identifiers introduced in branch guards
|
|
Generalizable(false),
|
|
);
|
|
|
|
(state_constraints, delayed_is_open_constraints, inner)
|
|
} else {
|
|
let delayed_is_open_constraints = state.delayed_is_open_constraints;
|
|
let state_constraints = constraints.and_constraint(state.constraints);
|
|
(
|
|
state_constraints,
|
|
delayed_is_open_constraints,
|
|
ret_constraint,
|
|
)
|
|
};
|
|
|
|
ConstrainedBranch {
|
|
vars: state.vars,
|
|
headers: state.headers,
|
|
pattern_constraints,
|
|
is_open_constrains: delayed_is_open_constraints,
|
|
body_constraints,
|
|
}
|
|
}
|
|
|
|
fn constrain_field(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
field_var: Variable,
|
|
loc_expr: &Loc<Expr>,
|
|
) -> (Type, Constraint) {
|
|
let field_type = constraints.push_variable(field_var);
|
|
let field_expected = constraints.push_expected_type(NoExpectation(field_type));
|
|
let constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
field_expected,
|
|
);
|
|
|
|
(Variable(field_var), constraint)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn constrain_empty_record(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
region: Region,
|
|
expected: ExpectedTypeIndex,
|
|
) -> Constraint {
|
|
let record_type_index = constraints.push_type(types, Types::EMPTY_RECORD);
|
|
constraints.equal_types(record_type_index, expected, Category::Record, region)
|
|
}
|
|
|
|
/// Constrain top-level module declarations
|
|
#[inline(always)]
|
|
pub fn constrain_decls(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
home: ModuleId,
|
|
declarations: &Declarations,
|
|
) -> Constraint {
|
|
let mut constraint = Constraint::SaveTheEnvironment;
|
|
|
|
let mut env = Env {
|
|
home,
|
|
rigids: MutMap::default(),
|
|
resolutions_to_make: vec![],
|
|
};
|
|
|
|
debug_assert_eq!(declarations.declarations.len(), declarations.symbols.len());
|
|
|
|
let mut index = 0;
|
|
while index < declarations.len() {
|
|
// Clear the rigids from the previous iteration.
|
|
// rigids are not shared between top-level definitions
|
|
env.rigids.clear();
|
|
|
|
use roc_can::expr::DeclarationTag::*;
|
|
let tag = declarations.declarations[index];
|
|
match tag {
|
|
Value => {
|
|
constraint = constrain_value_def(
|
|
types,
|
|
constraints,
|
|
&mut env,
|
|
declarations,
|
|
index,
|
|
constraint,
|
|
);
|
|
}
|
|
Expectation => {
|
|
let loc_expr = &declarations.expressions[index];
|
|
|
|
let bool_type = constraints.push_variable(Variable::BOOL);
|
|
let expected = constraints.push_expected_type(Expected::ForReason(
|
|
Reason::ExpectCondition,
|
|
bool_type,
|
|
loc_expr.region,
|
|
));
|
|
|
|
let expect_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
&mut env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected,
|
|
);
|
|
|
|
constraint = constraints.let_constraint(
|
|
[],
|
|
[],
|
|
[],
|
|
expect_constraint,
|
|
constraint,
|
|
Generalizable(false),
|
|
)
|
|
}
|
|
ExpectationFx => {
|
|
let loc_expr = &declarations.expressions[index];
|
|
|
|
let bool_type = constraints.push_variable(Variable::BOOL);
|
|
let expected = constraints.push_expected_type(Expected::ForReason(
|
|
Reason::ExpectCondition,
|
|
bool_type,
|
|
loc_expr.region,
|
|
));
|
|
|
|
let expect_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
&mut env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected,
|
|
);
|
|
|
|
constraint = constraints.let_constraint(
|
|
[],
|
|
[],
|
|
[],
|
|
expect_constraint,
|
|
constraint,
|
|
Generalizable(false),
|
|
)
|
|
}
|
|
Function(function_def_index) => {
|
|
constraint = constrain_function_def(
|
|
types,
|
|
constraints,
|
|
&mut env,
|
|
declarations,
|
|
index,
|
|
function_def_index,
|
|
constraint,
|
|
);
|
|
}
|
|
Recursive(_) | TailRecursive(_) => {
|
|
// for the type it does not matter that a recursive call is a tail call
|
|
constraint = constrain_recursive_declarations(
|
|
types,
|
|
constraints,
|
|
&mut env,
|
|
declarations,
|
|
index..index + 1,
|
|
constraint,
|
|
IllegalCycleMark::empty(),
|
|
);
|
|
}
|
|
Destructure(destructure_def_index) => {
|
|
constraint = constrain_destructure_def(
|
|
types,
|
|
constraints,
|
|
&mut env,
|
|
declarations,
|
|
index,
|
|
destructure_def_index,
|
|
constraint,
|
|
);
|
|
}
|
|
MutualRecursion { length, cycle_mark } => {
|
|
// the next `length` defs belong to this group
|
|
let length = length as usize;
|
|
|
|
constraint = constrain_recursive_declarations(
|
|
types,
|
|
constraints,
|
|
&mut env,
|
|
declarations,
|
|
index + 1..index + 1 + length,
|
|
constraint,
|
|
cycle_mark,
|
|
);
|
|
|
|
index += length as usize;
|
|
}
|
|
}
|
|
|
|
index += 1;
|
|
}
|
|
|
|
// this assert make the "root" of the constraint wasn't dropped
|
|
debug_assert!(constraints.contains_save_the_environment(&constraint));
|
|
|
|
constraint
|
|
}
|
|
|
|
pub(crate) fn constrain_def_pattern(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
loc_pattern: &Loc<Pattern>,
|
|
expr_type: TypeOrVar,
|
|
) -> PatternState {
|
|
let pattern_expected = constraints.push_pat_expected_type(PExpected::NoExpectation(expr_type));
|
|
|
|
let mut state = PatternState {
|
|
headers: VecMap::default(),
|
|
vars: Vec::with_capacity(1),
|
|
constraints: Vec::with_capacity(1),
|
|
delayed_is_open_constraints: vec![],
|
|
};
|
|
|
|
constrain_pattern(
|
|
types,
|
|
constraints,
|
|
env,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
pattern_expected,
|
|
&mut state,
|
|
);
|
|
|
|
state
|
|
}
|
|
|
|
/// Generate constraints for a definition with a type signature
|
|
fn constrain_typed_def(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
def: &Def,
|
|
body_con: Constraint,
|
|
annotation: &roc_can::def::Annotation,
|
|
) -> Constraint {
|
|
let expr_var = def.expr_var;
|
|
let expr_type_index = constraints.push_variable(expr_var);
|
|
|
|
let mut def_pattern_state =
|
|
constrain_def_pattern(types, constraints, env, &def.loc_pattern, expr_type_index);
|
|
|
|
def_pattern_state.vars.push(expr_var);
|
|
|
|
let arity = annotation.signature.arity();
|
|
let rigids = &env.rigids;
|
|
let mut ftv = rigids.clone();
|
|
|
|
let InstantiateRigids {
|
|
signature,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
} = instantiate_rigids(
|
|
types,
|
|
constraints,
|
|
&annotation.signature,
|
|
&annotation.introduced_variables,
|
|
&def.loc_pattern,
|
|
&mut ftv,
|
|
&mut def_pattern_state.headers,
|
|
);
|
|
|
|
let env = &mut Env {
|
|
home: env.home,
|
|
resolutions_to_make: vec![],
|
|
rigids: ftv,
|
|
};
|
|
|
|
let signature_index = constraints.push_type(types, signature);
|
|
|
|
let annotation_expected = constraints.push_expected_type(FromAnnotation(
|
|
def.loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
));
|
|
|
|
def_pattern_state.constraints.push(constraints.equal_types(
|
|
expr_type_index,
|
|
annotation_expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
Region::span_across(&annotation.region, &def.loc_expr.region),
|
|
));
|
|
|
|
// when a def is annotated, and its 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, types[signature]) {
|
|
(
|
|
Closure(ClosureData {
|
|
function_type: fn_var,
|
|
closure_type: closure_var,
|
|
return_type: ret_var,
|
|
captured_symbols,
|
|
arguments,
|
|
loc_body,
|
|
name,
|
|
..
|
|
}),
|
|
TypeTag::Function(signature_closure_type, ret_type),
|
|
) => {
|
|
let arg_types = types.get_type_arguments(signature);
|
|
|
|
// NOTE if we ever have problems with the closure, the ignored `_closure_type`
|
|
// is probably a good place to start the investigation!
|
|
|
|
let region = def.loc_expr.region;
|
|
|
|
let loc_body_expr = &**loc_body;
|
|
let mut argument_pattern_state = PatternState {
|
|
headers: VecMap::default(),
|
|
vars: Vec::with_capacity(arguments.len()),
|
|
constraints: Vec::with_capacity(1),
|
|
delayed_is_open_constraints: vec![],
|
|
};
|
|
let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
|
|
let ret_var = *ret_var;
|
|
let closure_var = *closure_var;
|
|
let ret_type_index = constraints.push_type(types, ret_type);
|
|
|
|
vars.push(ret_var);
|
|
vars.push(closure_var);
|
|
|
|
constrain_typed_function_arguments(
|
|
types,
|
|
constraints,
|
|
env,
|
|
def,
|
|
&mut def_pattern_state,
|
|
&mut argument_pattern_state,
|
|
arguments,
|
|
arg_types,
|
|
);
|
|
|
|
let closure_constraint = constrain_closure_size(
|
|
types,
|
|
constraints,
|
|
*name,
|
|
region,
|
|
*fn_var,
|
|
captured_symbols,
|
|
closure_var,
|
|
&mut vars,
|
|
);
|
|
|
|
let body_type = constraints.push_expected_type(FromAnnotation(
|
|
def.loc_pattern.clone(),
|
|
arguments.len(),
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
ret_type_index,
|
|
));
|
|
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_body_expr.region,
|
|
&loc_body_expr.value,
|
|
body_type,
|
|
);
|
|
let ret_constraint = attach_resolution_constraints(constraints, env, ret_constraint);
|
|
|
|
vars.push(*fn_var);
|
|
let defs_constraint = constraints.and_constraint(argument_pattern_state.constraints);
|
|
|
|
let signature_closure_type = {
|
|
let signature_closure_type_index =
|
|
constraints.push_type(types, signature_closure_type);
|
|
constraints.push_expected_type(Expected::FromAnnotation(
|
|
def.loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_closure_type_index,
|
|
))
|
|
};
|
|
let cons = [
|
|
constraints.let_constraint(
|
|
[],
|
|
argument_pattern_state.vars,
|
|
argument_pattern_state.headers,
|
|
defs_constraint,
|
|
ret_constraint,
|
|
// This is a syntactic function, it can be generalized
|
|
Generalizable(true),
|
|
),
|
|
constraints.equal_types_var(
|
|
closure_var,
|
|
signature_closure_type,
|
|
Category::ClosureSize,
|
|
region,
|
|
),
|
|
constraints.store(signature_index, *fn_var, std::file!(), std::line!()),
|
|
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
|
|
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
|
|
closure_constraint,
|
|
];
|
|
|
|
let expr_con = constraints.exists_many(vars, cons);
|
|
|
|
constrain_def_make_constraint(
|
|
constraints,
|
|
new_rigid_variables.into_iter(),
|
|
new_infer_variables.into_iter(),
|
|
expr_con,
|
|
body_con,
|
|
def_pattern_state,
|
|
Generalizable(true),
|
|
)
|
|
}
|
|
|
|
_ => {
|
|
let annotation_expected = constraints.push_expected_type(FromAnnotation(
|
|
def.loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
expr_type_index,
|
|
));
|
|
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
def.loc_expr.region,
|
|
&def.loc_expr.value,
|
|
annotation_expected,
|
|
);
|
|
let expr_con = attach_resolution_constraints(constraints, env, ret_constraint);
|
|
|
|
let generalizable = Generalizable(is_generalizable_expr(&def.loc_expr.value));
|
|
|
|
constrain_def_make_constraint(
|
|
constraints,
|
|
new_rigid_variables.into_iter(),
|
|
new_infer_variables.into_iter(),
|
|
expr_con,
|
|
body_con,
|
|
def_pattern_state,
|
|
generalizable,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn constrain_typed_function_arguments(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
def: &Def,
|
|
def_pattern_state: &mut PatternState,
|
|
argument_pattern_state: &mut PatternState,
|
|
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
|
|
arg_types: Slice<TypeTag>,
|
|
) {
|
|
// 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 it = arguments.iter().zip(arg_types.into_iter()).enumerate();
|
|
for (index, ((pattern_var, annotated_mark, loc_pattern), ann)) in it {
|
|
let pattern_var_index = constraints.push_variable(*pattern_var);
|
|
let ann_index = constraints.push_type(types, ann);
|
|
|
|
if loc_pattern.value.surely_exhaustive() {
|
|
// OPT: we don't need to perform any type-level exhaustiveness checking.
|
|
// Check instead only that the pattern unifies with the annotation type.
|
|
let pattern_expected = constraints.push_pat_expected_type(PExpected::ForReason(
|
|
PReason::TypedArg {
|
|
index: HumanIndex::zero_based(index),
|
|
opt_name: opt_label,
|
|
},
|
|
ann_index,
|
|
loc_pattern.region,
|
|
));
|
|
|
|
constrain_pattern(
|
|
types,
|
|
constraints,
|
|
env,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
pattern_expected,
|
|
argument_pattern_state,
|
|
);
|
|
|
|
{
|
|
// NOTE: because we perform an equality with part of the signature
|
|
// this constraint must be to the def_pattern_state's constraints
|
|
def_pattern_state.vars.push(*pattern_var);
|
|
|
|
let ann_expected =
|
|
constraints.push_expected_type(Expected::NoExpectation(ann_index));
|
|
let pattern_con = constraints.equal_types_var(
|
|
*pattern_var,
|
|
ann_expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
loc_pattern.region,
|
|
);
|
|
|
|
def_pattern_state.constraints.push(pattern_con);
|
|
}
|
|
} else {
|
|
// We need to check the types, and run exhaustiveness checking.
|
|
let &AnnotatedMark {
|
|
annotation_var,
|
|
exhaustive,
|
|
} = annotated_mark;
|
|
|
|
def_pattern_state.vars.push(*pattern_var);
|
|
def_pattern_state.vars.push(annotation_var);
|
|
|
|
{
|
|
// First, solve the type that the pattern is expecting to match in this
|
|
// position.
|
|
let pattern_expected =
|
|
constraints.push_pat_expected_type(PExpected::NoExpectation(pattern_var_index));
|
|
constrain_pattern(
|
|
types,
|
|
constraints,
|
|
env,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
pattern_expected,
|
|
argument_pattern_state,
|
|
);
|
|
}
|
|
|
|
{
|
|
// Store the actual type in a variable.
|
|
let ann_expected =
|
|
constraints.push_expected_type(Expected::NoExpectation(ann_index));
|
|
argument_pattern_state
|
|
.constraints
|
|
.push(constraints.equal_types_var(
|
|
annotation_var,
|
|
ann_expected,
|
|
Category::Storage(file!(), line!()),
|
|
Region::zero(),
|
|
));
|
|
}
|
|
|
|
{
|
|
// let pattern_expected = PExpected::ForReason(
|
|
// PReason::TypedArg {
|
|
// index: HumanIndex::zero_based(index),
|
|
// opt_name: opt_label,
|
|
// },
|
|
// ann.clone(),
|
|
// loc_pattern.region,
|
|
// );
|
|
|
|
// Exhaustiveness-check the type in the pattern against what the
|
|
// annotation wants.
|
|
let sketched_rows = sketch_pattern_to_rows(loc_pattern.region, &loc_pattern.value);
|
|
let category = loc_pattern.value.category();
|
|
let expected = constraints.push_pat_expected_type(PExpected::ForReason(
|
|
PReason::TypedArg {
|
|
index: HumanIndex::zero_based(index),
|
|
opt_name: opt_label,
|
|
},
|
|
pattern_var_index,
|
|
loc_pattern.region,
|
|
));
|
|
let exhaustive_constraint = constraints.exhaustive(
|
|
annotation_var,
|
|
loc_pattern.region,
|
|
Err((category, expected)),
|
|
sketched_rows,
|
|
ExhaustiveContext::BadArg,
|
|
exhaustive,
|
|
);
|
|
argument_pattern_state
|
|
.constraints
|
|
.push(exhaustive_constraint)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn constrain_typed_function_arguments_simple(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
symbol: Symbol,
|
|
def_pattern_state: &mut PatternState,
|
|
argument_pattern_state: &mut PatternState,
|
|
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
|
|
arg_types: Slice<TypeTag>,
|
|
) {
|
|
let it = arguments.iter().zip(arg_types.into_iter()).enumerate();
|
|
for (index, ((pattern_var, annotated_mark, loc_pattern), ann)) in it {
|
|
let pattern_var_index = constraints.push_variable(*pattern_var);
|
|
let ann_index = constraints.push_type(types, ann);
|
|
|
|
if loc_pattern.value.surely_exhaustive() {
|
|
// OPT: we don't need to perform any type-level exhaustiveness checking.
|
|
// Check instead only that the pattern unifies with the annotation type.
|
|
let pattern_expected = constraints.push_pat_expected_type(PExpected::ForReason(
|
|
PReason::TypedArg {
|
|
index: HumanIndex::zero_based(index),
|
|
opt_name: Some(symbol),
|
|
},
|
|
ann_index,
|
|
loc_pattern.region,
|
|
));
|
|
|
|
constrain_pattern(
|
|
types,
|
|
constraints,
|
|
env,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
pattern_expected,
|
|
argument_pattern_state,
|
|
);
|
|
|
|
{
|
|
// NOTE: because we perform an equality with part of the signature
|
|
// this constraint must be to the def_pattern_state's constraints
|
|
def_pattern_state.vars.push(*pattern_var);
|
|
|
|
let ann_expected =
|
|
constraints.push_expected_type(Expected::NoExpectation(ann_index));
|
|
let pattern_con = constraints.equal_types_var(
|
|
*pattern_var,
|
|
ann_expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
loc_pattern.region,
|
|
);
|
|
|
|
def_pattern_state.constraints.push(pattern_con);
|
|
}
|
|
} else {
|
|
// We need to check the types, and run exhaustiveness checking.
|
|
let &AnnotatedMark {
|
|
annotation_var,
|
|
exhaustive,
|
|
} = annotated_mark;
|
|
|
|
def_pattern_state.vars.push(*pattern_var);
|
|
def_pattern_state.vars.push(annotation_var);
|
|
|
|
{
|
|
// First, solve the type that the pattern is expecting to match in this
|
|
// position.
|
|
let pattern_expected =
|
|
constraints.push_pat_expected_type(PExpected::NoExpectation(pattern_var_index));
|
|
constrain_pattern(
|
|
types,
|
|
constraints,
|
|
env,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
pattern_expected,
|
|
argument_pattern_state,
|
|
);
|
|
}
|
|
|
|
{
|
|
// Store the actual type in a variable.
|
|
let expected_annotation =
|
|
constraints.push_expected_type(Expected::NoExpectation(ann_index));
|
|
argument_pattern_state
|
|
.constraints
|
|
.push(constraints.equal_types_var(
|
|
annotation_var,
|
|
expected_annotation,
|
|
Category::Storage(file!(), line!()),
|
|
Region::zero(),
|
|
));
|
|
}
|
|
|
|
{
|
|
// Exhaustiveness-check the type in the pattern against what the
|
|
// annotation wants.
|
|
let sketched_rows = sketch_pattern_to_rows(loc_pattern.region, &loc_pattern.value);
|
|
let category = loc_pattern.value.category();
|
|
let expected = constraints.push_pat_expected_type(PExpected::ForReason(
|
|
PReason::TypedArg {
|
|
index: HumanIndex::zero_based(index),
|
|
opt_name: Some(symbol),
|
|
},
|
|
pattern_var_index,
|
|
loc_pattern.region,
|
|
));
|
|
let exhaustive_constraint = constraints.exhaustive(
|
|
annotation_var,
|
|
loc_pattern.region,
|
|
Err((category, expected)),
|
|
sketched_rows,
|
|
ExhaustiveContext::BadArg,
|
|
exhaustive,
|
|
);
|
|
argument_pattern_state
|
|
.constraints
|
|
.push(exhaustive_constraint)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn attach_resolution_constraints(
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
constraint: Constraint,
|
|
) -> Constraint {
|
|
let resolution_constrs =
|
|
constraints.and_constraint(env.resolutions_to_make.drain(..).map(Constraint::Resolve));
|
|
constraints.and_constraint([constraint, resolution_constrs])
|
|
}
|
|
|
|
fn constrain_def(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
def: &Def,
|
|
body_con: Constraint,
|
|
) -> Constraint {
|
|
match &def.annotation {
|
|
Some(annotation) => constrain_typed_def(types, constraints, env, def, body_con, annotation),
|
|
None => {
|
|
let expr_var = def.expr_var;
|
|
let expr_type_index = constraints.push_variable(expr_var);
|
|
|
|
let mut def_pattern_state =
|
|
constrain_def_pattern(types, constraints, env, &def.loc_pattern, expr_type_index);
|
|
|
|
def_pattern_state.vars.push(expr_var);
|
|
// no annotation, so no extra work with rigids
|
|
|
|
let expected = constraints.push_expected_type(NoExpectation(expr_type_index));
|
|
let expr_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
def.loc_expr.region,
|
|
&def.loc_expr.value,
|
|
expected,
|
|
);
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
|
|
let generalizable = Generalizable(is_generalizable_expr(&def.loc_expr.value));
|
|
|
|
constrain_def_make_constraint(
|
|
constraints,
|
|
std::iter::empty(),
|
|
std::iter::empty(),
|
|
expr_con,
|
|
body_con,
|
|
def_pattern_state,
|
|
generalizable,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a let-constraint for a non-recursive def.
|
|
/// Recursive defs should always use `constrain_recursive_defs`.
|
|
pub(crate) fn constrain_def_make_constraint(
|
|
constraints: &mut Constraints,
|
|
annotation_rigid_variables: impl Iterator<Item = Variable>,
|
|
annotation_infer_variables: impl Iterator<Item = Variable>,
|
|
def_expr_con: Constraint,
|
|
after_def_con: Constraint,
|
|
def_pattern_state: PatternState,
|
|
generalizable: Generalizable,
|
|
) -> Constraint {
|
|
let all_flex_variables = (def_pattern_state.vars.into_iter()).chain(annotation_infer_variables);
|
|
|
|
let pattern_constraints = constraints.and_constraint(def_pattern_state.constraints);
|
|
let def_pattern_and_body_con = constraints.and_constraint([pattern_constraints, def_expr_con]);
|
|
|
|
constraints.let_constraint(
|
|
annotation_rigid_variables,
|
|
all_flex_variables,
|
|
def_pattern_state.headers,
|
|
def_pattern_and_body_con,
|
|
after_def_con,
|
|
generalizable,
|
|
)
|
|
}
|
|
|
|
fn constrain_value_def_make_constraint(
|
|
constraints: &mut Constraints,
|
|
new_rigid_variables: Vec<Variable>,
|
|
new_infer_variables: Vec<Variable>,
|
|
expr_con: Constraint,
|
|
body_con: Constraint,
|
|
symbol: Loc<Symbol>,
|
|
expr_var: Variable,
|
|
expr_type: TypeOrVar,
|
|
generalizable: Generalizable,
|
|
) -> Constraint {
|
|
let def_con = constraints.let_constraint(
|
|
[],
|
|
new_infer_variables,
|
|
[], // empty, because our functions have no arguments!
|
|
Constraint::True,
|
|
expr_con,
|
|
generalizable,
|
|
);
|
|
|
|
let headers = [(symbol.value, Loc::at(symbol.region, expr_type))];
|
|
|
|
constraints.let_constraint(
|
|
new_rigid_variables,
|
|
[expr_var],
|
|
headers,
|
|
def_con,
|
|
body_con,
|
|
generalizable,
|
|
)
|
|
}
|
|
|
|
fn constrain_function_def_make_constraint(
|
|
constraints: &mut Constraints,
|
|
new_rigid_variables: Vec<Variable>,
|
|
new_infer_variables: Vec<Variable>,
|
|
expr_con: Constraint,
|
|
body_con: Constraint,
|
|
def_pattern_state: PatternState,
|
|
) -> Constraint {
|
|
let and_constraint = constraints.and_constraint(def_pattern_state.constraints);
|
|
|
|
let def_con = constraints.let_constraint(
|
|
[],
|
|
new_infer_variables,
|
|
[], // empty, because our functions have no arguments!
|
|
and_constraint,
|
|
expr_con,
|
|
Generalizable(true),
|
|
);
|
|
|
|
constraints.let_constraint(
|
|
new_rigid_variables,
|
|
def_pattern_state.vars,
|
|
def_pattern_state.headers,
|
|
def_con,
|
|
body_con,
|
|
Generalizable(true),
|
|
)
|
|
}
|
|
|
|
fn constrain_closure_size(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
name: Symbol,
|
|
region: Region,
|
|
ambient_function: Variable,
|
|
captured_symbols: &[(Symbol, Variable)],
|
|
closure_var: Variable,
|
|
variables: &mut Vec<Variable>,
|
|
) -> Constraint {
|
|
debug_assert!(variables.iter().any(|s| *s == closure_var));
|
|
|
|
let mut captured_types = Vec::with_capacity(captured_symbols.len());
|
|
let mut captured_symbols_constraints = Vec::with_capacity(captured_symbols.len());
|
|
|
|
for (symbol, var) in captured_symbols {
|
|
// make sure the variable is registered
|
|
variables.push(*var);
|
|
|
|
// this symbol is captured, so it must be part of the closure type
|
|
captured_types.push(Type::Variable(*var));
|
|
|
|
// make the variable equal to the looked-up type of symbol
|
|
let store_var_index = constraints.push_variable(*var);
|
|
let store_into = constraints.push_expected_type(Expected::NoExpectation(store_var_index));
|
|
captured_symbols_constraints.push(constraints.lookup(*symbol, store_into, Region::zero()));
|
|
}
|
|
|
|
let finalizer = {
|
|
// pick a more efficient representation if we don't actually capture anything
|
|
let closure_type = {
|
|
let typ = types.from_old_type(&Type::ClosureTag {
|
|
name,
|
|
captures: captured_types,
|
|
ambient_function,
|
|
});
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let clos_type = constraints.push_expected_type(NoExpectation(closure_type));
|
|
constraints.equal_types_var(closure_var, clos_type, Category::ClosureSize, region)
|
|
};
|
|
|
|
captured_symbols_constraints.push(finalizer);
|
|
|
|
constraints.and_constraint(captured_symbols_constraints)
|
|
}
|
|
|
|
pub struct InstantiateRigids {
|
|
pub signature: Index<TypeTag>,
|
|
pub new_rigid_variables: Vec<Variable>,
|
|
pub new_infer_variables: Vec<Variable>,
|
|
}
|
|
|
|
fn instantiate_rigids(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
annotation: &Type,
|
|
introduced_vars: &IntroducedVariables,
|
|
loc_pattern: &Loc<Pattern>,
|
|
ftv: &mut MutMap<Lowercase, Variable>, // rigids defined before the current annotation
|
|
headers: &mut VecMap<Symbol, Loc<TypeOrVar>>,
|
|
) -> InstantiateRigids {
|
|
let mut annotation = annotation.clone();
|
|
let mut new_rigid_variables: Vec<Variable> = Vec::new();
|
|
|
|
let mut rigid_substitution: MutMap<Variable, Variable> = MutMap::default();
|
|
for named in introduced_vars.iter_named() {
|
|
use std::collections::hash_map::Entry::*;
|
|
|
|
match ftv.entry(named.name().clone()) {
|
|
Occupied(occupied) => {
|
|
let existing_rigid = occupied.get();
|
|
rigid_substitution.insert(named.variable(), *existing_rigid);
|
|
}
|
|
Vacant(vacant) => {
|
|
// It's possible to use this rigid in nested defs
|
|
vacant.insert(named.variable());
|
|
new_rigid_variables.push(named.variable());
|
|
}
|
|
}
|
|
}
|
|
|
|
// wildcards are always freshly introduced in this annotation
|
|
new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value));
|
|
|
|
// lambda set vars are always freshly introduced in this annotation
|
|
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
|
|
|
|
// ext-infer vars are always freshly introduced in this annotation
|
|
new_rigid_variables.extend(introduced_vars.infer_ext_in_output.iter().copied());
|
|
|
|
let new_infer_variables: Vec<Variable> =
|
|
introduced_vars.inferred.iter().map(|v| v.value).collect();
|
|
|
|
// Instantiate rigid variables
|
|
if !rigid_substitution.is_empty() {
|
|
annotation.substitute_variables(&rigid_substitution);
|
|
}
|
|
let annotation_index = types.from_old_type(&annotation);
|
|
|
|
// TODO investigate when we can skip this. It seems to only be required for correctness
|
|
// for recursive functions. For non-recursive functions the final type is correct, but
|
|
// alias information is sometimes lost
|
|
//
|
|
// Skipping all of this cloning here would be neat!
|
|
let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation);
|
|
if let Pattern::Identifier(symbol) = loc_pattern.value {
|
|
let annotation_index = constraints.push_type(types, annotation_index);
|
|
headers.insert(symbol, Loc::at(loc_pattern.region, annotation_index));
|
|
} else if let Some(new_headers) = crate::pattern::headers_from_annotation(
|
|
types,
|
|
constraints,
|
|
&loc_pattern.value,
|
|
&loc_annotation_ref,
|
|
) {
|
|
headers.extend(new_headers)
|
|
}
|
|
|
|
InstantiateRigids {
|
|
signature: annotation_index,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
}
|
|
}
|
|
|
|
fn instantiate_rigids_simple(
|
|
types: &mut Types,
|
|
annotation: &Type,
|
|
introduced_vars: &IntroducedVariables,
|
|
ftv: &mut MutMap<Lowercase, Variable>, // rigids defined before the current annotation
|
|
) -> InstantiateRigids {
|
|
let mut annotation = annotation.clone();
|
|
let mut new_rigid_variables: Vec<Variable> = Vec::new();
|
|
|
|
let mut rigid_substitution: MutMap<Variable, Variable> = MutMap::default();
|
|
for named in introduced_vars.iter_named() {
|
|
use std::collections::hash_map::Entry::*;
|
|
|
|
match ftv.entry(named.name().clone()) {
|
|
Occupied(occupied) => {
|
|
let existing_rigid = occupied.get();
|
|
rigid_substitution.insert(named.variable(), *existing_rigid);
|
|
}
|
|
Vacant(vacant) => {
|
|
// It's possible to use this rigid in nested defs
|
|
vacant.insert(named.variable());
|
|
new_rigid_variables.push(named.variable());
|
|
}
|
|
}
|
|
}
|
|
|
|
// wildcards are always freshly introduced in this annotation
|
|
new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value));
|
|
|
|
// lambda set vars are always freshly introduced in this annotation
|
|
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
|
|
|
|
// ext-infer vars are always freshly introduced in this annotation
|
|
new_rigid_variables.extend(introduced_vars.infer_ext_in_output.iter().copied());
|
|
|
|
let new_infer_variables: Vec<Variable> =
|
|
introduced_vars.inferred.iter().map(|v| v.value).collect();
|
|
|
|
// Instantiate rigid variables
|
|
if !rigid_substitution.is_empty() {
|
|
annotation.substitute_variables(&rigid_substitution);
|
|
}
|
|
|
|
InstantiateRigids {
|
|
signature: types.from_old_type(&annotation),
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
}
|
|
}
|
|
|
|
fn constrain_recursive_declarations(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
declarations: &Declarations,
|
|
range: Range<usize>,
|
|
body_con: Constraint,
|
|
cycle_mark: IllegalCycleMark,
|
|
) -> Constraint {
|
|
rec_defs_help_simple(
|
|
types,
|
|
constraints,
|
|
env,
|
|
declarations,
|
|
range,
|
|
body_con,
|
|
cycle_mark,
|
|
)
|
|
}
|
|
|
|
fn constraint_recursive_function(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
declarations: &Declarations,
|
|
index: usize,
|
|
function_def_index: Index<Loc<FunctionDef>>,
|
|
rigid_info: &mut Info,
|
|
flex_info: &mut Info,
|
|
) {
|
|
let loc_expr = &declarations.expressions[index];
|
|
let loc_symbol = declarations.symbols[index];
|
|
let expr_var = declarations.variables[index];
|
|
let opt_annotation = &declarations.annotations[index];
|
|
|
|
let loc_function_def = &declarations.function_bodies[function_def_index.index()];
|
|
let function_def = &loc_function_def.value;
|
|
|
|
match opt_annotation {
|
|
None => {
|
|
let expr_type_index = constraints.push_variable(expr_var);
|
|
|
|
let expected_expr = constraints.push_expected_type(NoExpectation(expr_type_index));
|
|
let expr_con = constrain_untyped_closure(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_function_def.region,
|
|
expected_expr,
|
|
expr_var,
|
|
function_def.closure_type,
|
|
function_def.return_type,
|
|
&function_def.arguments,
|
|
loc_expr,
|
|
&function_def.captured_symbols,
|
|
loc_symbol.value,
|
|
);
|
|
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
let def_con = expr_con;
|
|
|
|
flex_info.vars = vec![expr_var];
|
|
flex_info.constraints.push(def_con);
|
|
flex_info.def_types.insert(
|
|
loc_symbol.value,
|
|
Loc::at(loc_symbol.region, expr_type_index),
|
|
);
|
|
}
|
|
|
|
Some(annotation) => {
|
|
let arity = annotation.signature.arity();
|
|
let rigids = &env.rigids;
|
|
let mut ftv = rigids.clone();
|
|
|
|
let InstantiateRigids {
|
|
signature,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
} = instantiate_rigids_simple(
|
|
types,
|
|
&annotation.signature,
|
|
&annotation.introduced_variables,
|
|
&mut ftv,
|
|
);
|
|
|
|
let loc_pattern = Loc::at(loc_symbol.region, Pattern::Identifier(loc_symbol.value));
|
|
|
|
let signature_index = constraints.push_type(types, signature);
|
|
|
|
let annotation_expected = constraints.push_expected_type(FromAnnotation(
|
|
loc_pattern,
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
));
|
|
|
|
let (arg_types, _signature_closure_type, ret_type) = match types[signature] {
|
|
TypeTag::Function(signature_closure_type, ret_type) => (
|
|
types.get_type_arguments(signature),
|
|
signature_closure_type,
|
|
ret_type,
|
|
),
|
|
_ => todo!("TODO {:?}", (loc_symbol, &signature)),
|
|
};
|
|
|
|
let region = loc_function_def.region;
|
|
|
|
let loc_body_expr = loc_expr;
|
|
let mut argument_pattern_state = PatternState {
|
|
headers: VecMap::default(),
|
|
vars: Vec::with_capacity(function_def.arguments.len()),
|
|
constraints: Vec::with_capacity(1),
|
|
delayed_is_open_constraints: vec![],
|
|
};
|
|
let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
|
|
let ret_var = function_def.return_type;
|
|
let closure_var = function_def.closure_type;
|
|
let ret_type_index = constraints.push_type(types, ret_type);
|
|
|
|
vars.push(ret_var);
|
|
vars.push(closure_var);
|
|
|
|
let mut def_pattern_state = PatternState::default();
|
|
|
|
def_pattern_state.headers.insert(
|
|
loc_symbol.value,
|
|
Loc {
|
|
region,
|
|
// TODO coalesce with other `signature_index`.
|
|
// This doesn't yet work; needs investigation as to why.
|
|
// My guess is that when types SoA lands, this might just resolve itself, since
|
|
// types will be composed from variables to begin with.
|
|
value: {
|
|
let typ =
|
|
types.clone_with_variable_substitutions(signature, &Default::default());
|
|
constraints.push_type(types, typ)
|
|
},
|
|
},
|
|
);
|
|
|
|
constrain_typed_function_arguments_simple(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_symbol.value,
|
|
&mut def_pattern_state,
|
|
&mut argument_pattern_state,
|
|
&function_def.arguments,
|
|
arg_types,
|
|
);
|
|
|
|
let pattern_types = types
|
|
.from_old_type_slice(function_def.arguments.iter().map(|a| Type::Variable(a.0)));
|
|
|
|
let closure_constraint = constrain_closure_size(
|
|
types,
|
|
constraints,
|
|
loc_symbol.value,
|
|
region,
|
|
expr_var,
|
|
&function_def.captured_symbols,
|
|
closure_var,
|
|
&mut vars,
|
|
);
|
|
|
|
let fn_type = {
|
|
// TODO(types-soa) optimize for Variable
|
|
let lambda_set = types.from_old_type(&Type::Variable(closure_var));
|
|
let typ = types.function(pattern_types, lambda_set, ret_type);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
|
|
let expr_con = {
|
|
let expected = constraints.push_expected_type(NoExpectation(ret_type_index));
|
|
constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_body_expr.region,
|
|
&loc_body_expr.value,
|
|
expected,
|
|
)
|
|
};
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
|
|
vars.push(expr_var);
|
|
|
|
let state_constraints = constraints.and_constraint(argument_pattern_state.constraints);
|
|
let cons = [
|
|
constraints.let_constraint(
|
|
[],
|
|
argument_pattern_state.vars,
|
|
argument_pattern_state.headers,
|
|
state_constraints,
|
|
expr_con,
|
|
// Syntactic function can be generalized
|
|
Generalizable(true),
|
|
),
|
|
constraints.equal_types(fn_type, annotation_expected, Category::Lambda, region),
|
|
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
|
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
|
|
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
|
|
closure_constraint,
|
|
];
|
|
|
|
let and_constraint = constraints.and_constraint(cons);
|
|
let def_con = constraints.exists(vars, and_constraint);
|
|
|
|
rigid_info.vars.extend(&new_rigid_variables);
|
|
flex_info.vars.extend(&new_infer_variables);
|
|
|
|
rigid_info.constraints.push({
|
|
// Solve the body of the recursive function, making sure it lines up with the
|
|
// signature.
|
|
//
|
|
// This happens when we're checking that the def of a recursive function actually
|
|
// aligns with what the (mutually-)recursive signature says, so finish
|
|
// generalization of the function.
|
|
let rigids = new_rigid_variables;
|
|
let flex = def_pattern_state
|
|
.vars
|
|
.into_iter()
|
|
.chain(new_infer_variables);
|
|
|
|
constraints.let_constraint(
|
|
rigids,
|
|
flex,
|
|
// Although we will have already introduced the headers of the def in the
|
|
// outermost scope when we introduced the rigid variables, we now re-introduce
|
|
// them to force an occurs check and appropriate fixing, since we might end up
|
|
// inferring recursive types at inference variable points. E.g.
|
|
//
|
|
// f : _ -> _
|
|
// f = \F c -> F (List.map f c)
|
|
//
|
|
// TODO: I (Ayaz) believe we can considerably simplify all this.
|
|
def_pattern_state.headers.clone(),
|
|
def_con,
|
|
Constraint::True,
|
|
Generalizable(true),
|
|
)
|
|
});
|
|
rigid_info.def_types.extend(def_pattern_state.headers);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn rec_defs_help_simple(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
declarations: &Declarations,
|
|
range: Range<usize>,
|
|
body_con: Constraint,
|
|
cycle_mark: IllegalCycleMark,
|
|
) -> Constraint {
|
|
let length = range.end - range.start;
|
|
|
|
// We partition recursive defs into three buckets:
|
|
// rigid: those with fully-elaborated type annotations (no inference vars), e.g. a -> b
|
|
// hybrid: those with type annotations containing an inference variable, e.g. _ -> b
|
|
// flex: those without a type annotation
|
|
let mut rigid_info = Info::with_capacity(length);
|
|
let mut hybrid_and_flex_info = Info::with_capacity(length);
|
|
|
|
let mut loc_symbols = Vec::with_capacity(length);
|
|
let mut expr_regions = Vec::with_capacity(length);
|
|
|
|
// Rec defs are generalizable only if everything in the cycle is generalizable.
|
|
let generalizable = {
|
|
let generalizable = range.clone().all(|i| match declarations.declarations[i] {
|
|
DeclarationTag::Value => {
|
|
let loc_expr = &declarations.expressions[i];
|
|
is_generalizable_expr(&loc_expr.value)
|
|
}
|
|
_ => true, // this must be a function
|
|
});
|
|
// TODO(weakening)
|
|
#[allow(clippy::logic_bug)]
|
|
Generalizable(generalizable || true)
|
|
};
|
|
|
|
for index in range {
|
|
// Clear the rigids from the previous iteration.
|
|
// rigids are not shared between top-level definitions
|
|
env.rigids.clear();
|
|
|
|
let loc_symbol = declarations.symbols[index];
|
|
loc_symbols.push((loc_symbol.value, loc_symbol.region));
|
|
|
|
match declarations.declarations[index] {
|
|
DeclarationTag::Value => {
|
|
let expr_var = declarations.variables[index];
|
|
let expr_var_index = constraints.push_variable(expr_var);
|
|
let opt_annotation = &declarations.annotations[index];
|
|
|
|
let loc_expr = &declarations.expressions[index];
|
|
expr_regions.push(loc_expr.region);
|
|
|
|
match opt_annotation {
|
|
None => {
|
|
let expected =
|
|
constraints.push_expected_type(NoExpectation(expr_var_index));
|
|
let expr_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected,
|
|
);
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
|
|
let def_con = expr_con;
|
|
|
|
hybrid_and_flex_info.vars.push(expr_var);
|
|
hybrid_and_flex_info.constraints.push(def_con);
|
|
hybrid_and_flex_info
|
|
.def_types
|
|
.insert(loc_symbol.value, Loc::at(loc_symbol.region, expr_var_index));
|
|
}
|
|
Some(annotation) => {
|
|
let arity = annotation.signature.arity();
|
|
let rigids = &env.rigids;
|
|
let mut ftv = rigids.clone();
|
|
|
|
let InstantiateRigids {
|
|
signature,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
} = instantiate_rigids_simple(
|
|
types,
|
|
&annotation.signature,
|
|
&annotation.introduced_variables,
|
|
&mut ftv,
|
|
);
|
|
|
|
let loc_pattern =
|
|
Loc::at(loc_symbol.region, Pattern::Identifier(loc_symbol.value));
|
|
|
|
let is_hybrid = !new_infer_variables.is_empty();
|
|
|
|
hybrid_and_flex_info.vars.extend(new_infer_variables);
|
|
|
|
let signature_index = constraints.push_type(types, signature);
|
|
|
|
let annotation_expected = FromAnnotation(
|
|
loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
);
|
|
|
|
let expected = constraints.push_expected_type(annotation_expected);
|
|
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected,
|
|
);
|
|
let ret_constraint =
|
|
attach_resolution_constraints(constraints, env, ret_constraint);
|
|
|
|
let cons = [
|
|
ret_constraint,
|
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
|
constraints.store(
|
|
signature_index,
|
|
expr_var,
|
|
std::file!(),
|
|
std::line!(),
|
|
),
|
|
];
|
|
let def_con = constraints.and_constraint(cons);
|
|
|
|
let loc_type = Loc::at(loc_symbol.region, signature_index);
|
|
if is_hybrid {
|
|
hybrid_and_flex_info.vars.extend(&new_rigid_variables);
|
|
hybrid_and_flex_info.constraints.push(def_con);
|
|
hybrid_and_flex_info
|
|
.def_types
|
|
.insert(loc_symbol.value, loc_type);
|
|
} else {
|
|
rigid_info.vars.extend(&new_rigid_variables);
|
|
|
|
rigid_info.constraints.push(constraints.let_constraint(
|
|
new_rigid_variables,
|
|
[expr_var],
|
|
[], // no headers introduced (at this level)
|
|
def_con,
|
|
Constraint::True,
|
|
generalizable,
|
|
));
|
|
rigid_info.def_types.insert(loc_symbol.value, loc_type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DeclarationTag::Recursive(f_index) | DeclarationTag::TailRecursive(f_index) => {
|
|
expr_regions.push(declarations.function_bodies[f_index.index()].region);
|
|
|
|
constraint_recursive_function(
|
|
types,
|
|
constraints,
|
|
env,
|
|
declarations,
|
|
index,
|
|
f_index,
|
|
&mut rigid_info,
|
|
&mut hybrid_and_flex_info,
|
|
);
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
// Strategy for recursive defs:
|
|
//
|
|
// 1. Let-generalize all rigid annotations. These are the source of truth we'll solve
|
|
// everything else with. If there are circular type errors here, they will be caught
|
|
// during the let-generalization.
|
|
//
|
|
// 2. Introduce all symbols of the flex + hybrid defs, but don't generalize them yet.
|
|
// Now, solve those defs' bodies. This way, when checking something like
|
|
// f = \x -> f [x]
|
|
// we introduce `f: b -> c`, then constrain the call `f [x]`,
|
|
// forcing `b -> c ~ List b -> c` and correctly picking up a recursion error.
|
|
// Had we generalized `b -> c`, the call `f [x]` would have been generalized, and this
|
|
// error would not be found.
|
|
//
|
|
// - This works just as well for mutually recursive defs.
|
|
// - For hybrid defs, we also ensure solved types agree with what the
|
|
// elaborated parts of their type annotations demand.
|
|
//
|
|
// 3. Now properly let-generalize the flex + hybrid defs, since we now know their types and
|
|
// that they don't have circular type errors.
|
|
//
|
|
// 4. Solve the bodies of the typed body defs, and check that they agree the types of the type
|
|
// annotation.
|
|
//
|
|
// 5. Solve the rest of the program that happens after this recursive def block.
|
|
|
|
// 2. Solve untyped defs without generalization of their symbols.
|
|
let untyped_body_constraints = constraints.and_constraint(hybrid_and_flex_info.constraints);
|
|
let untyped_def_symbols_constr = constraints.let_constraint(
|
|
[],
|
|
[],
|
|
hybrid_and_flex_info.def_types.clone(),
|
|
Constraint::True,
|
|
untyped_body_constraints,
|
|
generalizable,
|
|
);
|
|
|
|
// an extra constraint that propagates information to the solver to check for invalid recursion
|
|
// and generate a good error message there.
|
|
let cycle_constraint = constraints.check_cycle(loc_symbols, expr_regions, cycle_mark);
|
|
|
|
// 4 + 5. Solve the typed body defs, and the rest of the program.
|
|
let typed_body_constraints = constraints.and_constraint(rigid_info.constraints);
|
|
let typed_body_and_final_constr =
|
|
constraints.and_constraint([typed_body_constraints, cycle_constraint, body_con]);
|
|
|
|
// 3. Properly generalize untyped defs after solving them.
|
|
let inner = constraints.let_constraint(
|
|
[],
|
|
hybrid_and_flex_info.vars,
|
|
hybrid_and_flex_info.def_types,
|
|
untyped_def_symbols_constr,
|
|
typed_body_and_final_constr,
|
|
generalizable,
|
|
);
|
|
|
|
// 1. Let-generalize annotations we know.
|
|
constraints.let_constraint(
|
|
rigid_info.vars,
|
|
[],
|
|
rigid_info.def_types,
|
|
Constraint::True,
|
|
inner,
|
|
generalizable,
|
|
)
|
|
}
|
|
|
|
/// A let-bound expression is generalizable if it is
|
|
/// - a syntactic function under an opaque wrapper
|
|
/// - a number literal under an opaque wrapper
|
|
fn is_generalizable_expr(mut expr: &Expr) -> bool {
|
|
loop {
|
|
match expr {
|
|
Num(..) | Int(..) | Float(..) => return true,
|
|
Closure(_) => return true,
|
|
OpaqueRef { argument, .. } => expr = &argument.1.value,
|
|
Str(_) | List { .. } | SingleQuote(_, _, _, _) | When { .. } | If { .. }
|
|
| LetRec(_, _, _)
|
|
| LetNonRec(_, _)
|
|
=> {
|
|
return false
|
|
}
|
|
// TODO(weakening)
|
|
Var(_, _)
|
|
| AbilityMember(_, _, _)
|
|
| Call(_, _, _)
|
|
| RunLowLevel { .. }
|
|
| ForeignCall { .. }
|
|
| Expr::Record { .. }
|
|
| EmptyRecord
|
|
| Crash { .. }
|
|
| Access { .. }
|
|
| Accessor(_)
|
|
| Update { .. }
|
|
| Tag { .. }
|
|
| ZeroArgumentTag { .. }
|
|
| OpaqueWrapFunction(_)
|
|
| Expect { .. }
|
|
| ExpectFx { .. }
|
|
| Dbg { .. }
|
|
| TypedHole(_)
|
|
| RuntimeError(_) => return true,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn constrain_recursive_defs(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
defs: &[Def],
|
|
body_con: Constraint,
|
|
cycle_mark: IllegalCycleMark,
|
|
) -> Constraint {
|
|
rec_defs_help(types, constraints, env, defs, body_con, cycle_mark)
|
|
}
|
|
|
|
fn rec_defs_help(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
defs: &[Def],
|
|
body_con: Constraint,
|
|
cycle_mark: IllegalCycleMark,
|
|
) -> Constraint {
|
|
// We partition recursive defs into three buckets:
|
|
// rigid: those with fully-elaborated type annotations (no inference vars), e.g. a -> b
|
|
// hybrid: those with type annotations containing an inference variable, e.g. _ -> b
|
|
// flex: those without a type annotation
|
|
let mut rigid_info = Info::with_capacity(defs.len());
|
|
let mut hybrid_and_flex_info = Info::with_capacity(defs.len());
|
|
|
|
// Rec defs are generalizable only if everything in the cycle is generalizable.
|
|
let generalizable = {
|
|
let generalizable = defs
|
|
.iter()
|
|
.all(|d| is_generalizable_expr(&d.loc_expr.value));
|
|
// TODO(weakening)
|
|
#[allow(clippy::logic_bug)]
|
|
Generalizable(generalizable || true)
|
|
};
|
|
|
|
for def in defs {
|
|
let expr_var = def.expr_var;
|
|
let expr_type_index = constraints.push_variable(expr_var);
|
|
|
|
let mut def_pattern_state =
|
|
constrain_def_pattern(types, constraints, env, &def.loc_pattern, expr_type_index);
|
|
|
|
def_pattern_state.vars.push(expr_var);
|
|
|
|
match &def.annotation {
|
|
None => {
|
|
let expected = constraints.push_expected_type(NoExpectation(expr_type_index));
|
|
let expr_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
def.loc_expr.region,
|
|
&def.loc_expr.value,
|
|
expected,
|
|
);
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
|
|
let def_con = expr_con;
|
|
|
|
hybrid_and_flex_info.vars.extend(def_pattern_state.vars);
|
|
hybrid_and_flex_info.constraints.push(def_con);
|
|
hybrid_and_flex_info
|
|
.def_types
|
|
.extend(def_pattern_state.headers);
|
|
}
|
|
|
|
Some(annotation) => {
|
|
let arity = annotation.signature.arity();
|
|
let mut ftv = env.rigids.clone();
|
|
|
|
let InstantiateRigids {
|
|
signature,
|
|
new_rigid_variables,
|
|
new_infer_variables,
|
|
} = instantiate_rigids(
|
|
types,
|
|
constraints,
|
|
&annotation.signature,
|
|
&annotation.introduced_variables,
|
|
&def.loc_pattern,
|
|
&mut ftv,
|
|
&mut def_pattern_state.headers,
|
|
);
|
|
|
|
let is_hybrid = !new_infer_variables.is_empty();
|
|
|
|
hybrid_and_flex_info.vars.extend(new_infer_variables);
|
|
|
|
let signature_index = constraints.push_type(types, signature);
|
|
|
|
let annotation_expected = FromAnnotation(
|
|
def.loc_pattern.clone(),
|
|
arity,
|
|
AnnotationSource::TypedBody {
|
|
region: annotation.region,
|
|
},
|
|
signature_index,
|
|
);
|
|
|
|
// 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, types[signature]) {
|
|
(
|
|
Closure(ClosureData {
|
|
function_type: fn_var,
|
|
closure_type: closure_var,
|
|
return_type: ret_var,
|
|
captured_symbols,
|
|
arguments,
|
|
loc_body,
|
|
name,
|
|
..
|
|
}),
|
|
TypeTag::Function(_closure_type, ret_type),
|
|
) => {
|
|
// NOTE if we ever have trouble with closure type unification, the ignored
|
|
// `_closure_type` here is a good place to start investigating
|
|
|
|
let arg_types = types.get_type_arguments(signature);
|
|
|
|
let expected = annotation_expected;
|
|
let region = def.loc_expr.region;
|
|
|
|
let loc_body_expr = &**loc_body;
|
|
let mut state = PatternState {
|
|
headers: VecMap::default(),
|
|
vars: Vec::with_capacity(arguments.len()),
|
|
constraints: Vec::with_capacity(1),
|
|
delayed_is_open_constraints: vec![],
|
|
};
|
|
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
|
|
let ret_var = *ret_var;
|
|
let closure_var = *closure_var;
|
|
let ret_type_index = constraints.push_type(types, ret_type);
|
|
|
|
vars.push(ret_var);
|
|
vars.push(closure_var);
|
|
|
|
constrain_typed_function_arguments(
|
|
types,
|
|
constraints,
|
|
env,
|
|
def,
|
|
&mut def_pattern_state,
|
|
&mut state,
|
|
arguments,
|
|
arg_types,
|
|
);
|
|
let pattern_types = types
|
|
.from_old_type_slice(arguments.iter().map(|a| Type::Variable(a.0)));
|
|
|
|
let closure_constraint = constrain_closure_size(
|
|
types,
|
|
constraints,
|
|
*name,
|
|
region,
|
|
*fn_var,
|
|
captured_symbols,
|
|
closure_var,
|
|
&mut vars,
|
|
);
|
|
|
|
let fn_type_index = {
|
|
// TODO(types-soa) optimize for variable
|
|
let lambda_set = types.from_old_type(&Type::Variable(closure_var));
|
|
let typ = types.function(pattern_types, lambda_set, ret_type);
|
|
constraints.push_type(types, typ)
|
|
};
|
|
let body_type =
|
|
constraints.push_expected_type(NoExpectation(ret_type_index));
|
|
let expr_con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_body_expr.region,
|
|
&loc_body_expr.value,
|
|
body_type,
|
|
);
|
|
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
|
|
|
|
vars.push(*fn_var);
|
|
|
|
let state_constraints = constraints.and_constraint(state.constraints);
|
|
let expected_index = constraints.push_expected_type(expected);
|
|
let cons = [
|
|
constraints.let_constraint(
|
|
[],
|
|
state.vars,
|
|
state.headers,
|
|
state_constraints,
|
|
expr_con,
|
|
generalizable,
|
|
),
|
|
constraints.equal_types(
|
|
fn_type_index,
|
|
expected_index,
|
|
Category::Lambda,
|
|
region,
|
|
),
|
|
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
|
constraints.store(signature_index, *fn_var, std::file!(), std::line!()),
|
|
constraints.store(
|
|
signature_index,
|
|
expr_var,
|
|
std::file!(),
|
|
std::line!(),
|
|
),
|
|
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
|
|
closure_constraint,
|
|
];
|
|
|
|
let and_constraint = constraints.and_constraint(cons);
|
|
let def_con = constraints.exists(vars, and_constraint);
|
|
|
|
if is_hybrid {
|
|
// TODO this is not quite right, types that are purely rigid should not
|
|
// be stored as hybrid!
|
|
// However it might not be possible to fix this before types SoA lands.
|
|
hybrid_and_flex_info.vars.extend(&new_rigid_variables);
|
|
hybrid_and_flex_info.constraints.push(def_con);
|
|
hybrid_and_flex_info
|
|
.def_types
|
|
.extend(def_pattern_state.headers);
|
|
} else {
|
|
rigid_info.vars.extend(&new_rigid_variables);
|
|
|
|
rigid_info.constraints.push(constraints.let_constraint(
|
|
new_rigid_variables,
|
|
def_pattern_state.vars,
|
|
[], // no headers introduced (at this level)
|
|
def_con,
|
|
Constraint::True,
|
|
generalizable,
|
|
));
|
|
rigid_info.def_types.extend(def_pattern_state.headers);
|
|
}
|
|
}
|
|
_ => {
|
|
let expected = constraints.push_expected_type(annotation_expected);
|
|
|
|
let ret_constraint = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
def.loc_expr.region,
|
|
&def.loc_expr.value,
|
|
expected,
|
|
);
|
|
let ret_constraint =
|
|
attach_resolution_constraints(constraints, env, ret_constraint);
|
|
|
|
let cons = [
|
|
ret_constraint,
|
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
|
constraints.store(
|
|
signature_index,
|
|
expr_var,
|
|
std::file!(),
|
|
std::line!(),
|
|
),
|
|
];
|
|
let def_con = constraints.and_constraint(cons);
|
|
|
|
if is_hybrid {
|
|
hybrid_and_flex_info.vars.extend(&new_rigid_variables);
|
|
hybrid_and_flex_info.constraints.push(def_con);
|
|
hybrid_and_flex_info
|
|
.def_types
|
|
.extend(def_pattern_state.headers);
|
|
} else {
|
|
rigid_info.vars.extend(&new_rigid_variables);
|
|
|
|
rigid_info.constraints.push(constraints.let_constraint(
|
|
new_rigid_variables,
|
|
def_pattern_state.vars,
|
|
[], // no headers introduced (at this level)
|
|
def_con,
|
|
Constraint::True,
|
|
generalizable,
|
|
));
|
|
rigid_info.def_types.extend(def_pattern_state.headers);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Strategy for recursive defs:
|
|
//
|
|
// 1. Let-generalize all rigid annotations. These are the source of truth we'll solve
|
|
// everything else with. If there are circular type errors here, they will be caught
|
|
// during the let-generalization.
|
|
//
|
|
// 2. Introduce all symbols of the flex + hybrid defs, but don't generalize them yet.
|
|
// Now, solve those defs' bodies. This way, when checking something like
|
|
// f = \x -> f [x]
|
|
// we introduce `f: b -> c`, then constrain the call `f [x]`,
|
|
// forcing `b -> c ~ List b -> c` and correctly picking up a recursion error.
|
|
// Had we generalized `b -> c`, the call `f [x]` would have been generalized, and this
|
|
// error would not be found.
|
|
//
|
|
// - This works just as well for mutually recursive defs.
|
|
// - For hybrid defs, we also ensure solved types agree with what the
|
|
// elaborated parts of their type annotations demand.
|
|
//
|
|
// 3. Now properly let-generalize the flex + hybrid defs, since we now know their types and
|
|
// that they don't have circular type errors.
|
|
//
|
|
// 4. Solve the bodies of the typed body defs, and check that they agree the types of the type
|
|
// annotation.
|
|
//
|
|
// 5. Solve the rest of the program that happens after this recursive def block.
|
|
|
|
// 2. Solve untyped defs without generalization of their symbols.
|
|
let untyped_body_constraints = constraints.and_constraint(hybrid_and_flex_info.constraints);
|
|
let untyped_def_symbols_constr = constraints.let_constraint(
|
|
[],
|
|
[],
|
|
hybrid_and_flex_info.def_types.clone(),
|
|
Constraint::True,
|
|
untyped_body_constraints,
|
|
generalizable,
|
|
);
|
|
|
|
// an extra constraint that propagates information to the solver to check for invalid recursion
|
|
// and generate a good error message there.
|
|
let (loc_symbols, expr_regions): (Vec<_>, Vec<_>) = defs
|
|
.iter()
|
|
.flat_map(|def| {
|
|
symbols_introduced_from_pattern(&def.loc_pattern)
|
|
.map(move |loc_symbol| ((loc_symbol.value, loc_symbol.region), def.loc_expr.region))
|
|
})
|
|
.unzip();
|
|
|
|
let cycle_constraint = constraints.check_cycle(loc_symbols, expr_regions, cycle_mark);
|
|
|
|
let typed_body_constraints = constraints.and_constraint(rigid_info.constraints);
|
|
let typed_body_and_final_constr =
|
|
constraints.and_constraint([typed_body_constraints, cycle_constraint, body_con]);
|
|
|
|
// 3. Properly generalize untyped defs after solving them.
|
|
let inner = constraints.let_constraint(
|
|
[],
|
|
hybrid_and_flex_info.vars,
|
|
hybrid_and_flex_info.def_types,
|
|
untyped_def_symbols_constr,
|
|
// 4 + 5. Solve the typed body defs, and the rest of the program.
|
|
typed_body_and_final_constr,
|
|
generalizable,
|
|
);
|
|
|
|
// 1. Let-generalize annotations we know.
|
|
constraints.let_constraint(
|
|
rigid_info.vars,
|
|
[],
|
|
rigid_info.def_types,
|
|
Constraint::True,
|
|
inner,
|
|
generalizable,
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn constrain_field_update(
|
|
types: &mut Types,
|
|
constraints: &mut Constraints,
|
|
env: &mut Env,
|
|
var: Variable,
|
|
region: Region,
|
|
field: Lowercase,
|
|
loc_expr: &Loc<Expr>,
|
|
) -> (Variable, Type, Constraint) {
|
|
let field_type = constraints.push_variable(var);
|
|
let reason = Reason::RecordUpdateValue(field);
|
|
let expected = constraints.push_expected_type(ForReason(reason, field_type, region));
|
|
let con = constrain_expr(
|
|
types,
|
|
constraints,
|
|
env,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
expected,
|
|
);
|
|
|
|
(var, Variable(var), con)
|
|
}
|