mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
2814 lines
85 KiB
Rust
2814 lines
85 KiB
Rust
use bumpalo::{collections::Vec as BumpVec, Bump};
|
|
|
|
use roc_can::expected::{Expected, PExpected};
|
|
use roc_collections::all::{BumpMap, BumpMapDefault, HumanIndex, SendMap};
|
|
use roc_module::{
|
|
ident::{Lowercase, TagName},
|
|
symbol::Symbol,
|
|
};
|
|
use roc_region::all::Region;
|
|
use roc_types::{
|
|
subs::Variable,
|
|
types::{self, AnnotationSource, IndexOrField, PReason, PatternCategory},
|
|
types::{Category, Reason},
|
|
};
|
|
|
|
use crate::{
|
|
lang::{
|
|
core::{
|
|
expr::{
|
|
expr2::{ClosureExtra, Expr2, ExprId, WhenBranch},
|
|
record_field::RecordField,
|
|
},
|
|
fun_def::FunctionDef,
|
|
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
|
|
types::{Type2, TypeId},
|
|
val_def::ValueDef,
|
|
},
|
|
env::Env,
|
|
},
|
|
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone},
|
|
};
|
|
|
|
/// A presence constraint is an additive constraint that defines the lower bound
|
|
/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the
|
|
/// type `t1` must contain at least the tag `A`. The additive nature of these
|
|
/// constraints makes them behaviorally different from unification-based constraints.
|
|
#[derive(Debug)]
|
|
pub enum PresenceConstraint<'a> {
|
|
IncludesTag(TagName, BumpVec<'a, Type2>),
|
|
IsOpen,
|
|
Pattern(Region, PatternCategory, PExpected<Type2>),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Constraint<'a> {
|
|
Eq(Type2, Expected<Type2>, Category, Region),
|
|
// Store(Type, Variable, &'static str, u32),
|
|
Lookup(Symbol, Expected<Type2>, Region),
|
|
Pattern(Region, PatternCategory, Type2, PExpected<Type2>),
|
|
And(BumpVec<'a, Constraint<'a>>),
|
|
Let(&'a LetConstraint<'a>),
|
|
// SaveTheEnvironment,
|
|
True, // Used for things that always unify, e.g. blanks and runtime errors
|
|
Present(Type2, PresenceConstraint<'a>),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LetConstraint<'a> {
|
|
pub rigid_vars: BumpVec<'a, Variable>,
|
|
pub flex_vars: BumpVec<'a, Variable>,
|
|
pub def_types: BumpMap<Symbol, Type2>,
|
|
pub defs_constraint: Constraint<'a>,
|
|
pub ret_constraint: Constraint<'a>,
|
|
}
|
|
|
|
pub fn constrain_expr<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
expr: &Expr2,
|
|
expected: Expected<Type2>,
|
|
region: Region,
|
|
) -> Constraint<'a> {
|
|
use Constraint::*;
|
|
|
|
match expr {
|
|
Expr2::Blank | Expr2::RuntimeError() | Expr2::InvalidLookup(_) => True,
|
|
Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, region),
|
|
Expr2::SmallStr(_) => Eq(str_type(env.pool), expected, Category::Str, region),
|
|
Expr2::Var(symbol) => Lookup(*symbol, expected, region),
|
|
Expr2::EmptyRecord => constrain_empty_record(expected, region),
|
|
Expr2::SmallInt { var, .. } | Expr2::I128 { var, .. } | Expr2::U128 { var, .. } => {
|
|
let mut flex_vars = BumpVec::with_capacity_in(1, arena);
|
|
|
|
flex_vars.push(*var);
|
|
|
|
let precision_var = env.var_store.fresh();
|
|
|
|
let range_type = Type2::Variable(precision_var);
|
|
|
|
let range_type_id = env.pool.add(range_type);
|
|
|
|
exists(
|
|
arena,
|
|
flex_vars,
|
|
Eq(
|
|
num_num(env.pool, range_type_id),
|
|
expected,
|
|
Category::Num,
|
|
region,
|
|
),
|
|
)
|
|
}
|
|
Expr2::Float { var, .. } => {
|
|
let mut flex_vars = BumpVec::with_capacity_in(1, arena);
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(2, arena);
|
|
|
|
let num_type = Type2::Variable(*var);
|
|
|
|
flex_vars.push(*var);
|
|
|
|
let precision_var = env.var_store.fresh();
|
|
|
|
let range_type = Type2::Variable(precision_var);
|
|
|
|
let range_type_id = env.pool.add(range_type);
|
|
|
|
and_constraints.push(Eq(
|
|
num_type.shallow_clone(),
|
|
Expected::ForReason(
|
|
Reason::FloatLiteral,
|
|
num_float(env.pool, range_type_id),
|
|
region,
|
|
),
|
|
Category::Int,
|
|
region,
|
|
));
|
|
|
|
and_constraints.push(Eq(num_type, expected, Category::Frac, region));
|
|
|
|
let defs_constraint = And(and_constraints);
|
|
|
|
exists(arena, flex_vars, defs_constraint)
|
|
}
|
|
Expr2::List {
|
|
elem_var, elems, ..
|
|
} => {
|
|
let mut flex_vars = BumpVec::with_capacity_in(1, arena);
|
|
|
|
flex_vars.push(*elem_var);
|
|
|
|
if elems.is_empty() {
|
|
exists(
|
|
arena,
|
|
flex_vars,
|
|
Eq(
|
|
empty_list_type(env.pool, *elem_var),
|
|
expected,
|
|
Category::List,
|
|
region,
|
|
),
|
|
)
|
|
} else {
|
|
let mut constraints = BumpVec::with_capacity_in(1 + elems.len(), arena);
|
|
|
|
let list_elem_type = Type2::Variable(*elem_var);
|
|
|
|
let indexed_node_ids: Vec<(usize, ExprId)> =
|
|
elems.iter(env.pool).copied().enumerate().collect();
|
|
|
|
for (index, elem_node_id) in indexed_node_ids {
|
|
let elem_expr = env.pool.get(elem_node_id);
|
|
|
|
let elem_expected = Expected::ForReason(
|
|
Reason::ElemInList {
|
|
index: HumanIndex::zero_based(index),
|
|
},
|
|
list_elem_type.shallow_clone(),
|
|
region,
|
|
);
|
|
|
|
let constraint = constrain_expr(arena, env, elem_expr, elem_expected, region);
|
|
|
|
constraints.push(constraint);
|
|
}
|
|
|
|
constraints.push(Eq(
|
|
list_type(env.pool, list_elem_type),
|
|
expected,
|
|
Category::List,
|
|
region,
|
|
));
|
|
|
|
exists(arena, flex_vars, And(constraints))
|
|
}
|
|
}
|
|
Expr2::Record { fields, record_var } => {
|
|
if fields.is_empty() {
|
|
constrain_empty_record(expected, region)
|
|
} else {
|
|
let field_types = PoolVec::with_capacity(fields.len() as u32, env.pool);
|
|
|
|
let mut field_vars = BumpVec::with_capacity_in(fields.len(), arena);
|
|
|
|
// Constraints need capacity for each field
|
|
// + 1 for the record itself + 1 for record var
|
|
let mut constraints = BumpVec::with_capacity_in(2 + fields.len(), arena);
|
|
|
|
for (record_field_node_id, field_type_node_id) in
|
|
fields.iter_node_ids().zip(field_types.iter_node_ids())
|
|
{
|
|
let record_field = env.pool.get(record_field_node_id);
|
|
|
|
match record_field {
|
|
RecordField::LabeledValue(pool_str, var, node_id) => {
|
|
let expr = env.pool.get(*node_id);
|
|
|
|
let (field_type, field_con) = constrain_field(arena, env, *var, expr);
|
|
|
|
field_vars.push(*var);
|
|
|
|
let field_type_id = env.pool.add(field_type);
|
|
|
|
env.pool[field_type_node_id] =
|
|
(*pool_str, types::RecordField::Required(field_type_id));
|
|
|
|
constraints.push(field_con);
|
|
}
|
|
e => todo!("{:?}", e),
|
|
}
|
|
}
|
|
|
|
let record_type = Type2::Record(field_types, env.pool.add(Type2::EmptyRec));
|
|
|
|
let record_con = Eq(
|
|
record_type,
|
|
expected.shallow_clone(),
|
|
Category::Record,
|
|
region,
|
|
);
|
|
|
|
constraints.push(record_con);
|
|
|
|
// variable to store in the AST
|
|
let stored_con = Eq(
|
|
Type2::Variable(*record_var),
|
|
expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
);
|
|
|
|
field_vars.push(*record_var);
|
|
constraints.push(stored_con);
|
|
|
|
exists(arena, field_vars, And(constraints))
|
|
}
|
|
}
|
|
Expr2::Tag {
|
|
variant_var,
|
|
ext_var,
|
|
name,
|
|
arguments,
|
|
} => {
|
|
let tag_name = TagName(name.as_str(env.pool).into());
|
|
|
|
constrain_tag(
|
|
arena,
|
|
env,
|
|
expected,
|
|
region,
|
|
tag_name,
|
|
arguments,
|
|
*ext_var,
|
|
*variant_var,
|
|
)
|
|
}
|
|
Expr2::Call {
|
|
args,
|
|
expr_var,
|
|
expr_id: expr_node_id,
|
|
closure_var,
|
|
fn_var,
|
|
called_via,
|
|
} => {
|
|
// The expression that evaluates to the function being called, e.g. `foo` in
|
|
// (foo) bar baz
|
|
let call_expr = env.pool.get(*expr_node_id);
|
|
|
|
let opt_symbol = if let Expr2::Var(symbol) = call_expr {
|
|
Some(*symbol)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let fn_type = Type2::Variable(*fn_var);
|
|
let fn_region = region;
|
|
let fn_expected = Expected::NoExpectation(fn_type.shallow_clone());
|
|
|
|
let fn_reason = Reason::FnCall {
|
|
name: opt_symbol,
|
|
arity: args.len() as u8,
|
|
called_via: *called_via,
|
|
};
|
|
|
|
let fn_con = constrain_expr(arena, env, call_expr, fn_expected, region);
|
|
|
|
// The function's return type
|
|
// TODO: don't use expr_var?
|
|
let ret_type = Type2::Variable(*expr_var);
|
|
|
|
// type of values captured in the closure
|
|
let closure_type = Type2::Variable(*closure_var);
|
|
|
|
// This will be used in the occurs check
|
|
let mut vars = BumpVec::with_capacity_in(2 + args.len(), arena);
|
|
|
|
vars.push(*fn_var);
|
|
// TODO: don't use expr_var?
|
|
vars.push(*expr_var);
|
|
vars.push(*closure_var);
|
|
|
|
let mut arg_types = BumpVec::with_capacity_in(args.len(), arena);
|
|
let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena);
|
|
|
|
for (index, arg_node_id) in args.iter_node_ids().enumerate() {
|
|
let (arg_var, arg) = env.pool.get(arg_node_id);
|
|
let arg_expr = env.pool.get(*arg);
|
|
|
|
let region = region;
|
|
let arg_type = Type2::Variable(*arg_var);
|
|
|
|
let reason = Reason::FnArg {
|
|
name: opt_symbol,
|
|
arg_index: HumanIndex::zero_based(index),
|
|
};
|
|
|
|
let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region);
|
|
|
|
let arg_con = constrain_expr(arena, env, arg_expr, expected_arg, region);
|
|
|
|
vars.push(*arg_var);
|
|
arg_types.push(arg_type);
|
|
arg_cons.push(arg_con);
|
|
}
|
|
|
|
let expected_fn_type = Expected::ForReason(
|
|
fn_reason,
|
|
Type2::Function(
|
|
PoolVec::new(arg_types.into_iter(), env.pool),
|
|
env.pool.add(closure_type),
|
|
env.pool.add(ret_type.shallow_clone()),
|
|
),
|
|
region,
|
|
);
|
|
|
|
let category = Category::CallResult(opt_symbol, *called_via);
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(4, arena);
|
|
|
|
and_constraints.push(fn_con);
|
|
and_constraints.push(Eq(fn_type, expected_fn_type, category.clone(), fn_region));
|
|
and_constraints.push(And(arg_cons));
|
|
and_constraints.push(Eq(ret_type, expected, category, region));
|
|
|
|
exists(arena, vars, And(and_constraints))
|
|
}
|
|
Expr2::Accessor {
|
|
function_var,
|
|
closure_var,
|
|
field,
|
|
record_var,
|
|
ext_var,
|
|
field_var,
|
|
} => {
|
|
let ext_var = *ext_var;
|
|
let ext_type = Type2::Variable(ext_var);
|
|
|
|
let field_var = *field_var;
|
|
let field_type = Type2::Variable(field_var);
|
|
|
|
let record_field =
|
|
types::RecordField::Demanded(env.pool.add(field_type.shallow_clone()));
|
|
|
|
let record_type = Type2::Record(
|
|
PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool),
|
|
env.pool.add(ext_type),
|
|
);
|
|
|
|
let category = Category::Accessor(IndexOrField::Field(field.as_str(env.pool).into()));
|
|
|
|
let record_expected = Expected::NoExpectation(record_type.shallow_clone());
|
|
let record_con = Eq(
|
|
Type2::Variable(*record_var),
|
|
record_expected,
|
|
category.clone(),
|
|
region,
|
|
);
|
|
|
|
let function_type = Type2::Function(
|
|
PoolVec::new(vec![record_type].into_iter(), env.pool),
|
|
env.pool.add(Type2::Variable(*closure_var)),
|
|
env.pool.add(field_type),
|
|
);
|
|
|
|
let mut flex_vars = BumpVec::with_capacity_in(5, arena);
|
|
|
|
flex_vars.push(*record_var);
|
|
flex_vars.push(*function_var);
|
|
flex_vars.push(*closure_var);
|
|
flex_vars.push(field_var);
|
|
flex_vars.push(ext_var);
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(3, arena);
|
|
|
|
and_constraints.push(Eq(
|
|
function_type.shallow_clone(),
|
|
expected,
|
|
category.clone(),
|
|
region,
|
|
));
|
|
|
|
and_constraints.push(Eq(
|
|
function_type,
|
|
Expected::NoExpectation(Type2::Variable(*function_var)),
|
|
category,
|
|
region,
|
|
));
|
|
|
|
and_constraints.push(record_con);
|
|
|
|
exists(arena, flex_vars, And(and_constraints))
|
|
}
|
|
Expr2::Access {
|
|
expr: expr_id,
|
|
field,
|
|
field_var,
|
|
record_var,
|
|
ext_var,
|
|
} => {
|
|
let ext_type = Type2::Variable(*ext_var);
|
|
|
|
let field_type = Type2::Variable(*field_var);
|
|
|
|
let record_field =
|
|
types::RecordField::Demanded(env.pool.add(field_type.shallow_clone()));
|
|
|
|
let record_type = Type2::Record(
|
|
PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool),
|
|
env.pool.add(ext_type),
|
|
);
|
|
|
|
let record_expected = Expected::NoExpectation(record_type);
|
|
|
|
let category = Category::RecordAccess(field.as_str(env.pool).into());
|
|
|
|
let record_con = Eq(
|
|
Type2::Variable(*record_var),
|
|
record_expected.shallow_clone(),
|
|
category.clone(),
|
|
region,
|
|
);
|
|
|
|
let access_expr = env.pool.get(*expr_id);
|
|
|
|
let constraint = constrain_expr(arena, env, access_expr, record_expected, region);
|
|
|
|
let mut flex_vars = BumpVec::with_capacity_in(3, arena);
|
|
|
|
flex_vars.push(*record_var);
|
|
flex_vars.push(*field_var);
|
|
flex_vars.push(*ext_var);
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(3, arena);
|
|
|
|
and_constraints.push(constraint);
|
|
and_constraints.push(Eq(field_type, expected, category, region));
|
|
and_constraints.push(record_con);
|
|
|
|
exists(arena, flex_vars, And(and_constraints))
|
|
}
|
|
Expr2::If {
|
|
cond_var,
|
|
expr_var,
|
|
branches,
|
|
final_else,
|
|
} => {
|
|
let expect_bool = |region| {
|
|
let bool_type = Type2::Variable(Variable::BOOL);
|
|
Expected::ForReason(Reason::IfCondition, bool_type, region)
|
|
};
|
|
|
|
let mut branch_cons = BumpVec::with_capacity_in(2 * branches.len() + 3, arena);
|
|
|
|
// TODO why does this cond var exist? is it for error messages?
|
|
// let first_cond_region = branches[0].0.region;
|
|
let cond_var_is_bool_con = Eq(
|
|
Type2::Variable(*cond_var),
|
|
expect_bool(region),
|
|
Category::If,
|
|
region,
|
|
);
|
|
|
|
branch_cons.push(cond_var_is_bool_con);
|
|
|
|
let final_else_expr = env.pool.get(*final_else);
|
|
|
|
let mut flex_vars = BumpVec::with_capacity_in(2, arena);
|
|
|
|
flex_vars.push(*cond_var);
|
|
flex_vars.push(*expr_var);
|
|
|
|
match expected {
|
|
Expected::FromAnnotation(name, arity, ann_source, tipe) => {
|
|
let num_branches = branches.len() + 1;
|
|
|
|
for (index, branch_id) in branches.iter_node_ids().enumerate() {
|
|
let (cond_id, body_id) = env.pool.get(branch_id);
|
|
|
|
let cond = env.pool.get(*cond_id);
|
|
let body = env.pool.get(*body_id);
|
|
|
|
let cond_con =
|
|
constrain_expr(arena, env, cond, expect_bool(region), region);
|
|
|
|
let then_con = constrain_expr(
|
|
arena,
|
|
env,
|
|
body,
|
|
Expected::FromAnnotation(
|
|
name.clone(),
|
|
arity,
|
|
AnnotationSource::TypedIfBranch {
|
|
index: HumanIndex::zero_based(index),
|
|
num_branches,
|
|
region: ann_source.region(),
|
|
},
|
|
tipe.shallow_clone(),
|
|
),
|
|
region,
|
|
);
|
|
|
|
branch_cons.push(cond_con);
|
|
branch_cons.push(then_con);
|
|
}
|
|
|
|
let else_con = constrain_expr(
|
|
arena,
|
|
env,
|
|
final_else_expr,
|
|
Expected::FromAnnotation(
|
|
name,
|
|
arity,
|
|
AnnotationSource::TypedIfBranch {
|
|
index: HumanIndex::zero_based(branches.len()),
|
|
num_branches,
|
|
region: ann_source.region(),
|
|
},
|
|
tipe.shallow_clone(),
|
|
),
|
|
region,
|
|
);
|
|
|
|
let ast_con = Eq(
|
|
Type2::Variable(*expr_var),
|
|
Expected::NoExpectation(tipe),
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
);
|
|
|
|
branch_cons.push(ast_con);
|
|
branch_cons.push(else_con);
|
|
|
|
exists(arena, flex_vars, And(branch_cons))
|
|
}
|
|
_ => {
|
|
for (index, branch_id) in branches.iter_node_ids().enumerate() {
|
|
let (cond_id, body_id) = env.pool.get(branch_id);
|
|
|
|
let cond = env.pool.get(*cond_id);
|
|
let body = env.pool.get(*body_id);
|
|
|
|
let cond_con =
|
|
constrain_expr(arena, env, cond, expect_bool(region), region);
|
|
|
|
let then_con = constrain_expr(
|
|
arena,
|
|
env,
|
|
body,
|
|
Expected::ForReason(
|
|
Reason::IfBranch {
|
|
index: HumanIndex::zero_based(index),
|
|
total_branches: branches.len(),
|
|
},
|
|
Type2::Variable(*expr_var),
|
|
// should be from body
|
|
region,
|
|
),
|
|
region,
|
|
);
|
|
|
|
branch_cons.push(cond_con);
|
|
branch_cons.push(then_con);
|
|
}
|
|
|
|
let else_con = constrain_expr(
|
|
arena,
|
|
env,
|
|
final_else_expr,
|
|
Expected::ForReason(
|
|
Reason::IfBranch {
|
|
index: HumanIndex::zero_based(branches.len()),
|
|
total_branches: branches.len() + 1,
|
|
},
|
|
Type2::Variable(*expr_var),
|
|
// should come from final_else
|
|
region,
|
|
),
|
|
region,
|
|
);
|
|
|
|
branch_cons.push(Eq(
|
|
Type2::Variable(*expr_var),
|
|
expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
));
|
|
|
|
branch_cons.push(else_con);
|
|
|
|
exists(arena, flex_vars, And(branch_cons))
|
|
}
|
|
}
|
|
}
|
|
Expr2::When {
|
|
cond_var,
|
|
expr_var,
|
|
cond: cond_id,
|
|
branches,
|
|
} => {
|
|
// Infer the condition expression's type.
|
|
let cond_type = Type2::Variable(*cond_var);
|
|
|
|
let cond = env.pool.get(*cond_id);
|
|
|
|
let expr_con = constrain_expr(
|
|
arena,
|
|
env,
|
|
cond,
|
|
Expected::NoExpectation(cond_type.shallow_clone()),
|
|
region,
|
|
);
|
|
|
|
let mut constraints = BumpVec::with_capacity_in(branches.len() + 1, arena);
|
|
|
|
constraints.push(expr_con);
|
|
|
|
let mut flex_vars = BumpVec::with_capacity_in(2, arena);
|
|
|
|
flex_vars.push(*cond_var);
|
|
flex_vars.push(*expr_var);
|
|
|
|
match &expected {
|
|
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
|
|
let typ = Type2::Variable(*expr_var);
|
|
|
|
for (index, when_branch_id) in branches.iter_node_ids().enumerate() {
|
|
let when_branch = env.pool.get(when_branch_id);
|
|
|
|
// let pattern_region = Region::across_all(
|
|
// when_branch.patterns.iter(env.pool).map(|v| &v.region),
|
|
// );
|
|
|
|
let pattern_expected = |sub_pattern, sub_region| {
|
|
PExpected::ForReason(
|
|
PReason::WhenMatch {
|
|
index: HumanIndex::zero_based(index),
|
|
sub_pattern,
|
|
},
|
|
cond_type.shallow_clone(),
|
|
sub_region,
|
|
)
|
|
};
|
|
|
|
let branch_con = constrain_when_branch(
|
|
arena,
|
|
env,
|
|
// TODO: when_branch.value.region,
|
|
region,
|
|
when_branch,
|
|
pattern_expected,
|
|
Expected::FromAnnotation(
|
|
name.clone(),
|
|
*arity,
|
|
AnnotationSource::TypedWhenBranch {
|
|
index: HumanIndex::zero_based(index),
|
|
region: ann_source.region(),
|
|
},
|
|
typ.shallow_clone(),
|
|
),
|
|
);
|
|
|
|
constraints.push(branch_con);
|
|
}
|
|
|
|
constraints.push(Eq(typ, expected, Category::When, region));
|
|
|
|
return exists(arena, flex_vars, And(constraints));
|
|
}
|
|
|
|
_ => {
|
|
let branch_type = Type2::Variable(*expr_var);
|
|
let mut branch_cons = BumpVec::with_capacity_in(branches.len(), arena);
|
|
|
|
for (index, when_branch_id) in branches.iter_node_ids().enumerate() {
|
|
let when_branch = env.pool.get(when_branch_id);
|
|
|
|
// let pattern_region =
|
|
// Region::across_all(when_branch.patterns.iter().map(|v| &v.region));
|
|
|
|
let pattern_expected = |sub_pattern, sub_region| {
|
|
PExpected::ForReason(
|
|
PReason::WhenMatch {
|
|
index: HumanIndex::zero_based(index),
|
|
sub_pattern,
|
|
},
|
|
cond_type.shallow_clone(),
|
|
sub_region,
|
|
)
|
|
};
|
|
|
|
let branch_con = constrain_when_branch(
|
|
arena,
|
|
env,
|
|
region,
|
|
when_branch,
|
|
pattern_expected,
|
|
Expected::ForReason(
|
|
Reason::WhenBranch {
|
|
index: HumanIndex::zero_based(index),
|
|
},
|
|
branch_type.shallow_clone(),
|
|
// TODO: when_branch.value.region,
|
|
region,
|
|
),
|
|
);
|
|
|
|
branch_cons.push(branch_con);
|
|
}
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(2, arena);
|
|
|
|
and_constraints.push(And(branch_cons));
|
|
and_constraints.push(Eq(branch_type, expected, Category::When, region));
|
|
|
|
constraints.push(And(and_constraints));
|
|
}
|
|
}
|
|
|
|
// exhautiveness checking happens when converting to mono::Expr
|
|
exists(arena, flex_vars, And(constraints))
|
|
}
|
|
Expr2::LetValue {
|
|
def_id,
|
|
body_id,
|
|
body_var,
|
|
} => {
|
|
let value_def = env.pool.get(*def_id);
|
|
let body = env.pool.get(*body_id);
|
|
|
|
let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region);
|
|
|
|
match value_def {
|
|
ValueDef::WithAnnotation { .. } => todo!("implement {:?}", value_def),
|
|
ValueDef::NoAnnotation {
|
|
pattern_id,
|
|
expr_id,
|
|
expr_var,
|
|
} => {
|
|
let pattern = env.pool.get(*pattern_id);
|
|
|
|
let mut flex_vars = BumpVec::with_capacity_in(1, arena);
|
|
flex_vars.push(*body_var);
|
|
|
|
let expr_type = Type2::Variable(*expr_var);
|
|
|
|
let pattern_expected = PExpected::NoExpectation(expr_type.shallow_clone());
|
|
let mut state = PatternState2 {
|
|
headers: BumpMap::new_in(arena),
|
|
vars: BumpVec::with_capacity_in(1, arena),
|
|
constraints: BumpVec::with_capacity_in(1, arena),
|
|
};
|
|
|
|
constrain_pattern(
|
|
arena,
|
|
env,
|
|
pattern,
|
|
region,
|
|
pattern_expected,
|
|
&mut state,
|
|
false,
|
|
);
|
|
state.vars.push(*expr_var);
|
|
|
|
let def_expr = env.pool.get(*expr_id);
|
|
|
|
let constrained_def = Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena),
|
|
flex_vars: state.vars,
|
|
def_types: state.headers,
|
|
defs_constraint: Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena), // always empty
|
|
flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments
|
|
def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments!
|
|
defs_constraint: And(state.constraints),
|
|
ret_constraint: constrain_expr(
|
|
arena,
|
|
env,
|
|
def_expr,
|
|
Expected::NoExpectation(expr_type),
|
|
region,
|
|
),
|
|
})),
|
|
ret_constraint: body_con,
|
|
}));
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(2, arena);
|
|
|
|
and_constraints.push(constrained_def);
|
|
and_constraints.push(Eq(
|
|
Type2::Variable(*body_var),
|
|
expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
// TODO: needs to be ret region
|
|
region,
|
|
));
|
|
|
|
exists(arena, flex_vars, And(and_constraints))
|
|
}
|
|
}
|
|
}
|
|
// In an expression like
|
|
// id = \x -> x
|
|
//
|
|
// id 1
|
|
// The `def_id` refers to the definition `id = \x -> x`,
|
|
// and the body refers to `id 1`.
|
|
Expr2::LetFunction {
|
|
def_id,
|
|
body_id,
|
|
body_var: _,
|
|
} => {
|
|
let body = env.pool.get(*body_id);
|
|
let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region);
|
|
|
|
let function_def = env.pool.get(*def_id);
|
|
|
|
let (name, arguments, body_id, rigid_vars, args_constrs) = match function_def {
|
|
FunctionDef::WithAnnotation {
|
|
name,
|
|
arguments,
|
|
body_id,
|
|
rigids,
|
|
return_type: _,
|
|
} => {
|
|
// The annotation gives us arguments with proper Type2s, but the constraints we
|
|
// generate below args bound to type variables. Create fresh ones and bind them
|
|
// to the types we already know.
|
|
let mut args_constrs = BumpVec::with_capacity_in(arguments.len(), arena);
|
|
let args_vars = PoolVec::with_capacity(arguments.len() as u32, env.pool);
|
|
for (arg_ty_node_id, arg_var_node_id) in
|
|
arguments.iter_node_ids().zip(args_vars.iter_node_ids())
|
|
{
|
|
let (ty, pattern) = env.pool.get(arg_ty_node_id);
|
|
let arg_var = env.var_store.fresh();
|
|
let ty = env.pool.get(*ty);
|
|
args_constrs.push(Eq(
|
|
Type2::Variable(arg_var),
|
|
Expected::NoExpectation(ty.shallow_clone()),
|
|
Category::Storage(std::file!(), std::line!()),
|
|
// TODO: should be the actual region of the argument
|
|
region,
|
|
));
|
|
env.pool[arg_var_node_id] = (arg_var, *pattern);
|
|
}
|
|
|
|
let rigids = env.pool.get(*rigids);
|
|
let rigid_vars: BumpVec<Variable> =
|
|
BumpVec::from_iter_in(rigids.names.iter(env.pool).map(|&(_, v)| v), arena);
|
|
|
|
(name, args_vars, body_id, rigid_vars, args_constrs)
|
|
}
|
|
FunctionDef::NoAnnotation {
|
|
name,
|
|
arguments,
|
|
body_id,
|
|
return_var: _,
|
|
} => {
|
|
(
|
|
name,
|
|
arguments.shallow_clone(),
|
|
body_id,
|
|
BumpVec::new_in(arena), // The function is unannotated, so there are no rigid type vars
|
|
BumpVec::new_in(arena), // No extra constraints to generate for arguments
|
|
)
|
|
}
|
|
};
|
|
|
|
// A function definition is equivalent to a named value definition, where the
|
|
// value is a closure. So, we create a closure definition in correspondence
|
|
// with the function definition, generate type constraints for it, and demand
|
|
// that type of the function is just the type of the resolved closure.
|
|
let fn_var = env.var_store.fresh();
|
|
let fn_ty = Type2::Variable(fn_var);
|
|
|
|
let extra = ClosureExtra {
|
|
return_type: env.var_store.fresh(),
|
|
captured_symbols: PoolVec::empty(env.pool),
|
|
closure_type: env.var_store.fresh(),
|
|
closure_ext_var: env.var_store.fresh(),
|
|
};
|
|
let clos = Expr2::Closure {
|
|
args: arguments.shallow_clone(),
|
|
uniq_symbol: *name,
|
|
body_id: *body_id,
|
|
function_type: env.var_store.fresh(),
|
|
extra: env.pool.add(extra),
|
|
recursive: roc_can::expr::Recursive::Recursive,
|
|
};
|
|
let clos_con = constrain_expr(
|
|
arena,
|
|
env,
|
|
&clos,
|
|
Expected::NoExpectation(fn_ty.shallow_clone()),
|
|
region,
|
|
);
|
|
|
|
// This is the `foo` part in `foo = \...`. We want to bind the name of the
|
|
// function with its type, whose constraints we generated above.
|
|
let mut def_pattern_state = PatternState2 {
|
|
headers: BumpMap::new_in(arena),
|
|
vars: BumpVec::new_in(arena),
|
|
constraints: args_constrs,
|
|
};
|
|
def_pattern_state.headers.insert(*name, fn_ty);
|
|
def_pattern_state.vars.push(fn_var);
|
|
|
|
Let(arena.alloc(LetConstraint {
|
|
rigid_vars,
|
|
flex_vars: def_pattern_state.vars,
|
|
def_types: def_pattern_state.headers, // Binding function name -> its type
|
|
defs_constraint: Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena), // always empty
|
|
flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments
|
|
def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments
|
|
defs_constraint: And(def_pattern_state.constraints),
|
|
ret_constraint: clos_con,
|
|
})),
|
|
ret_constraint: body_con,
|
|
}))
|
|
}
|
|
Expr2::Update {
|
|
symbol,
|
|
updates,
|
|
ext_var,
|
|
record_var,
|
|
} => {
|
|
let field_types = PoolVec::with_capacity(updates.len() as u32, env.pool);
|
|
let mut flex_vars = BumpVec::with_capacity_in(updates.len() + 2, arena);
|
|
let mut cons = BumpVec::with_capacity_in(updates.len() + 1, arena);
|
|
let mut record_key_updates = SendMap::default();
|
|
|
|
for (record_field_id, field_type_node_id) in
|
|
updates.iter_node_ids().zip(field_types.iter_node_ids())
|
|
{
|
|
let record_field = env.pool.get(record_field_id);
|
|
|
|
match record_field {
|
|
RecordField::LabeledValue(pool_str, var, node_id) => {
|
|
let expr = env.pool.get(*node_id);
|
|
|
|
let (field_type, field_con) = constrain_field_update(
|
|
arena,
|
|
env,
|
|
*var,
|
|
pool_str.as_str(env.pool).into(),
|
|
expr,
|
|
);
|
|
|
|
let field_type_id = env.pool.add(field_type);
|
|
|
|
env.pool[field_type_node_id] =
|
|
(*pool_str, types::RecordField::Required(field_type_id));
|
|
|
|
record_key_updates.insert(pool_str.as_str(env.pool).into(), Region::zero());
|
|
|
|
flex_vars.push(*var);
|
|
cons.push(field_con);
|
|
}
|
|
e => todo!("{:?}", e),
|
|
}
|
|
}
|
|
|
|
let fields_type = Type2::Record(field_types, env.pool.add(Type2::Variable(*ext_var)));
|
|
let record_type = Type2::Variable(*record_var);
|
|
|
|
// NOTE from elm compiler: fields_type is separate so that Error propagates better
|
|
let fields_con = Eq(
|
|
record_type.shallow_clone(),
|
|
Expected::NoExpectation(fields_type),
|
|
Category::Record,
|
|
region,
|
|
);
|
|
let record_con = Eq(
|
|
record_type.shallow_clone(),
|
|
expected,
|
|
Category::Record,
|
|
region,
|
|
);
|
|
|
|
flex_vars.push(*record_var);
|
|
flex_vars.push(*ext_var);
|
|
|
|
let con = Lookup(
|
|
*symbol,
|
|
Expected::ForReason(
|
|
Reason::RecordUpdateKeys(*symbol, record_key_updates),
|
|
record_type,
|
|
region,
|
|
),
|
|
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);
|
|
|
|
exists(arena, flex_vars, And(cons))
|
|
}
|
|
|
|
Expr2::RunLowLevel { op, args, ret_var } => {
|
|
// This is a modified version of what we do for function calls.
|
|
|
|
// The operation's return type
|
|
let ret_type = Type2::Variable(*ret_var);
|
|
|
|
// This will be used in the occurs check
|
|
let mut vars = BumpVec::with_capacity_in(1 + args.len(), arena);
|
|
|
|
vars.push(*ret_var);
|
|
|
|
let mut arg_types = BumpVec::with_capacity_in(args.len(), arena);
|
|
let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena);
|
|
|
|
for (index, node_id) in args.iter_node_ids().enumerate() {
|
|
let (arg_var, arg_id) = env.pool.get(node_id);
|
|
|
|
vars.push(*arg_var);
|
|
|
|
let arg_type = Type2::Variable(*arg_var);
|
|
|
|
let reason = Reason::LowLevelOpArg {
|
|
op: *op,
|
|
arg_index: HumanIndex::zero_based(index),
|
|
};
|
|
let expected_arg =
|
|
Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero());
|
|
let arg = env.pool.get(*arg_id);
|
|
|
|
let arg_con = constrain_expr(arena, env, arg, expected_arg, Region::zero());
|
|
|
|
arg_types.push(arg_type);
|
|
arg_cons.push(arg_con);
|
|
}
|
|
|
|
let category = Category::LowLevelOpResult(*op);
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(2, arena);
|
|
|
|
and_constraints.push(And(arg_cons));
|
|
and_constraints.push(Eq(ret_type, expected, category, region));
|
|
|
|
exists(arena, vars, And(and_constraints))
|
|
}
|
|
Expr2::Closure {
|
|
args,
|
|
uniq_symbol,
|
|
body_id,
|
|
function_type: fn_var,
|
|
extra,
|
|
..
|
|
} => {
|
|
// NOTE defs are treated somewhere else!
|
|
let body = env.pool.get(*body_id);
|
|
|
|
let ClosureExtra {
|
|
captured_symbols,
|
|
return_type: ret_var,
|
|
closure_type: closure_var,
|
|
closure_ext_var,
|
|
} = env.pool.get(*extra);
|
|
|
|
let closure_type = Type2::Variable(*closure_var);
|
|
let return_type = Type2::Variable(*ret_var);
|
|
|
|
let (mut vars, pattern_state, function_type) =
|
|
constrain_untyped_args(arena, env, args, closure_type, return_type.shallow_clone());
|
|
|
|
vars.push(*ret_var);
|
|
vars.push(*closure_var);
|
|
vars.push(*closure_ext_var);
|
|
vars.push(*fn_var);
|
|
|
|
let expected_body_type = Expected::NoExpectation(return_type);
|
|
// Region here should come from body expr
|
|
let ret_constraint = constrain_expr(arena, env, body, expected_body_type, region);
|
|
|
|
let captured_symbols_as_vec = captured_symbols
|
|
.iter(env.pool)
|
|
.copied()
|
|
.collect::<Vec<(Symbol, Variable)>>();
|
|
|
|
// make sure the captured symbols are sorted!
|
|
debug_assert_eq!(captured_symbols_as_vec, {
|
|
let mut copy: Vec<(Symbol, Variable)> = captured_symbols_as_vec.clone();
|
|
|
|
copy.sort();
|
|
|
|
copy
|
|
});
|
|
|
|
let closure_constraint = constrain_closure_size(
|
|
arena,
|
|
env,
|
|
*uniq_symbol,
|
|
region,
|
|
captured_symbols,
|
|
*closure_var,
|
|
*closure_ext_var,
|
|
&mut vars,
|
|
);
|
|
|
|
let mut and_constraints = BumpVec::with_capacity_in(4, arena);
|
|
|
|
and_constraints.push(Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena),
|
|
flex_vars: pattern_state.vars,
|
|
def_types: pattern_state.headers,
|
|
defs_constraint: And(pattern_state.constraints),
|
|
ret_constraint,
|
|
})));
|
|
|
|
// "the closure's type is equal to expected type"
|
|
and_constraints.push(Eq(
|
|
function_type.shallow_clone(),
|
|
expected,
|
|
Category::Lambda,
|
|
region,
|
|
));
|
|
|
|
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
|
and_constraints.push(Eq(
|
|
Type2::Variable(*fn_var),
|
|
Expected::NoExpectation(function_type),
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
));
|
|
|
|
and_constraints.push(closure_constraint);
|
|
|
|
exists(arena, vars, And(and_constraints))
|
|
}
|
|
Expr2::LetRec { .. } => todo!(),
|
|
}
|
|
}
|
|
|
|
fn exists<'a>(
|
|
arena: &'a Bump,
|
|
flex_vars: BumpVec<'a, Variable>,
|
|
defs_constraint: Constraint<'a>,
|
|
) -> Constraint<'a> {
|
|
Constraint::Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena),
|
|
flex_vars,
|
|
def_types: BumpMap::new_in(arena),
|
|
defs_constraint,
|
|
ret_constraint: Constraint::True,
|
|
}))
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn constrain_tag<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
expected: Expected<Type2>,
|
|
region: Region,
|
|
tag_name: TagName,
|
|
arguments: &PoolVec<(Variable, ExprId)>,
|
|
ext_var: Variable,
|
|
variant_var: Variable,
|
|
) -> Constraint<'a> {
|
|
use Constraint::*;
|
|
|
|
let mut flex_vars = BumpVec::with_capacity_in(arguments.len(), arena);
|
|
let types = PoolVec::with_capacity(arguments.len() as u32, env.pool);
|
|
let mut arg_cons = BumpVec::with_capacity_in(arguments.len(), arena);
|
|
|
|
for (argument_node_id, type_node_id) in arguments.iter_node_ids().zip(types.iter_node_ids()) {
|
|
let (var, expr_node_id) = env.pool.get(argument_node_id);
|
|
|
|
let argument_expr = env.pool.get(*expr_node_id);
|
|
|
|
let arg_con = constrain_expr(
|
|
arena,
|
|
env,
|
|
argument_expr,
|
|
Expected::NoExpectation(Type2::Variable(*var)),
|
|
region,
|
|
);
|
|
|
|
arg_cons.push(arg_con);
|
|
flex_vars.push(*var);
|
|
|
|
env.pool[type_node_id] = Type2::Variable(*var);
|
|
}
|
|
|
|
let union_con = Eq(
|
|
Type2::TagUnion(
|
|
PoolVec::new(std::iter::once((tag_name.clone(), types)), env.pool),
|
|
env.pool.add(Type2::Variable(ext_var)),
|
|
),
|
|
expected.shallow_clone(),
|
|
Category::TagApply {
|
|
tag_name,
|
|
args_count: arguments.len(),
|
|
},
|
|
region,
|
|
);
|
|
|
|
let ast_con = Eq(
|
|
Type2::Variable(variant_var),
|
|
expected,
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
);
|
|
|
|
flex_vars.push(variant_var);
|
|
flex_vars.push(ext_var);
|
|
|
|
arg_cons.push(union_con);
|
|
arg_cons.push(ast_con);
|
|
|
|
exists(arena, flex_vars, And(arg_cons))
|
|
}
|
|
|
|
fn constrain_field<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
field_var: Variable,
|
|
expr: &Expr2,
|
|
) -> (Type2, Constraint<'a>) {
|
|
let field_type = Type2::Variable(field_var);
|
|
let field_expected = Expected::NoExpectation(field_type.shallow_clone());
|
|
let constraint = constrain_expr(arena, env, expr, field_expected, Region::zero());
|
|
|
|
(field_type, constraint)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn constrain_field_update<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
field_var: Variable,
|
|
field: Lowercase,
|
|
expr: &Expr2,
|
|
) -> (Type2, Constraint<'a>) {
|
|
let field_type = Type2::Variable(field_var);
|
|
let reason = Reason::RecordUpdateValue(field);
|
|
let field_expected = Expected::ForReason(reason, field_type.shallow_clone(), Region::zero());
|
|
let con = constrain_expr(arena, env, expr, field_expected, Region::zero());
|
|
|
|
(field_type, con)
|
|
}
|
|
|
|
fn constrain_empty_record<'a>(expected: Expected<Type2>, region: Region) -> Constraint<'a> {
|
|
Constraint::Eq(Type2::EmptyRec, expected, Category::Record, region)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn constrain_when_branch<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
region: Region,
|
|
when_branch: &WhenBranch,
|
|
pattern_expected: impl Fn(HumanIndex, Region) -> PExpected<Type2>,
|
|
expr_expected: Expected<Type2>,
|
|
) -> Constraint<'a> {
|
|
let when_expr = env.pool.get(when_branch.body);
|
|
|
|
let ret_constraint = constrain_expr(arena, env, when_expr, expr_expected, region);
|
|
|
|
let mut state = PatternState2 {
|
|
headers: BumpMap::new_in(arena),
|
|
vars: BumpVec::with_capacity_in(1, arena),
|
|
constraints: BumpVec::with_capacity_in(1, arena),
|
|
};
|
|
|
|
// TODO investigate for error messages, is it better to unify all branches with a variable,
|
|
// then unify that variable with the expectation?
|
|
for (sub_pattern, pattern_id) in when_branch.patterns.iter_node_ids().enumerate() {
|
|
let pattern = env.pool.get(pattern_id);
|
|
|
|
let pattern_expected = pattern_expected(
|
|
HumanIndex::zero_based(sub_pattern),
|
|
// TODO: use the proper subpattern region. Not available to us right now.
|
|
region,
|
|
);
|
|
|
|
constrain_pattern(
|
|
arena,
|
|
env,
|
|
pattern,
|
|
// loc_pattern.region,
|
|
region,
|
|
pattern_expected,
|
|
&mut state,
|
|
true,
|
|
);
|
|
}
|
|
|
|
if let Some(guard_id) = &when_branch.guard {
|
|
let guard = env.pool.get(*guard_id);
|
|
|
|
let guard_constraint = constrain_expr(
|
|
arena,
|
|
env,
|
|
guard,
|
|
Expected::ForReason(
|
|
Reason::WhenGuard,
|
|
Type2::Variable(Variable::BOOL),
|
|
// TODO: loc_guard.region,
|
|
region,
|
|
),
|
|
region,
|
|
);
|
|
|
|
// must introduce the headers from the pattern before constraining the guard
|
|
Constraint::Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena),
|
|
flex_vars: state.vars,
|
|
def_types: state.headers,
|
|
defs_constraint: Constraint::And(state.constraints),
|
|
ret_constraint: Constraint::Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena),
|
|
flex_vars: BumpVec::new_in(arena),
|
|
def_types: BumpMap::new_in(arena),
|
|
defs_constraint: guard_constraint,
|
|
ret_constraint,
|
|
})),
|
|
}))
|
|
} else {
|
|
Constraint::Let(arena.alloc(LetConstraint {
|
|
rigid_vars: BumpVec::new_in(arena),
|
|
flex_vars: state.vars,
|
|
def_types: state.headers,
|
|
defs_constraint: Constraint::And(state.constraints),
|
|
ret_constraint,
|
|
}))
|
|
}
|
|
}
|
|
|
|
fn make_pattern_constraint(
|
|
region: Region,
|
|
category: PatternCategory,
|
|
actual: Type2,
|
|
expected: PExpected<Type2>,
|
|
presence_con: bool,
|
|
) -> Constraint<'static> {
|
|
if presence_con {
|
|
Constraint::Present(
|
|
actual,
|
|
PresenceConstraint::Pattern(region, category, expected),
|
|
)
|
|
} else {
|
|
Constraint::Pattern(region, category, actual, expected)
|
|
}
|
|
}
|
|
|
|
/// This accepts PatternState (rather than returning it) so that the caller can
|
|
/// initialize the Vecs in PatternState using with_capacity
|
|
/// based on its knowledge of their lengths.
|
|
pub fn constrain_pattern<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
pattern: &Pattern2,
|
|
region: Region,
|
|
expected: PExpected<Type2>,
|
|
state: &mut PatternState2<'a>,
|
|
destruct_position: bool,
|
|
) {
|
|
use Pattern2::*;
|
|
|
|
match pattern {
|
|
Underscore if destruct_position => {
|
|
// This is an underscore in a position where we destruct a variable,
|
|
// like a when expression:
|
|
// when x is
|
|
// A -> ""
|
|
// _ -> ""
|
|
// so, we know that "x" (in this case, a tag union) must be open.
|
|
state.constraints.push(Constraint::Present(
|
|
expected.get_type(),
|
|
PresenceConstraint::IsOpen,
|
|
));
|
|
}
|
|
|
|
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => {
|
|
// Neither the _ pattern nor erroneous ones add any constraints.
|
|
}
|
|
|
|
Identifier(symbol) => {
|
|
if destruct_position {
|
|
state.constraints.push(Constraint::Present(
|
|
expected.get_type_ref().shallow_clone(),
|
|
PresenceConstraint::IsOpen,
|
|
));
|
|
}
|
|
state.headers.insert(*symbol, expected.get_type());
|
|
}
|
|
|
|
NumLiteral(var, _) => {
|
|
state.vars.push(*var);
|
|
|
|
let type_id = env.pool.add(Type2::Variable(*var));
|
|
|
|
state.constraints.push(Constraint::Pattern(
|
|
region,
|
|
PatternCategory::Num,
|
|
num_num(env.pool, type_id),
|
|
expected,
|
|
));
|
|
}
|
|
|
|
IntLiteral(_int_val) => {
|
|
let precision_var = env.var_store.fresh();
|
|
|
|
let range = env.add(Type2::Variable(precision_var), region);
|
|
|
|
state.constraints.push(Constraint::Pattern(
|
|
region,
|
|
PatternCategory::Int,
|
|
num_int(env.pool, range),
|
|
expected,
|
|
));
|
|
}
|
|
|
|
FloatLiteral(_float_val) => {
|
|
let precision_var = env.var_store.fresh();
|
|
|
|
let range = env.add(Type2::Variable(precision_var), region);
|
|
|
|
state.constraints.push(Constraint::Pattern(
|
|
region,
|
|
PatternCategory::Float,
|
|
num_float(env.pool, range),
|
|
expected,
|
|
));
|
|
}
|
|
|
|
StrLiteral(_) => {
|
|
state.constraints.push(Constraint::Pattern(
|
|
region,
|
|
PatternCategory::Str,
|
|
str_type(env.pool),
|
|
expected,
|
|
));
|
|
}
|
|
|
|
CharacterLiteral(_) => {
|
|
state.constraints.push(Constraint::Pattern(
|
|
region,
|
|
PatternCategory::Character,
|
|
num_unsigned32(env.pool),
|
|
expected,
|
|
));
|
|
}
|
|
|
|
RecordDestructure {
|
|
whole_var,
|
|
ext_var,
|
|
destructs,
|
|
} => {
|
|
state.vars.push(*whole_var);
|
|
state.vars.push(*ext_var);
|
|
let ext_type = Type2::Variable(*ext_var);
|
|
|
|
let mut field_types = Vec::new();
|
|
|
|
for destruct_id in destructs.iter_node_ids() {
|
|
let RecordDestruct {
|
|
var,
|
|
label,
|
|
symbol,
|
|
typ,
|
|
} = env.pool.get(destruct_id);
|
|
|
|
let pat_type = Type2::Variable(*var);
|
|
let expected = PExpected::NoExpectation(pat_type.shallow_clone());
|
|
|
|
if !state.headers.contains_key(symbol) {
|
|
state.headers.insert(*symbol, pat_type.shallow_clone());
|
|
}
|
|
|
|
let destruct_type = env.pool.get(*typ);
|
|
|
|
let field_type = match destruct_type {
|
|
DestructType::Guard(guard_var, guard_id) => {
|
|
state.constraints.push(make_pattern_constraint(
|
|
region,
|
|
PatternCategory::PatternGuard,
|
|
Type2::Variable(*guard_var),
|
|
PExpected::ForReason(
|
|
PReason::PatternGuard,
|
|
pat_type.shallow_clone(),
|
|
// TODO: region should be from guard_id
|
|
region,
|
|
),
|
|
destruct_position,
|
|
));
|
|
|
|
state.vars.push(*guard_var);
|
|
|
|
let guard = env.pool.get(*guard_id);
|
|
|
|
// TODO: region should be from guard_id
|
|
constrain_pattern(
|
|
arena,
|
|
env,
|
|
guard,
|
|
region,
|
|
expected,
|
|
state,
|
|
destruct_position,
|
|
);
|
|
|
|
types::RecordField::Demanded(env.pool.add(pat_type))
|
|
}
|
|
DestructType::Optional(expr_var, expr_id) => {
|
|
state.constraints.push(make_pattern_constraint(
|
|
region,
|
|
PatternCategory::PatternDefault,
|
|
Type2::Variable(*expr_var),
|
|
PExpected::ForReason(
|
|
PReason::OptionalField,
|
|
pat_type.shallow_clone(),
|
|
// TODO: region should be from expr_id
|
|
region,
|
|
),
|
|
destruct_position,
|
|
));
|
|
|
|
state.vars.push(*expr_var);
|
|
|
|
let expr_expected = Expected::ForReason(
|
|
Reason::RecordDefaultField(label.as_str(env.pool).into()),
|
|
pat_type.shallow_clone(),
|
|
// TODO: region should be from expr_id
|
|
region,
|
|
);
|
|
|
|
let expr = env.pool.get(*expr_id);
|
|
|
|
// TODO: region should be from expr_id
|
|
let expr_con = constrain_expr(arena, env, expr, expr_expected, region);
|
|
|
|
state.constraints.push(expr_con);
|
|
|
|
types::RecordField::Optional(env.pool.add(pat_type))
|
|
}
|
|
DestructType::Required => {
|
|
// No extra constraints necessary.
|
|
types::RecordField::Demanded(env.pool.add(pat_type))
|
|
}
|
|
};
|
|
|
|
field_types.push((*label, field_type));
|
|
|
|
state.vars.push(*var);
|
|
}
|
|
|
|
let record_type = Type2::Record(
|
|
PoolVec::new(field_types.into_iter(), env.pool),
|
|
env.pool.add(ext_type),
|
|
);
|
|
|
|
let whole_con = Constraint::Eq(
|
|
Type2::Variable(*whole_var),
|
|
Expected::NoExpectation(record_type),
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
);
|
|
|
|
let record_con = make_pattern_constraint(
|
|
region,
|
|
PatternCategory::Record,
|
|
Type2::Variable(*whole_var),
|
|
expected,
|
|
destruct_position,
|
|
);
|
|
|
|
state.constraints.push(whole_con);
|
|
state.constraints.push(record_con);
|
|
}
|
|
Tag {
|
|
whole_var,
|
|
ext_var,
|
|
tag_name: name,
|
|
arguments,
|
|
} => {
|
|
let tag_name = TagName(name.as_str(env.pool).into());
|
|
|
|
constrain_tag_pattern(
|
|
arena,
|
|
env,
|
|
region,
|
|
expected,
|
|
state,
|
|
*whole_var,
|
|
*ext_var,
|
|
arguments,
|
|
tag_name,
|
|
destruct_position,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn constrain_tag_pattern<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
region: Region,
|
|
expected: PExpected<Type2>,
|
|
state: &mut PatternState2<'a>,
|
|
whole_var: Variable,
|
|
ext_var: Variable,
|
|
arguments: &PoolVec<(Variable, PatternId)>,
|
|
tag_name: TagName,
|
|
destruct_position: bool,
|
|
) {
|
|
let mut argument_types = Vec::with_capacity(arguments.len());
|
|
|
|
for (index, arg_id) in arguments.iter_node_ids().enumerate() {
|
|
let (pattern_var, pattern_id) = env.pool.get(arg_id);
|
|
let pattern = env.pool.get(*pattern_id);
|
|
|
|
state.vars.push(*pattern_var);
|
|
|
|
let pattern_type = Type2::Variable(*pattern_var);
|
|
argument_types.push(pattern_type.shallow_clone());
|
|
|
|
let expected = PExpected::ForReason(
|
|
PReason::TagArg {
|
|
tag_name: tag_name.clone(),
|
|
index: HumanIndex::zero_based(index),
|
|
},
|
|
pattern_type,
|
|
region,
|
|
);
|
|
|
|
// TODO region should come from pattern
|
|
constrain_pattern(arena, env, pattern, region, expected, state, false);
|
|
}
|
|
|
|
let whole_con = if destruct_position {
|
|
Constraint::Present(
|
|
expected.get_type_ref().shallow_clone(),
|
|
PresenceConstraint::IncludesTag(
|
|
tag_name.clone(),
|
|
BumpVec::from_iter_in(argument_types.into_iter(), arena),
|
|
),
|
|
)
|
|
} else {
|
|
Constraint::Eq(
|
|
Type2::Variable(whole_var),
|
|
Expected::NoExpectation(Type2::TagUnion(
|
|
PoolVec::new(
|
|
vec![(
|
|
tag_name.clone(),
|
|
PoolVec::new(argument_types.into_iter(), env.pool),
|
|
)]
|
|
.into_iter(),
|
|
env.pool,
|
|
),
|
|
env.pool.add(Type2::Variable(ext_var)),
|
|
)),
|
|
Category::Storage(std::file!(), std::line!()),
|
|
region,
|
|
)
|
|
};
|
|
|
|
let tag_con = make_pattern_constraint(
|
|
region,
|
|
PatternCategory::Ctor(tag_name),
|
|
Type2::Variable(whole_var),
|
|
expected,
|
|
destruct_position,
|
|
);
|
|
|
|
state.vars.push(whole_var);
|
|
state.vars.push(ext_var);
|
|
state.constraints.push(whole_con);
|
|
state.constraints.push(tag_con);
|
|
}
|
|
|
|
fn constrain_untyped_args<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
arguments: &PoolVec<(Variable, PatternId)>,
|
|
closure_type: Type2,
|
|
return_type: Type2,
|
|
) -> (BumpVec<'a, Variable>, PatternState2<'a>, Type2) {
|
|
let mut vars = BumpVec::with_capacity_in(arguments.len(), arena);
|
|
|
|
let pattern_types = PoolVec::with_capacity(arguments.len() as u32, env.pool);
|
|
|
|
let mut pattern_state = PatternState2 {
|
|
headers: BumpMap::new_in(arena),
|
|
vars: BumpVec::with_capacity_in(1, arena),
|
|
constraints: BumpVec::with_capacity_in(1, arena),
|
|
};
|
|
|
|
for (arg_node_id, pattern_type_id) in
|
|
arguments.iter_node_ids().zip(pattern_types.iter_node_ids())
|
|
{
|
|
let (pattern_var, pattern_id) = env.pool.get(arg_node_id);
|
|
let pattern = env.pool.get(*pattern_id);
|
|
|
|
let pattern_type = Type2::Variable(*pattern_var);
|
|
let pattern_expected = PExpected::NoExpectation(pattern_type.shallow_clone());
|
|
|
|
env.pool[pattern_type_id] = pattern_type;
|
|
|
|
constrain_pattern(
|
|
arena,
|
|
env,
|
|
pattern,
|
|
// TODO needs to come from pattern
|
|
Region::zero(),
|
|
pattern_expected,
|
|
&mut pattern_state,
|
|
false,
|
|
);
|
|
|
|
vars.push(*pattern_var);
|
|
}
|
|
|
|
let function_type = Type2::Function(
|
|
pattern_types,
|
|
env.pool.add(closure_type),
|
|
env.pool.add(return_type),
|
|
);
|
|
|
|
(vars, pattern_state, function_type)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn constrain_closure_size<'a>(
|
|
arena: &'a Bump,
|
|
env: &mut Env,
|
|
_name: Symbol,
|
|
region: Region,
|
|
captured_symbols: &PoolVec<(Symbol, Variable)>,
|
|
closure_var: Variable,
|
|
closure_ext_var: Variable,
|
|
variables: &mut BumpVec<'a, Variable>,
|
|
) -> Constraint<'a> {
|
|
use Constraint::*;
|
|
|
|
debug_assert!(variables.iter().any(|s| *s == closure_var));
|
|
debug_assert!(variables.iter().any(|s| *s == closure_ext_var));
|
|
|
|
let tag_arguments = PoolVec::with_capacity(captured_symbols.len() as u32, env.pool);
|
|
let mut captured_symbols_constraints = BumpVec::with_capacity_in(captured_symbols.len(), arena);
|
|
|
|
for (captured_symbol_id, tag_arg_id) in captured_symbols
|
|
.iter_node_ids()
|
|
.zip(tag_arguments.iter_node_ids())
|
|
{
|
|
let (symbol, var) = env.pool.get(captured_symbol_id);
|
|
|
|
// make sure the variable is registered
|
|
variables.push(*var);
|
|
|
|
let tag_arg_type = Type2::Variable(*var);
|
|
|
|
// this symbol is captured, so it must be part of the closure type
|
|
env.pool[tag_arg_id] = tag_arg_type.shallow_clone();
|
|
|
|
// make the variable equal to the looked-up type of symbol
|
|
captured_symbols_constraints.push(Lookup(
|
|
*symbol,
|
|
Expected::NoExpectation(tag_arg_type),
|
|
Region::zero(),
|
|
));
|
|
}
|
|
|
|
// This is incorrect, but the editor will be using the Can AST soon, so disregarding for now.
|
|
let tag_name = TagName("FAKE CLOSURE".into());
|
|
let closure_type = Type2::TagUnion(
|
|
PoolVec::new(vec![(tag_name, tag_arguments)].into_iter(), env.pool),
|
|
env.pool.add(Type2::Variable(closure_ext_var)),
|
|
);
|
|
|
|
let finalizer = Eq(
|
|
Type2::Variable(closure_var),
|
|
Expected::NoExpectation(closure_type),
|
|
Category::ClosureSize,
|
|
region,
|
|
);
|
|
|
|
captured_symbols_constraints.push(finalizer);
|
|
|
|
And(captured_symbols_constraints)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn builtin_type(symbol: Symbol, args: PoolVec<Type2>) -> Type2 {
|
|
Type2::Apply(symbol, args)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn str_type(pool: &mut Pool) -> Type2 {
|
|
builtin_type(Symbol::STR_STR, PoolVec::empty(pool))
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn empty_list_type(pool: &mut Pool, var: Variable) -> Type2 {
|
|
list_type(pool, Type2::Variable(var))
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn list_type(pool: &mut Pool, typ: Type2) -> Type2 {
|
|
builtin_type(Symbol::LIST_LIST, PoolVec::new(vec![typ].into_iter(), pool))
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn num_float(pool: &mut Pool, range: TypeId) -> Type2 {
|
|
let num_floatingpoint_type = num_floatingpoint(pool, range);
|
|
let num_floatingpoint_id = pool.add(num_floatingpoint_type);
|
|
|
|
let num_num_type = num_num(pool, num_floatingpoint_id);
|
|
let num_num_id = pool.add(num_num_type);
|
|
|
|
Type2::Alias(
|
|
Symbol::NUM_FRAC,
|
|
PoolVec::new(vec![range].into_iter(), pool),
|
|
num_num_id,
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 {
|
|
let range_type = pool.get(range);
|
|
|
|
let alias_content = range_type.shallow_clone();
|
|
|
|
Type2::Opaque(
|
|
Symbol::NUM_FLOATINGPOINT,
|
|
PoolVec::new(vec![range].into_iter(), pool),
|
|
pool.add(alias_content),
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn num_int(pool: &mut Pool, range: TypeId) -> Type2 {
|
|
let num_integer_type = _num_integer(pool, range);
|
|
let num_integer_id = pool.add(num_integer_type);
|
|
|
|
let num_num_type = num_num(pool, num_integer_id);
|
|
let num_num_id = pool.add(num_num_type);
|
|
|
|
Type2::Alias(
|
|
Symbol::NUM_INT,
|
|
PoolVec::new(vec![range].into_iter(), pool),
|
|
num_num_id,
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn _num_signed64(pool: &mut Pool) -> Type2 {
|
|
Type2::Alias(
|
|
Symbol::NUM_SIGNED64,
|
|
PoolVec::empty(pool),
|
|
pool.add(Type2::EmptyTagUnion),
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn num_unsigned32(pool: &mut Pool) -> Type2 {
|
|
let alias_content = Type2::EmptyTagUnion;
|
|
|
|
Type2::Alias(
|
|
Symbol::NUM_UNSIGNED32,
|
|
PoolVec::empty(pool),
|
|
pool.add(alias_content),
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
|
|
let range_type = pool.get(range);
|
|
|
|
let alias_content = range_type.shallow_clone();
|
|
|
|
Type2::Opaque(
|
|
Symbol::NUM_INTEGER,
|
|
PoolVec::new(vec![range].into_iter(), pool),
|
|
pool.add(alias_content),
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 {
|
|
let range_type = pool.get(type_id);
|
|
|
|
let alias_content = range_type.shallow_clone();
|
|
|
|
Type2::Opaque(
|
|
Symbol::NUM_NUM,
|
|
PoolVec::new(vec![type_id].into_iter(), pool),
|
|
pool.add(alias_content),
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod test_constrain {
|
|
use bumpalo::Bump;
|
|
use roc_can::expected::Expected;
|
|
use roc_collections::all::MutMap;
|
|
use roc_module::{
|
|
ident::Lowercase,
|
|
symbol::{IdentIds, Interns, ModuleIds, Symbol},
|
|
};
|
|
use roc_parse::parser::{SourceError, SyntaxError};
|
|
use roc_region::all::Region;
|
|
use roc_solve::module::Solved;
|
|
use roc_types::{
|
|
pretty_print::{name_and_print_var, DebugPrint},
|
|
subs::{Subs, VarStore, Variable},
|
|
};
|
|
|
|
use super::Constraint;
|
|
use crate::{
|
|
constrain::constrain_expr,
|
|
lang::{
|
|
core::{
|
|
expr::{expr2::Expr2, expr_to_expr2::loc_expr_to_expr2, output::Output},
|
|
types::Type2,
|
|
},
|
|
env::Env,
|
|
scope::Scope,
|
|
},
|
|
mem_pool::pool::Pool,
|
|
solve_type,
|
|
};
|
|
use indoc::indoc;
|
|
|
|
fn run_solve(
|
|
arena: &Bump,
|
|
mempool: &mut Pool,
|
|
aliases: MutMap<Symbol, roc_types::types::Alias>,
|
|
rigid_variables: MutMap<Variable, Lowercase>,
|
|
constraint: Constraint,
|
|
var_store: VarStore,
|
|
) -> (Solved<Subs>, solve_type::Env, Vec<solve_type::TypeError>) {
|
|
let env = solve_type::Env {
|
|
vars_by_symbol: MutMap::default(),
|
|
aliases,
|
|
};
|
|
|
|
let mut subs = Subs::new_from_varstore(var_store);
|
|
|
|
for (var, name) in rigid_variables {
|
|
subs.rigid_var(var, name);
|
|
}
|
|
|
|
// Now that the module is parsed, canonicalized, and constrained,
|
|
// we need to type check it.
|
|
let mut problems = Vec::new();
|
|
|
|
// Run the solver to populate Subs.
|
|
let (solved_subs, solved_env) =
|
|
solve_type::run(arena, mempool, &env, &mut problems, subs, &constraint);
|
|
|
|
(solved_subs, solved_env, problems)
|
|
}
|
|
|
|
fn infer_eq(actual: &str, expected_str: &str) {
|
|
let mut env_pool = Pool::with_capacity(1024);
|
|
let env_arena = Bump::new();
|
|
let code_arena = Bump::new();
|
|
|
|
let mut var_store = VarStore::default();
|
|
let var = var_store.fresh();
|
|
let dep_idents = IdentIds::exposed_builtins(8);
|
|
let exposed_ident_ids = IdentIds::default();
|
|
let mut module_ids = ModuleIds::default();
|
|
let mod_id = module_ids.get_or_insert(&"ModId123".into());
|
|
|
|
let mut env = Env::new(
|
|
mod_id,
|
|
&env_arena,
|
|
&mut env_pool,
|
|
&mut var_store,
|
|
dep_idents,
|
|
&module_ids,
|
|
exposed_ident_ids,
|
|
);
|
|
|
|
let mut scope = Scope::new(env.home, env.pool, env.var_store);
|
|
|
|
let region = Region::zero();
|
|
|
|
let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region);
|
|
|
|
match expr2_result {
|
|
Ok((expr, output)) => {
|
|
let constraint = constrain_expr(
|
|
&code_arena,
|
|
&mut env,
|
|
&expr,
|
|
Expected::NoExpectation(Type2::Variable(var)),
|
|
Region::zero(),
|
|
);
|
|
|
|
let Env {
|
|
pool,
|
|
var_store: ref_var_store,
|
|
mut dep_idents,
|
|
..
|
|
} = env;
|
|
|
|
// extract the var_store out of the env again
|
|
let mut var_store = VarStore::default();
|
|
std::mem::swap(ref_var_store, &mut var_store);
|
|
|
|
let rigids = output.introduced_variables.name_by_var;
|
|
|
|
let (mut solved, _, _) = run_solve(
|
|
&code_arena,
|
|
pool,
|
|
Default::default(),
|
|
rigids,
|
|
constraint,
|
|
var_store,
|
|
);
|
|
|
|
let subs = solved.inner_mut();
|
|
|
|
// Connect the ModuleId to it's IdentIds
|
|
dep_idents.insert(mod_id, env.ident_ids);
|
|
|
|
let interns = Interns {
|
|
module_ids: env.module_ids.clone(),
|
|
all_ident_ids: dep_idents,
|
|
};
|
|
|
|
let actual_str =
|
|
name_and_print_var(var, subs, mod_id, &interns, DebugPrint::NOTHING);
|
|
|
|
assert_eq!(actual_str, expected_str);
|
|
}
|
|
Err(e) => panic!("syntax error {e:?}"),
|
|
}
|
|
}
|
|
|
|
pub fn str_to_expr2<'a>(
|
|
arena: &'a Bump,
|
|
input: &'a str,
|
|
env: &mut Env<'a>,
|
|
scope: &mut Scope,
|
|
region: Region,
|
|
) -> Result<(Expr2, Output), SourceError<'a, SyntaxError<'a>>> {
|
|
match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) {
|
|
Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)),
|
|
Err(fail) => Err(fail),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_str() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
"type inference!"
|
|
"#
|
|
),
|
|
"Str",
|
|
)
|
|
}
|
|
|
|
// This will be more useful once we actually map
|
|
// strings less than 15 chars to SmallStr
|
|
#[test]
|
|
fn constrain_small_str() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
"a"
|
|
"#
|
|
),
|
|
"Str",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_empty_record() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
{}
|
|
"#
|
|
),
|
|
"{}",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_small_int() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
12
|
|
"#
|
|
),
|
|
"Num *",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_float() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
3.14
|
|
"#
|
|
),
|
|
"Float *",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_record() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
{ x : 1, y : "hi" }
|
|
"#
|
|
),
|
|
"{ x : Num *, y : Str }",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_empty_list() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
[]
|
|
"#
|
|
),
|
|
"List *",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_list() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
[1, 2]
|
|
"#
|
|
),
|
|
"List (Num *)",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_list_of_records() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
[{ x: 1 }, { x: 3 }]
|
|
"#
|
|
),
|
|
"List { x : Num * }",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_tag() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
Foo
|
|
"#
|
|
),
|
|
"[Foo]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_call_and_accessor() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
.foo { foo: "bar" }
|
|
"#
|
|
),
|
|
"Str",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_access() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
{ foo: "bar" }.foo
|
|
"#
|
|
),
|
|
"Str",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_if() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
if True then Green else Red
|
|
"#
|
|
),
|
|
"[Green, Red]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_when() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
when if True then Green else Red is
|
|
Green -> Blue
|
|
Red -> Purple
|
|
"#
|
|
),
|
|
"[Blue, Purple]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_let_value() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
person = { name: "roc" }
|
|
|
|
person
|
|
"#
|
|
),
|
|
"{ name : Str }",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_update() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
person = { name: "roc" }
|
|
|
|
{ person & name: "bird" }
|
|
"#
|
|
),
|
|
"{ name : Str }",
|
|
)
|
|
}
|
|
|
|
#[ignore = "TODO: implement builtins in the editor"]
|
|
#[test]
|
|
fn constrain_run_low_level() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
List.map [{ name: "roc" }, { name: "bird" }] .name
|
|
"#
|
|
),
|
|
"List Str",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn dual_arity_lambda() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\a, b -> Pair a b
|
|
"#
|
|
),
|
|
"a, b -> [Pair a b]",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn anonymous_identity() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
(\a -> a) 3.14
|
|
"#
|
|
),
|
|
"Float *",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn identity_of_identity() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
(\val -> val) (\val -> val)
|
|
"#
|
|
),
|
|
"a -> a",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn identity_function() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\val -> val
|
|
"#
|
|
),
|
|
"a -> a",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn apply_function() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\f, x -> f x
|
|
"#
|
|
),
|
|
"(a -> b), a -> b",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn flip_function() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\f -> (\a, b -> f b a)
|
|
"#
|
|
),
|
|
"(a, b -> d) -> (b, a -> d)",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn always_function() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\val -> \_ -> val
|
|
"#
|
|
),
|
|
"a -> (* -> a)",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pass_a_function() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\f -> f {}
|
|
"#
|
|
),
|
|
"({} -> a) -> a",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn constrain_closure() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
x = 1
|
|
|
|
\{} -> x
|
|
"#
|
|
),
|
|
"{}* -> Num a",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn recursive_identity() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
identity = \val -> val
|
|
|
|
identity
|
|
"#
|
|
),
|
|
"a -> a",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn use_apply() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
identity = \a -> a
|
|
apply = \f, x -> f x
|
|
|
|
apply identity 5
|
|
"#
|
|
),
|
|
"Num *",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn nested_let_function() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
curryPair = \a ->
|
|
getB = \b -> Pair a b
|
|
getB
|
|
|
|
curryPair
|
|
"#
|
|
),
|
|
"a -> (b -> [Pair a b])",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn record_with_bound_var() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
fn = \rec ->
|
|
x = rec.x
|
|
|
|
rec
|
|
|
|
fn
|
|
"#
|
|
),
|
|
"{ x : a }b -> { x : a }b",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn using_type_signature() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
bar : custom -> custom
|
|
bar = \x -> x
|
|
|
|
bar
|
|
"#
|
|
),
|
|
"custom -> custom",
|
|
);
|
|
}
|
|
|
|
#[ignore = "Currently panics at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1212:21"]
|
|
#[test]
|
|
fn using_type_signature2() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
id1 : tya -> tya
|
|
id1 = \x -> x
|
|
|
|
id2 : tyb -> tyb
|
|
id2 = id1
|
|
|
|
id2
|
|
"#
|
|
),
|
|
"tyb -> tyb",
|
|
);
|
|
}
|
|
|
|
#[ignore = "Implement annotation-only decls"]
|
|
#[test]
|
|
fn type_signature_without_body() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
foo: Str -> {}
|
|
|
|
foo "hi"
|
|
"#
|
|
),
|
|
"{}",
|
|
);
|
|
}
|
|
|
|
#[ignore = "Implement annotation-only decls"]
|
|
#[test]
|
|
fn type_signature_without_body_rigid() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
foo : Num * -> custom
|
|
|
|
foo 2
|
|
"#
|
|
),
|
|
"custom",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn inference_var_inside_arrow() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
id : _ -> _
|
|
id = \x -> x
|
|
id
|
|
"#
|
|
),
|
|
"a -> a",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: Type2::substitute"]
|
|
fn inference_var_inside_ctor() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
canIGo : _ -> Result _ _
|
|
canIGo = \color ->
|
|
when color is
|
|
"green" -> Ok "go!"
|
|
"yellow" -> Err (SlowIt "whoa, let's slow down!")
|
|
"red" -> Err (StopIt "absolutely not")
|
|
_ -> Err (UnknownColor "this is a weird stoplight")
|
|
canIGo
|
|
"#
|
|
),
|
|
"Str -> Result Str [SlowIt Str, StopIt Str, UnknownColor Str]*",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: Gives { x : *, y : * } -> { x : *, y : * }. This is a bug in typechecking defs with annotations."]
|
|
fn inference_var_inside_ctor_linked() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
swapRcd: {x: _, y: _} -> {x: _, y: _}
|
|
swapRcd = \{x, y} -> {x: y, y: x}
|
|
swapRcd
|
|
"#
|
|
),
|
|
"{ x : a, y : b } -> { x : b, y : a }",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn inference_var_link_with_rigid() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
swapRcd: {x: tx, y: ty} -> {x: _, y: _}
|
|
swapRcd = \{x, y} -> {x: y, y: x}
|
|
swapRcd
|
|
"#
|
|
),
|
|
"{ x : tx, y : ty } -> { x : ty, y : tx }",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: Type2::substitute"]
|
|
fn inference_var_inside_tag_ctor() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
badComics: Bool -> [CowTools _, Thagomizer _]
|
|
badComics = \c ->
|
|
when c is
|
|
True -> CowTools "The Far Side"
|
|
False -> Thagomizer "The Far Side"
|
|
badComics
|
|
"#
|
|
),
|
|
"Bool -> [CowTools Str, Thagomizer Str]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn inference_var_tag_union_ext() {
|
|
// TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here.
|
|
// See https://github.com/roc-lang/roc/issues/2053
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
pastelize: _ -> [Lavender, Peach]_
|
|
pastelize = \color ->
|
|
when color is
|
|
Blue -> Lavender
|
|
Orange -> Peach
|
|
col -> col
|
|
pastelize
|
|
"#
|
|
),
|
|
"[Blue, Lavender, Orange, Peach]a -> [Blue, Lavender, Orange, Peach]a",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: gives { email : a, name : b }c -> { email : a, name : b }c. This is a bug in typechecking defs with annotations."]
|
|
fn inference_var_rcd_union_ext() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
setRocEmail : _ -> { name: Str, email: Str }_
|
|
setRocEmail = \person ->
|
|
{ person & email: "\(person.name)@roclang.com" }
|
|
setRocEmail
|
|
"#
|
|
),
|
|
"{ email : Str, name : Str }a -> { email : Str, name : Str }a",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn infer_union_input_position1() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\tag ->
|
|
when tag is
|
|
A -> X
|
|
B -> Y
|
|
"#
|
|
),
|
|
"[A, B] -> [X, Y]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn infer_union_input_position2() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\tag ->
|
|
when tag is
|
|
A -> X
|
|
B -> Y
|
|
_ -> Z
|
|
"#
|
|
),
|
|
"[A, B]* -> [X, Y, Z]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn infer_union_input_position3() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\tag ->
|
|
when tag is
|
|
A M -> X
|
|
A N -> Y
|
|
"#
|
|
),
|
|
"[A [M, N]] -> [X, Y]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn infer_union_input_position4() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\tag ->
|
|
when tag is
|
|
A M -> X
|
|
A N -> Y
|
|
A _ -> Z
|
|
"#
|
|
),
|
|
"[A [M, N]] -> [X, Y, Z]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: currently [A [M [J]*, N [K]*]] -> [X]*"]
|
|
fn infer_union_input_position5() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\tag ->
|
|
when tag is
|
|
A (M J) -> X
|
|
A (N K) -> X
|
|
"#
|
|
),
|
|
"[A [M [J], N [K]]] -> [X]*",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn infer_union_input_position6() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\tag ->
|
|
when tag is
|
|
A M -> X
|
|
B -> X
|
|
A N -> X
|
|
"#
|
|
),
|
|
"[A [M, N], B] -> [X]",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: currently [A]* -> [A, X]*"]
|
|
fn infer_union_input_position7() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\tag ->
|
|
when tag is
|
|
A -> X
|
|
t -> t
|
|
"#
|
|
),
|
|
// TODO: we could be a bit smarter by subtracting "A" as a possible
|
|
// tag in the union known by t, which would yield the principal type
|
|
// [A,]a -> [X]a
|
|
"[A, X]a -> [A, X]a",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn infer_union_input_position8() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\opt ->
|
|
when opt is
|
|
Some ({tag: A}) -> 1
|
|
Some ({tag: B}) -> 1
|
|
None -> 0
|
|
"#
|
|
),
|
|
"[None, Some { tag : [A, B] }*] -> Num *",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: panicked at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1208:21"]
|
|
fn infer_union_input_position9() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
opt : [Some Str, None]
|
|
opt = Some ""
|
|
rcd = { opt }
|
|
|
|
when rcd is
|
|
{ opt: Some s } -> s
|
|
{ opt: None } -> "?"
|
|
"#
|
|
),
|
|
"Str",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "TODO: currently <type mismatch> -> Num a"]
|
|
fn infer_union_input_position10() {
|
|
infer_eq(
|
|
indoc!(
|
|
r#"
|
|
\r ->
|
|
when r is
|
|
{ x: Blue, y ? 3 } -> y
|
|
{ x: Red, y ? 5 } -> y
|
|
"#
|
|
),
|
|
"{ x : [Blue, Red], y ? Num a }* -> Num a",
|
|
)
|
|
}
|
|
}
|