mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Constrain canonical exprs and modules
This commit is contained in:
parent
be2d20162c
commit
ffd3cc4211
17 changed files with 1130 additions and 431 deletions
|
@ -5,6 +5,7 @@ use crate::can::expr::{
|
|||
canonicalize_expr, local_successors, references_from_call, references_from_local, union_pairs,
|
||||
Output, Recursive,
|
||||
};
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::can::pattern::remove_idents;
|
||||
use crate::can::pattern::PatternType::*;
|
||||
use crate::can::pattern::{canonicalize_pattern, idents_from_patterns, Pattern};
|
||||
|
@ -26,9 +27,11 @@ use std::fmt::Debug;
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Def {
|
||||
pub pattern: Located<Pattern>,
|
||||
pub expr: Located<Expr>,
|
||||
pub vars_by_symbol: SendMap<Symbol, Variable>,
|
||||
pub loc_pattern: Located<Pattern>,
|
||||
pub loc_expr: Located<Expr>,
|
||||
pub body_var: Variable,
|
||||
pub pattern_vars: SendMap<Symbol, Variable>,
|
||||
pub annotation: Option<(Type, SendMap<Variable, Lowercase>)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -41,6 +44,7 @@ pub struct CanDefs {
|
|||
#[inline(always)]
|
||||
pub fn canonicalize_defs<'a>(
|
||||
env: &mut Env,
|
||||
found_rigids: &mut SendMap<Variable, Lowercase>,
|
||||
var_store: &VarStore,
|
||||
scope: &mut Scope,
|
||||
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
|
||||
|
@ -77,6 +81,7 @@ pub fn canonicalize_defs<'a>(
|
|||
|
||||
canonicalize_def(
|
||||
env,
|
||||
found_rigids,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &typed,
|
||||
|
@ -90,6 +95,7 @@ pub fn canonicalize_defs<'a>(
|
|||
_ => {
|
||||
canonicalize_def(
|
||||
env,
|
||||
found_rigids,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &loc_def.value,
|
||||
|
@ -106,6 +112,7 @@ pub fn canonicalize_defs<'a>(
|
|||
_ => {
|
||||
canonicalize_def(
|
||||
env,
|
||||
found_rigids,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &loc_def.value,
|
||||
|
@ -229,10 +236,12 @@ pub fn sort_can_defs(
|
|||
let mut new_def = can_def.clone();
|
||||
|
||||
// Determine recursivity of closures that are not tail-recursive
|
||||
if let Closure(name, Recursive::NotRecursive, args, body) = new_def.expr.value {
|
||||
if let Closure(name, Recursive::NotRecursive, args, body) =
|
||||
new_def.loc_expr.value
|
||||
{
|
||||
let recursion = closure_recursivity(symbol.clone(), &env.closures);
|
||||
|
||||
new_def.expr.value = Closure(name, recursion, args, body);
|
||||
new_def.loc_expr.value = Closure(name, recursion, args, body);
|
||||
}
|
||||
|
||||
can_defs.push(new_def);
|
||||
|
@ -272,7 +281,7 @@ pub fn sort_can_defs(
|
|||
let mut regions = Vec::with_capacity(can_defs_by_symbol.len());
|
||||
|
||||
for def in can_defs_by_symbol.values() {
|
||||
regions.push((def.pattern.region, def.expr.region));
|
||||
regions.push((def.loc_pattern.region, def.loc_expr.region));
|
||||
}
|
||||
|
||||
(
|
||||
|
@ -288,7 +297,6 @@ fn canonicalize_def_pattern(
|
|||
loc_pattern: &Located<ast::Pattern>,
|
||||
scope: &mut Scope,
|
||||
var_store: &VarStore,
|
||||
expr_type: Type,
|
||||
) -> Located<Pattern> {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
|
@ -308,6 +316,7 @@ fn canonicalize_def_pattern(
|
|||
|
||||
fn canonicalize_def<'a>(
|
||||
env: &mut Env,
|
||||
found_rigids: &mut SendMap<Variable, Lowercase>,
|
||||
loc_def: Located<&'a ast::Def<'a>>,
|
||||
scope: &mut Scope,
|
||||
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
||||
|
@ -318,21 +327,22 @@ fn canonicalize_def<'a>(
|
|||
|
||||
// Make types for the body expr, even if we won't end up having a body.
|
||||
let expr_var = var_store.fresh();
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
let mut vars_by_symbol = SendMap::default();
|
||||
|
||||
// Each def gets to have all the idents in scope that are defined in this
|
||||
// block. Order of defs doesn't matter, thanks to referential transparency!
|
||||
match loc_def.value {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
let loc_can_pattern =
|
||||
canonicalize_def_pattern(env, loc_pattern, scope, var_store, expr_type.clone());
|
||||
let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store);
|
||||
|
||||
// annotation sans body cannot introduce new rigids that are visible in other annotations
|
||||
// but the rigids can show up in type error messages, so still register them
|
||||
let (_, can_annotation) = canonicalize_annotation(&loc_annotation.value, var_store);
|
||||
if true {
|
||||
panic!("TODO replace this call to canonicalize_annotation with something that *only* gets the arity, since that's all we use");
|
||||
let (seen_rigids, can_annotation) =
|
||||
canonicalize_annotation(&loc_annotation.value, var_store);
|
||||
|
||||
// union seen rigids with already found ones
|
||||
for (k, v) in seen_rigids {
|
||||
found_rigids.insert(k, v);
|
||||
}
|
||||
|
||||
let arity = can_annotation.arity();
|
||||
|
@ -364,7 +374,7 @@ fn canonicalize_def<'a>(
|
|||
region: loc_annotation.region,
|
||||
};
|
||||
|
||||
let body = Box::new((var_store.fresh(), body_expr));
|
||||
let body = Box::new((body_expr, var_store.fresh()));
|
||||
|
||||
Located {
|
||||
value: Closure(symbol, Recursive::NotRecursive, underscores, body),
|
||||
|
@ -376,22 +386,31 @@ fn canonicalize_def<'a>(
|
|||
can_defs_by_symbol.insert(
|
||||
symbol,
|
||||
Def {
|
||||
body_var: var_store.fresh(),
|
||||
// TODO try to remove this .clone()!
|
||||
pattern: loc_can_pattern.clone(),
|
||||
expr: Located {
|
||||
loc_pattern: loc_can_pattern.clone(),
|
||||
loc_expr: Located {
|
||||
region: loc_can_expr.region,
|
||||
// TODO try to remove this .clone()!
|
||||
value: loc_can_expr.value.clone(),
|
||||
},
|
||||
vars_by_symbol: im::HashMap::clone(&vars_by_symbol),
|
||||
pattern_vars: im::HashMap::clone(&vars_by_symbol),
|
||||
annotation: Some((can_annotation.clone(), found_rigids.clone())),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TypedDef(loc_pattern, loc_annotation, loc_expr) => {
|
||||
let loc_can_pattern =
|
||||
canonicalize_def_pattern(env, loc_pattern, scope, var_store, expr_type.clone());
|
||||
let (seen_rigids, can_annotation) =
|
||||
canonicalize_annotation(&loc_annotation.value, var_store);
|
||||
|
||||
// union seen rigids with already found ones
|
||||
for (k, v) in seen_rigids {
|
||||
found_rigids.insert(k, v);
|
||||
}
|
||||
|
||||
let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store);
|
||||
|
||||
// bookkeeping for tail-call detection. If we're assigning to an
|
||||
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
||||
|
@ -468,8 +487,6 @@ fn canonicalize_def<'a>(
|
|||
);
|
||||
}
|
||||
|
||||
let mut defined_symbols = Vec::new();
|
||||
|
||||
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
||||
// which defined names reference each other.
|
||||
for (ident, (symbol, region)) in
|
||||
|
@ -496,21 +513,19 @@ fn canonicalize_def<'a>(
|
|||
),
|
||||
);
|
||||
|
||||
defined_symbols.push(symbol.clone());
|
||||
}
|
||||
|
||||
for symbol in defined_symbols {
|
||||
can_defs_by_symbol.insert(
|
||||
symbol,
|
||||
Def {
|
||||
body_var: var_store.fresh(),
|
||||
// TODO try to remove this .clone()!
|
||||
pattern: loc_can_pattern.clone(),
|
||||
expr: Located {
|
||||
loc_pattern: loc_can_pattern.clone(),
|
||||
loc_expr: Located {
|
||||
region: loc_can_expr.region,
|
||||
// TODO try to remove this .clone()!
|
||||
value: loc_can_expr.value.clone(),
|
||||
},
|
||||
vars_by_symbol: im::HashMap::clone(&vars_by_symbol),
|
||||
pattern_vars: im::HashMap::clone(&vars_by_symbol),
|
||||
annotation: Some((can_annotation.clone(), found_rigids.clone())),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -518,8 +533,7 @@ fn canonicalize_def<'a>(
|
|||
// If we have a pattern, then the def has a body (that is, it's not a
|
||||
// standalone annotation), so we need to canonicalize the pattern and expr.
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
let loc_can_pattern =
|
||||
canonicalize_def_pattern(env, loc_pattern, scope, var_store, expr_type.clone());
|
||||
let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store);
|
||||
|
||||
// bookkeeping for tail-call detection. If we're assigning to an
|
||||
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
||||
|
@ -600,8 +614,6 @@ fn canonicalize_def<'a>(
|
|||
);
|
||||
}
|
||||
|
||||
let mut defined_symbols = Vec::new();
|
||||
|
||||
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
||||
// which defined names reference each other.
|
||||
for (ident, (symbol, region)) in
|
||||
|
@ -628,21 +640,19 @@ fn canonicalize_def<'a>(
|
|||
),
|
||||
);
|
||||
|
||||
defined_symbols.push(symbol.clone());
|
||||
}
|
||||
|
||||
for symbol in defined_symbols {
|
||||
can_defs_by_symbol.insert(
|
||||
symbol,
|
||||
Def {
|
||||
body_var: var_store.fresh(),
|
||||
// TODO try to remove this .clone()!
|
||||
pattern: loc_can_pattern.clone(),
|
||||
expr: Located {
|
||||
loc_pattern: loc_can_pattern.clone(),
|
||||
loc_expr: Located {
|
||||
region: loc_can_expr.region,
|
||||
// TODO try to remove this .clone()!
|
||||
value: loc_can_expr.value.clone(),
|
||||
},
|
||||
vars_by_symbol: im::HashMap::clone(&vars_by_symbol),
|
||||
pattern_vars: im::HashMap::clone(&vars_by_symbol),
|
||||
annotation: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -651,6 +661,7 @@ fn canonicalize_def<'a>(
|
|||
Nested(value) => {
|
||||
canonicalize_def(
|
||||
env,
|
||||
found_rigids,
|
||||
Located {
|
||||
value,
|
||||
region: loc_def.region,
|
||||
|
@ -723,7 +734,8 @@ pub fn can_defs_with_return<'a>(
|
|||
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
|
||||
loc_ret: &'a Located<ast::Expr<'a>>,
|
||||
) -> (Expr, Output) {
|
||||
let unsorted = canonicalize_defs(env, var_store, &mut scope, loc_defs);
|
||||
let mut found_rigids = SendMap::default();
|
||||
let unsorted = canonicalize_defs(env, &mut found_rigids, var_store, &mut scope, loc_defs);
|
||||
|
||||
// The def as a whole is a tail call iff its return expression is a tail call.
|
||||
// Use its output as a starting point because its tail_call already has the right answer!
|
||||
|
@ -732,6 +744,8 @@ pub fn can_defs_with_return<'a>(
|
|||
|
||||
let (can_defs, mut output) = sort_can_defs(env, unsorted, output);
|
||||
|
||||
output.rigids = output.rigids.union(found_rigids);
|
||||
|
||||
match can_defs {
|
||||
Ok(defs) => (Defs(defs, Box::new(ret_expr)), output),
|
||||
Err(err) => (RuntimeError(err), output),
|
||||
|
|
|
@ -5,9 +5,9 @@ use crate::can::num::{
|
|||
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result,
|
||||
int_expr_from_result,
|
||||
};
|
||||
use crate::can::pattern::idents_from_patterns;
|
||||
use crate::can::pattern::PatternType::*;
|
||||
use crate::can::pattern::{canonicalize_pattern, remove_idents, Pattern};
|
||||
use crate::can::pattern::{idents_from_patterns, PatternState};
|
||||
use crate::can::problem::Problem;
|
||||
use crate::can::problem::RuntimeError;
|
||||
use crate::can::problem::RuntimeError::*;
|
||||
|
@ -20,23 +20,16 @@ use crate::operator::CalledVia;
|
|||
use crate::parse::ast;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::{VarStore, Variable};
|
||||
use crate::types::AnnotationSource::*;
|
||||
use crate::types::Expected::{self, *};
|
||||
use crate::types::Type::{self, *};
|
||||
use im_rc::Vector;
|
||||
use std::fmt::Debug;
|
||||
use std::i64;
|
||||
use std::ops::Neg;
|
||||
|
||||
/// Whenever we encounter a user-defined type variable (a "rigid" var for short),
|
||||
/// 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 type Rigids = ImMap<Box<str>, Type>;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
pub struct Output {
|
||||
pub references: References,
|
||||
pub tail_call: Option<Symbol>,
|
||||
pub rigids: SendMap<Variable, Lowercase>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -49,31 +42,35 @@ pub enum Expr {
|
|||
List(Variable, Vec<(Variable, Located<Expr>)>),
|
||||
|
||||
// Lookups
|
||||
Var(Variable, Symbol),
|
||||
/// Works the same as Var, but has an important marking purpose.
|
||||
/// See 13623e3f5f65ea2d703cf155f16650c1e8246502 for the bug this fixed.
|
||||
FunctionPointer(Variable, Symbol),
|
||||
|
||||
Var {
|
||||
symbol_for_lookup: Symbol,
|
||||
resolved_symbol: Symbol,
|
||||
},
|
||||
// Pattern Matching
|
||||
/// When is guaranteed to be exhaustive at this point. (If it wasn't, then
|
||||
/// a _ branch was added at the end that will throw a runtime error.)
|
||||
/// Also, `If` is desugared into `When` matching on `False` and `_` at this point.
|
||||
When(
|
||||
Variable,
|
||||
Box<Located<Expr>>,
|
||||
Vec<((Variable, Located<Pattern>), (Variable, Located<Expr>))>,
|
||||
),
|
||||
When {
|
||||
cond_var: Variable,
|
||||
expr_var: Variable,
|
||||
loc_cond: Box<Located<Expr>>,
|
||||
branches: Vec<(Located<Pattern>, Located<Expr>)>,
|
||||
},
|
||||
Defs(Vec<Def>, Box<Located<Expr>>),
|
||||
|
||||
/// This is *only* for calling functions, not for tag application.
|
||||
/// The Tag variant contains any applied values inside it.
|
||||
Call(Box<Expr>, Vec<(Variable, Located<Expr>)>, CalledVia),
|
||||
Call(
|
||||
Box<(Variable, Located<Expr>, Variable)>,
|
||||
Vec<(Variable, Located<Expr>)>,
|
||||
CalledVia,
|
||||
),
|
||||
|
||||
Closure(
|
||||
Symbol,
|
||||
Recursive,
|
||||
Vec<(Variable, Located<Pattern>)>,
|
||||
Box<(Variable, Located<Expr>)>,
|
||||
Box<(Located<Expr>, Variable)>,
|
||||
),
|
||||
|
||||
// Product Types
|
||||
|
@ -190,11 +187,10 @@ pub fn canonicalize_expr(
|
|||
// The expression that evaluates to the function being called, e.g. `foo` in
|
||||
// (foo) bar baz
|
||||
let fn_region = loc_fn.region;
|
||||
// TODO look up the name and use NamedFnArg if possible.
|
||||
|
||||
// Canonicalize the function expression and its arguments
|
||||
let (fn_expr, mut output) =
|
||||
canonicalize_expr(env, var_store, scope, loc_fn.region, &loc_fn.value);
|
||||
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
|
||||
|
||||
// The function's return type
|
||||
let mut args = Vec::new();
|
||||
|
@ -212,26 +208,35 @@ pub fn canonicalize_expr(
|
|||
output.tail_call = None;
|
||||
|
||||
let expr = match fn_expr.value {
|
||||
Var(_, ref sym) | FunctionPointer(_, ref sym) => {
|
||||
// In the FunctionPointer case, we're calling an inline closure;
|
||||
// something like ((\a b -> a + b) 1 2).
|
||||
output.references.calls.insert(sym.clone());
|
||||
Var {
|
||||
ref resolved_symbol,
|
||||
..
|
||||
} => {
|
||||
output.references.calls.insert(resolved_symbol.clone());
|
||||
|
||||
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
|
||||
output.tail_call = match &env.tailcallable_symbol {
|
||||
Some(tc_sym) if tc_sym == sym => Some(sym.clone()),
|
||||
Some(tc_sym) if tc_sym == resolved_symbol => Some(resolved_symbol.clone()),
|
||||
Some(_) | None => None,
|
||||
};
|
||||
|
||||
Call(Box::new(fn_expr.value), args, *application_style)
|
||||
Call(
|
||||
Box::new((var_store.fresh(), fn_expr, var_store.fresh())),
|
||||
args,
|
||||
*application_style,
|
||||
)
|
||||
}
|
||||
RuntimeError(_) => {
|
||||
// We can't call a runtime error; bail out by propagating it!
|
||||
return (fn_expr, output);
|
||||
}
|
||||
not_var => {
|
||||
_ => {
|
||||
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
|
||||
Call(Box::new(not_var), args, *application_style)
|
||||
Call(
|
||||
Box::new((var_store.fresh(), fn_expr, var_store.fresh())),
|
||||
args,
|
||||
*application_style,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -250,7 +255,7 @@ pub fn canonicalize_expr(
|
|||
|
||||
let ident = Ident::new(module_parts, name);
|
||||
|
||||
canonicalize_lookup(env, scope, ident, symbol, region, var_store)
|
||||
canonicalize_lookup(env, scope, ident, symbol, region)
|
||||
} //ast::Expr::InterpolatedStr(pairs, suffix) => {
|
||||
// let mut output = Output::new();
|
||||
// let can_pairs: Vec<(String, Located<Expr>)> = pairs
|
||||
|
@ -326,11 +331,6 @@ pub fn canonicalize_expr(
|
|||
// it means there was shadowing, which will be handled later.
|
||||
scope.idents = union_pairs(scope.idents, arg_idents.iter());
|
||||
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
vars: Vec::with_capacity(loc_arg_patterns.len()),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
let mut can_args = Vec::with_capacity(loc_arg_patterns.len());
|
||||
|
||||
for loc_pattern in loc_arg_patterns.into_iter() {
|
||||
|
@ -384,7 +384,7 @@ pub fn canonicalize_expr(
|
|||
symbol,
|
||||
Recursive::NotRecursive,
|
||||
can_args,
|
||||
Box::new((var_store.fresh(), loc_body_expr)),
|
||||
Box::new((loc_body_expr, var_store.fresh())),
|
||||
),
|
||||
output,
|
||||
)
|
||||
|
@ -392,7 +392,6 @@ pub fn canonicalize_expr(
|
|||
ast::Expr::When(loc_cond, branches) => {
|
||||
// Infer the condition expression's type.
|
||||
let cond_var = var_store.fresh();
|
||||
let cond_type = Variable(cond_var);
|
||||
let (can_cond, mut output) =
|
||||
canonicalize_expr(env, var_store, scope, region, &loc_cond.value);
|
||||
|
||||
|
@ -401,7 +400,7 @@ pub fn canonicalize_expr(
|
|||
|
||||
let mut can_branches = Vec::with_capacity(branches.len());
|
||||
|
||||
for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() {
|
||||
for (loc_pattern, loc_expr) in branches {
|
||||
let mut shadowable_idents = scope.idents.clone();
|
||||
|
||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
@ -418,10 +417,7 @@ pub fn canonicalize_expr(
|
|||
|
||||
output.references = output.references.union(branch_references);
|
||||
|
||||
can_branches.push((
|
||||
(var_store.fresh(), can_pattern),
|
||||
(var_store.fresh(), loc_can_expr),
|
||||
));
|
||||
can_branches.push((can_pattern, loc_can_expr));
|
||||
}
|
||||
|
||||
// A "when" with no branches is a runtime error, but it will mess things up
|
||||
|
@ -432,7 +428,12 @@ pub fn canonicalize_expr(
|
|||
}
|
||||
|
||||
// Incorporate all three expressions into a combined Output value.
|
||||
let expr = When(cond_var, Box::new(can_cond), can_branches);
|
||||
let expr = When {
|
||||
expr_var: var_store.fresh(),
|
||||
cond_var,
|
||||
loc_cond: Box::new(can_cond),
|
||||
branches: can_branches,
|
||||
};
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
|
@ -549,15 +550,17 @@ fn canonicalize_lookup(
|
|||
env: &mut Env,
|
||||
scope: &Scope,
|
||||
ident: Ident,
|
||||
symbol: Symbol,
|
||||
symbol_for_lookup: Symbol,
|
||||
region: Region,
|
||||
var_store: &VarStore,
|
||||
) -> (Expr, Output) {
|
||||
use self::Expr::*;
|
||||
|
||||
let mut output = Output::default();
|
||||
let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) {
|
||||
Ok(sub_symbol) => Var(var_store.fresh(), sub_symbol),
|
||||
Ok(resolved_symbol) => Var {
|
||||
symbol_for_lookup,
|
||||
resolved_symbol,
|
||||
},
|
||||
Err(ident) => {
|
||||
let loc_ident = Located {
|
||||
region,
|
||||
|
|
|
@ -6,8 +6,9 @@ use crate::can::scope::Scope;
|
|||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::SendMap;
|
||||
use crate::parse::ast::{self, ExposesEntry};
|
||||
use crate::region::Located;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::{VarStore, Variable};
|
||||
use crate::types::Constraint;
|
||||
use bumpalo::Bump;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -15,6 +16,7 @@ pub struct Module {
|
|||
pub name: Option<Box<str>>,
|
||||
pub defs: Vec<Def>,
|
||||
pub exposed_imports: SendMap<Symbol, Variable>,
|
||||
pub constraint: Constraint,
|
||||
}
|
||||
|
||||
pub fn canonicalize_module_defs<'a, I>(
|
||||
|
@ -24,7 +26,11 @@ pub fn canonicalize_module_defs<'a, I>(
|
|||
_exposes: I,
|
||||
scope: &mut Scope,
|
||||
var_store: &VarStore,
|
||||
) -> (Vec<Def>, SendMap<Symbol, Variable>)
|
||||
) -> (
|
||||
Vec<Def>,
|
||||
SendMap<Symbol, Variable>,
|
||||
Vec<(Symbol, Variable, Region)>,
|
||||
)
|
||||
where
|
||||
I: Iterator<Item = Located<ExposesEntry<'a>>>,
|
||||
{
|
||||
|
@ -48,6 +54,7 @@ where
|
|||
}
|
||||
|
||||
let mut env = Env::new(home);
|
||||
let mut lookups = Vec::with_capacity(scope.idents.len());
|
||||
|
||||
// Exposed values are treated like defs that appear before any others, e.g.
|
||||
//
|
||||
|
@ -62,17 +69,24 @@ where
|
|||
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
|
||||
for (ident, (symbol, region)) in scope.idents.iter() {
|
||||
if ident.first_char().is_lowercase() {
|
||||
let expr_var = var_store.fresh();
|
||||
|
||||
// Add an entry to exposed_imports using the current module's name
|
||||
// as the key; e.g. if this is the Foo module and we have
|
||||
// exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when
|
||||
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
||||
exposed_imports.insert(scope.symbol(&*ident.clone().name()), var_store.fresh());
|
||||
exposed_imports.insert(scope.symbol(&*ident.clone().name()), expr_var);
|
||||
|
||||
// This will be used during constraint generation,
|
||||
// to add the usual Lookup constraint as if this were a normal def.
|
||||
lookups.push((symbol.clone(), expr_var, *region));
|
||||
} else {
|
||||
// TODO add type aliases to type alias dictionary, based on exposed types
|
||||
}
|
||||
}
|
||||
|
||||
let defs = canonicalize_defs(&mut env, var_store, scope, &desugared);
|
||||
let mut output = Output::default();
|
||||
let defs = canonicalize_defs(&mut env, &mut output.rigids, var_store, scope, &desugared);
|
||||
let defs = match sort_can_defs(&mut env, defs, Output::default()) {
|
||||
(Ok(defs), _) => {
|
||||
// TODO examine the patterns, extract toplevel identifiers from them,
|
||||
|
@ -89,5 +103,5 @@ where
|
|||
// TODO incorporate rigids into here (possibly by making this be a Let instead
|
||||
// of an And)
|
||||
|
||||
(defs, exposed_imports)
|
||||
(defs, exposed_imports, lookups)
|
||||
}
|
||||
|
|
|
@ -4,13 +4,12 @@ use crate::can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_
|
|||
use crate::can::problem::Problem;
|
||||
use crate::can::scope::Scope;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, SendMap};
|
||||
use crate::collections::ImMap;
|
||||
use crate::ident::Ident;
|
||||
use crate::parse::ast;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::VarStore;
|
||||
use crate::subs::Variable;
|
||||
use crate::types::{Constraint, PExpected, PatternCategory, Type};
|
||||
use im_rc::Vector;
|
||||
|
||||
/// A pattern, including possible problems (e.g. shadowing) so that
|
||||
|
@ -23,8 +22,16 @@ pub enum Pattern {
|
|||
AppliedTag(Symbol, Vec<Located<Pattern>>),
|
||||
IntLiteral(i64),
|
||||
FloatLiteral(f64),
|
||||
ExactString(Box<str>),
|
||||
RecordDestructure(Vec<(Located<Pattern>, Option<Located<Pattern>>)>),
|
||||
StrLiteral(Box<str>),
|
||||
RecordDestructure(
|
||||
Variable,
|
||||
Vec<(
|
||||
Variable,
|
||||
Lowercase,
|
||||
Symbol,
|
||||
Option<(Variable, Located<Pattern>)>,
|
||||
)>,
|
||||
),
|
||||
Underscore,
|
||||
|
||||
// Runtime Exceptions
|
||||
|
@ -137,7 +144,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
&StrLiteral(_string) => match pattern_type {
|
||||
WhenBranch => {
|
||||
panic!("TODO check whether string pattern is malformed.");
|
||||
// Pattern::ExactString((*string).into())
|
||||
// Pattern::StrLiteral((*string).into())
|
||||
}
|
||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
unsupported_pattern(env, ptype, region)
|
||||
|
@ -157,32 +164,49 @@ pub fn canonicalize_pattern<'a>(
|
|||
}
|
||||
&RecordDestructure(patterns) => {
|
||||
let mut fields = Vec::with_capacity(patterns.len());
|
||||
|
||||
for loc_pattern in patterns {
|
||||
match loc_pattern.value {
|
||||
Identifier(ref name) => {
|
||||
let result = match canonicalize_pattern_identifier(
|
||||
name,
|
||||
Identifier(label) => {
|
||||
let symbol = match canonicalize_pattern_identifier(
|
||||
&label,
|
||||
env,
|
||||
scope,
|
||||
region,
|
||||
shadowable_idents,
|
||||
) {
|
||||
Ok(symbol) => Pattern::Identifier(symbol),
|
||||
Err(loc_shadowed_ident) => Pattern::Shadowed(loc_shadowed_ident),
|
||||
Ok(symbol) => symbol,
|
||||
Err(loc_shadowed_ident) => {
|
||||
// If any idents are shadowed, consider the entire
|
||||
// destructure pattern shadowed!
|
||||
let _loc_pattern = Located {
|
||||
region,
|
||||
value: Pattern::Shadowed(loc_shadowed_ident),
|
||||
};
|
||||
panic!("TODO gather all the shadowing errors, not just the first one, and report them in Problems.");
|
||||
}
|
||||
};
|
||||
|
||||
fields.push((Located::at(region, result), None));
|
||||
fields.push((var_store.fresh(), Lowercase::from(label), symbol, None));
|
||||
}
|
||||
RecordField(ref name, loc_guard) => {
|
||||
let result = match canonicalize_pattern_identifier(
|
||||
name,
|
||||
RecordField(label, loc_guard) => {
|
||||
let symbol = match canonicalize_pattern_identifier(
|
||||
&label,
|
||||
env,
|
||||
scope,
|
||||
region,
|
||||
shadowable_idents,
|
||||
) {
|
||||
Ok(symbol) => Pattern::Identifier(symbol),
|
||||
Err(loc_shadowed_ident) => Pattern::Shadowed(loc_shadowed_ident),
|
||||
Ok(symbol) => symbol,
|
||||
Err(loc_shadowed_ident) => {
|
||||
// If any idents are shadowed, consider the entire
|
||||
// destructure pattern shadowed!
|
||||
let _loc_pattern = Located {
|
||||
region,
|
||||
value: Pattern::Shadowed(loc_shadowed_ident),
|
||||
};
|
||||
panic!("TODO gather all the shadowing errors, not just the first one, and report them in Problems.");
|
||||
}
|
||||
};
|
||||
|
||||
let can_guard = canonicalize_pattern(
|
||||
|
@ -195,12 +219,18 @@ pub fn canonicalize_pattern<'a>(
|
|||
shadowable_idents,
|
||||
);
|
||||
|
||||
fields.push((Located::at(region, result), Some(can_guard)));
|
||||
fields.push((
|
||||
var_store.fresh(),
|
||||
Lowercase::from(label),
|
||||
symbol,
|
||||
Some((var_store.fresh(), can_guard)),
|
||||
));
|
||||
}
|
||||
_ => panic!("invalid pattern in record"),
|
||||
}
|
||||
}
|
||||
Pattern::RecordDestructure(fields)
|
||||
|
||||
Pattern::RecordDestructure(var_store.fresh(), fields)
|
||||
}
|
||||
&RecordField(_name, _loc_pattern) => {
|
||||
unreachable!("should be handled in RecordDestructure");
|
||||
|
@ -279,10 +309,8 @@ pub fn canonicalize_pattern_identifier<'a>(
|
|||
// tag application patterns, which can bring multiple
|
||||
// new idents into scope. For example, it's important that
|
||||
// we catch (Blah foo foo) -> … as being an example of shadowing.
|
||||
scope
|
||||
.idents
|
||||
.insert(new_ident.clone(), symbol_and_region.clone());
|
||||
shadowable_idents.insert(new_ident, symbol_and_region);
|
||||
shadowable_idents.insert(new_ident.clone(), symbol_and_region.clone());
|
||||
scope.idents.insert(new_ident, symbol_and_region);
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
|
@ -299,131 +327,6 @@ fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region)
|
|||
Pattern::UnsupportedPattern(region)
|
||||
}
|
||||
|
||||
// CONSTRAIN
|
||||
|
||||
pub struct PatternState {
|
||||
pub headers: SendMap<Symbol, Located<Type>>,
|
||||
pub vars: Vec<Variable>,
|
||||
pub constraints: Vec<Constraint>,
|
||||
}
|
||||
|
||||
fn add_constraints<'a>(
|
||||
pattern: &'a ast::Pattern<'a>,
|
||||
scope: &'a Scope,
|
||||
region: Region,
|
||||
expected: PExpected<Type>,
|
||||
state: &'a mut PatternState,
|
||||
var_store: &VarStore,
|
||||
) {
|
||||
use crate::parse::ast::Pattern::*;
|
||||
|
||||
match pattern {
|
||||
Underscore | Malformed(_) | QualifiedIdentifier(_) => {
|
||||
// Neither the _ pattern nor malformed ones add any constraints.
|
||||
}
|
||||
Identifier(name) => {
|
||||
state.headers.insert(
|
||||
scope.symbol(name),
|
||||
Located {
|
||||
region,
|
||||
value: expected.get_type(),
|
||||
},
|
||||
);
|
||||
}
|
||||
IntLiteral(_) | NonBase10Literal { .. } => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Int,
|
||||
Type::int(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
FloatLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Float,
|
||||
Type::float(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
StrLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Str,
|
||||
Type::string(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
BlockStrLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Str,
|
||||
Type::string(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
||||
add_constraints(pattern, scope, region, expected, state, var_store)
|
||||
}
|
||||
|
||||
RecordDestructure(patterns) => {
|
||||
let ext_var = var_store.fresh();
|
||||
let ext_type = Type::Variable(ext_var);
|
||||
|
||||
let mut field_types: SendMap<Lowercase, Type> = SendMap::default();
|
||||
for loc_pattern in patterns {
|
||||
let pat_var = var_store.fresh();
|
||||
let pat_type = Type::Variable(pat_var);
|
||||
let expected = PExpected::NoExpectation(pat_type.clone());
|
||||
|
||||
match loc_pattern.value {
|
||||
Identifier(name) | RecordField(name, _) => {
|
||||
let symbol = scope.symbol(name);
|
||||
if !state.headers.contains_key(&symbol) {
|
||||
state
|
||||
.headers
|
||||
.insert(symbol, Located::at(region, pat_type.clone()));
|
||||
}
|
||||
field_types.insert(name.into(), pat_type.clone());
|
||||
}
|
||||
_ => panic!("invalid record pattern"),
|
||||
}
|
||||
|
||||
if let RecordField(_, guard) = loc_pattern.value {
|
||||
add_constraints(
|
||||
&guard.value,
|
||||
scope,
|
||||
guard.region,
|
||||
expected,
|
||||
state,
|
||||
var_store,
|
||||
);
|
||||
}
|
||||
|
||||
state.vars.push(pat_var);
|
||||
}
|
||||
|
||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
||||
let record_con =
|
||||
Constraint::Pattern(region, PatternCategory::Record, record_type, expected);
|
||||
|
||||
state.constraints.push(record_con);
|
||||
}
|
||||
|
||||
RecordField(_, _) => {
|
||||
// unreachable, this pattern is handled by already by RecordDestructure
|
||||
}
|
||||
|
||||
GlobalTag(_) | PrivateTag(_) | Apply(_, _) => {
|
||||
panic!("TODO add_constraints for {:?}", pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol, Region)>) {
|
||||
use crate::parse::ast::Pattern::*;
|
||||
|
||||
|
|
72
src/constrain/builtins.rs
Normal file
72
src/constrain/builtins.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use crate::region::Region;
|
||||
use crate::subs::Variable;
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::{self, *};
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::types::{self, Reason};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn int_literal(var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = number_literal_type("Int", "Integer");
|
||||
let reason = Reason::IntLiteral;
|
||||
|
||||
num_literal(var, typ, reason, expected, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_literal(var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = number_literal_type("Float", "FloatingPoint");
|
||||
let reason = Reason::FloatLiteral;
|
||||
|
||||
num_literal(var, typ, reason, expected, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num_literal(
|
||||
num_var: Variable,
|
||||
literal_type: Type,
|
||||
reason: Reason,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let num_type = Variable(num_var);
|
||||
let expected_literal = ForReason(reason, literal_type, region);
|
||||
|
||||
And(vec![
|
||||
Eq(num_type.clone(), expected_literal, region),
|
||||
Eq(num_type, expected, region),
|
||||
])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn number_literal_type(module_name: &str, type_name: &str) -> Type {
|
||||
builtin_type(
|
||||
types::MOD_NUM,
|
||||
types::TYPE_NUM,
|
||||
vec![builtin_type(module_name, type_name, Vec::new())],
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
|
||||
Type::Apply {
|
||||
module_name: module_name.into(),
|
||||
name: type_name.into(),
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn empty_list_type(var: Variable) -> Type {
|
||||
list_type(Type::Variable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn list_type(typ: Type) -> Type {
|
||||
builtin_type("List", "List", vec![typ])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn str_type() -> Type {
|
||||
builtin_type("Str", "Str", Vec::new())
|
||||
}
|
603
src/constrain/expr.rs
Normal file
603
src/constrain/expr.rs
Normal file
|
@ -0,0 +1,603 @@
|
|||
use crate::can::def::Def;
|
||||
use crate::can::expr::Expr::{self, *};
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, SendMap};
|
||||
use crate::constrain::builtins::{
|
||||
empty_list_type, float_literal, int_literal, list_type, str_type,
|
||||
};
|
||||
use crate::constrain::pattern::{constrain_pattern, PatternState};
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::Variable;
|
||||
use crate::types::AnnotationSource::*;
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::{self, *};
|
||||
use crate::types::PReason;
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::types::{LetConstraint, PExpected, Reason};
|
||||
|
||||
/// Whenever we encounter a user-defined type variable (a "rigid" var for short),
|
||||
/// 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 type Rigids = ImMap<Lowercase, Type>;
|
||||
|
||||
/// This is for constraining Defs
|
||||
#[derive(Default)]
|
||||
pub struct Info {
|
||||
pub vars: Vec<Variable>,
|
||||
pub constraints: Vec<Constraint>,
|
||||
pub def_types: SendMap<Symbol, Located<Type>>,
|
||||
}
|
||||
|
||||
impl Info {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Info {
|
||||
vars: Vec::with_capacity(capacity),
|
||||
constraints: Vec::with_capacity(capacity),
|
||||
def_types: SendMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
||||
Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars,
|
||||
def_types: SendMap::default(),
|
||||
defs_constraint: constraint,
|
||||
ret_constraint: Constraint::True,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn constrain_expr(
|
||||
rigids: &Rigids,
|
||||
region: Region,
|
||||
expr: &Expr,
|
||||
expected: Expected<Type>,
|
||||
) -> Constraint {
|
||||
match expr {
|
||||
Int(var, _) => int_literal(*var, expected, region),
|
||||
Float(var, _) => float_literal(*var, expected, region),
|
||||
EmptyRecord => constrain_empty_record(region, expected),
|
||||
Expr::Record(stored_var, fields) => {
|
||||
if fields.is_empty() {
|
||||
constrain_empty_record(region, expected)
|
||||
} else {
|
||||
let mut field_exprs = SendMap::default();
|
||||
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.
|
||||
let mut constraints = Vec::with_capacity(1 + fields.len());
|
||||
|
||||
for (label, (field_var, loc_field_expr)) in fields {
|
||||
let (field_type, field_con) =
|
||||
constrain_field(rigids, *field_var, loc_field_expr);
|
||||
|
||||
field_vars.push(*field_var);
|
||||
field_exprs.insert(label.clone(), loc_field_expr);
|
||||
field_types.insert(label.clone(), field_type);
|
||||
|
||||
constraints.push(field_con);
|
||||
}
|
||||
|
||||
let record_type = Type::Record(
|
||||
field_types,
|
||||
// TODO can we avoid doing Box::new on every single one of these?
|
||||
// For example, could we have a single lazy_static global Box they
|
||||
// could all share?
|
||||
Box::new(Type::EmptyRec),
|
||||
);
|
||||
let record_con = Eq(record_type, expected.clone(), region);
|
||||
constraints.push(record_con);
|
||||
|
||||
// variable to store in the AST
|
||||
let stored_con = Eq(Type::Variable(*stored_var), expected, region);
|
||||
|
||||
field_vars.push(*stored_var);
|
||||
constraints.push(stored_con);
|
||||
|
||||
exists(field_vars, And(constraints))
|
||||
}
|
||||
}
|
||||
Str(_) | BlockStr(_) => Eq(str_type(), expected, region),
|
||||
List(list_var, loc_elems) => {
|
||||
if loc_elems.is_empty() {
|
||||
Eq(empty_list_type(*list_var), expected, region)
|
||||
} else {
|
||||
let list_elem_type = Type::Variable(*list_var);
|
||||
let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2));
|
||||
|
||||
for (elem_var, loc_elem) in loc_elems {
|
||||
let elem_type = Variable(*elem_var);
|
||||
let elem_expected = NoExpectation(elem_type.clone());
|
||||
let list_elem_constraint = Eq(
|
||||
list_elem_type.clone(),
|
||||
ForReason(Reason::ElemInList, elem_type, region),
|
||||
region,
|
||||
);
|
||||
let constraint =
|
||||
constrain_expr(rigids, loc_elem.region, &loc_elem.value, elem_expected);
|
||||
|
||||
constraints.push(list_elem_constraint);
|
||||
constraints.push(constraint);
|
||||
}
|
||||
|
||||
constraints.push(Eq(list_type(list_elem_type), expected, region));
|
||||
|
||||
And(constraints)
|
||||
}
|
||||
}
|
||||
Call(boxed, loc_args, _application_style) => {
|
||||
let (fn_var, loc_fn, ret_var) = &**boxed;
|
||||
// The expression that evaluates to the function being called, e.g. `foo` in
|
||||
// (foo) bar baz
|
||||
let fn_type = Variable(*fn_var);
|
||||
let fn_region = loc_fn.region;
|
||||
let fn_expected = NoExpectation(fn_type.clone());
|
||||
// TODO look up the name and use NamedFnArg if possible.
|
||||
let fn_reason = Reason::AnonymousFnCall {
|
||||
arity: loc_args.len() as u8,
|
||||
};
|
||||
|
||||
let fn_con = constrain_expr(rigids, loc_fn.region, &loc_fn.value, fn_expected);
|
||||
|
||||
// The function's return type
|
||||
let ret_type = Variable(*ret_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);
|
||||
|
||||
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);
|
||||
// TODO look up the name and use NamedFnArg if possible.
|
||||
let reason = Reason::AnonymousFnArg {
|
||||
arg_index: index as u8,
|
||||
};
|
||||
let expected_arg = ForReason(reason, arg_type.clone(), region);
|
||||
let arg_con = constrain_expr(rigids, 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_type = ForReason(
|
||||
fn_reason,
|
||||
Function(arg_types, Box::new(ret_type.clone())),
|
||||
region,
|
||||
);
|
||||
|
||||
exists(
|
||||
vars,
|
||||
And(vec![
|
||||
fn_con,
|
||||
Eq(fn_type, expected_fn_type, fn_region),
|
||||
And(arg_cons),
|
||||
Eq(ret_type, expected, region),
|
||||
]),
|
||||
)
|
||||
}
|
||||
Var {
|
||||
symbol_for_lookup, ..
|
||||
} => Lookup(symbol_for_lookup.clone(), expected, region),
|
||||
Closure(_symbol, _recursive, args, boxed) => {
|
||||
let (loc_body_expr, ret_var) = &**boxed;
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
vars: Vec::with_capacity(args.len()),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
|
||||
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
|
||||
let ret_var = *ret_var;
|
||||
let ret_type = Type::Variable(ret_var);
|
||||
|
||||
vars.push(ret_var);
|
||||
|
||||
for (pattern_var, loc_pattern) in args {
|
||||
let pattern_type = Type::Variable(*pattern_var);
|
||||
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
|
||||
|
||||
pattern_types.push(pattern_type);
|
||||
|
||||
constrain_pattern(
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
pattern_expected,
|
||||
&mut state,
|
||||
);
|
||||
|
||||
vars.push(*pattern_var);
|
||||
}
|
||||
|
||||
let fn_typ = Type::Function(pattern_types, Box::new(ret_type.clone()));
|
||||
let body_type = NoExpectation(ret_type);
|
||||
let ret_constraint = constrain_expr(
|
||||
rigids,
|
||||
loc_body_expr.region,
|
||||
&loc_body_expr.value,
|
||||
body_type,
|
||||
);
|
||||
|
||||
let defs_constraint = And(state.constraints);
|
||||
|
||||
exists(
|
||||
vars,
|
||||
And(vec![
|
||||
Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: state.vars,
|
||||
def_types: state.headers,
|
||||
defs_constraint,
|
||||
ret_constraint,
|
||||
})),
|
||||
// "the closure's type is equal to expected type"
|
||||
Eq(fn_typ, expected, region),
|
||||
]),
|
||||
)
|
||||
}
|
||||
When {
|
||||
cond_var,
|
||||
expr_var,
|
||||
loc_cond,
|
||||
branches,
|
||||
} => {
|
||||
// Infer the condition expression's type.
|
||||
let cond_var = *cond_var;
|
||||
let cond_type = Variable(cond_var);
|
||||
let expr_con = constrain_expr(
|
||||
rigids,
|
||||
region,
|
||||
&loc_cond.value,
|
||||
NoExpectation(cond_type.clone()),
|
||||
);
|
||||
|
||||
let mut constraints = Vec::with_capacity(branches.len() + 1);
|
||||
|
||||
match expected {
|
||||
FromAnnotation(name, arity, _, typ) => {
|
||||
for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() {
|
||||
let branch_con = constrain_when_branch(
|
||||
rigids,
|
||||
region,
|
||||
loc_pattern,
|
||||
loc_expr,
|
||||
PExpected::ForReason(
|
||||
PReason::WhenMatch { index },
|
||||
cond_type.clone(),
|
||||
region,
|
||||
),
|
||||
FromAnnotation(
|
||||
name.clone(),
|
||||
arity,
|
||||
TypedWhenBranch(index),
|
||||
typ.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO investigate: why doesn't this use expr_var?
|
||||
// Shouldn't it?
|
||||
constraints.push(exists(
|
||||
vec![cond_var],
|
||||
// Each branch's pattern must have the same type
|
||||
// as the condition expression did.
|
||||
And(vec![expr_con.clone(), branch_con]),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let branch_type = Variable(*expr_var);
|
||||
let mut branch_cons = Vec::with_capacity(branches.len());
|
||||
|
||||
for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() {
|
||||
let branch_con = constrain_when_branch(
|
||||
rigids,
|
||||
region,
|
||||
loc_pattern,
|
||||
loc_expr,
|
||||
PExpected::ForReason(
|
||||
PReason::WhenMatch { index },
|
||||
cond_type.clone(),
|
||||
region,
|
||||
),
|
||||
ForReason(Reason::WhenBranch { index }, branch_type.clone(), region),
|
||||
);
|
||||
|
||||
branch_cons.push(branch_con);
|
||||
}
|
||||
|
||||
constraints.push(exists(
|
||||
vec![cond_var],
|
||||
And(vec![
|
||||
// Record the original conditional expression's constraint.
|
||||
expr_con,
|
||||
// Each branch's pattern must have the same type
|
||||
// as the condition expression did.
|
||||
And(branch_cons),
|
||||
// The return type of each branch must equal
|
||||
// the return type of the entire when-expression.
|
||||
Eq(branch_type, expected, region),
|
||||
]),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check for exhaustiveness. If this `case` is non-exaustive, then:
|
||||
//
|
||||
// 1. Record a Problem.
|
||||
// 2. Add an extra _ branch at the end which throws a runtime error.
|
||||
|
||||
And(constraints)
|
||||
}
|
||||
Access {
|
||||
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();
|
||||
|
||||
rec_field_types.insert(field.clone(), field_type.clone());
|
||||
|
||||
let record_type = Type::Record(rec_field_types, Box::new(ext_type));
|
||||
let record_expected = Expected::NoExpectation(record_type);
|
||||
|
||||
let constraint =
|
||||
constrain_expr(&ImMap::default(), region, &loc_expr.value, record_expected);
|
||||
|
||||
exists(
|
||||
vec![field_var, ext_var],
|
||||
And(vec![constraint, Eq(field_type, expected, region)]),
|
||||
)
|
||||
}
|
||||
Accessor {
|
||||
field,
|
||||
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();
|
||||
field_types.insert(field.clone(), field_type.clone());
|
||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
||||
|
||||
exists(
|
||||
vec![field_var, ext_var],
|
||||
Eq(
|
||||
Type::Function(vec![record_type], Box::new(field_type)),
|
||||
expected,
|
||||
region,
|
||||
),
|
||||
)
|
||||
}
|
||||
Defs(defs, loc_ret) => constrain_defs_with_return(
|
||||
rigids,
|
||||
defs,
|
||||
expected,
|
||||
Info::with_capacity(defs.len()),
|
||||
Info::with_capacity(defs.len()),
|
||||
loc_ret,
|
||||
),
|
||||
Tag(_, _) => {
|
||||
panic!("TODO constrain Tag");
|
||||
}
|
||||
RuntimeError(_) => True,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn constrain_when_branch<'a>(
|
||||
rigids: &Rigids,
|
||||
region: Region,
|
||||
loc_pattern: &Located<Pattern>,
|
||||
loc_expr: &Located<Expr>,
|
||||
pattern_expected: PExpected<Type>,
|
||||
expr_expected: Expected<Type>,
|
||||
) -> Constraint {
|
||||
let ret_constraint = constrain_expr(rigids, region, &loc_expr.value, expr_expected);
|
||||
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
constrain_pattern(
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
pattern_expected,
|
||||
&mut state,
|
||||
);
|
||||
|
||||
Constraint::Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: state.vars,
|
||||
def_types: state.headers,
|
||||
defs_constraint: Constraint::And(state.constraints),
|
||||
ret_constraint,
|
||||
}))
|
||||
}
|
||||
|
||||
fn constrain_field(
|
||||
rigids: &Rigids,
|
||||
field_var: Variable,
|
||||
loc_expr: &Located<Expr>,
|
||||
) -> (Type, Constraint) {
|
||||
let field_type = Variable(field_var);
|
||||
let field_expected = NoExpectation(field_type.clone());
|
||||
let constraint = constrain_expr(rigids, loc_expr.region, &loc_expr.value, field_expected);
|
||||
|
||||
(field_type, constraint)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn constrain_empty_record(region: Region, expected: Expected<Type>) -> Constraint {
|
||||
Eq(EmptyRec, expected, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn constrain_defs(
|
||||
rigids: &Rigids,
|
||||
found_rigids: &mut SendMap<Variable, Lowercase>,
|
||||
defs: &[Def],
|
||||
flex_info: &mut Info,
|
||||
) {
|
||||
for def in defs {
|
||||
constrain_def(rigids, found_rigids, def, flex_info)
|
||||
}
|
||||
}
|
||||
|
||||
fn constrain_def_pattern(loc_pattern: &Located<Pattern>, expr_type: Type) -> PatternState {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
let pattern_expected = PExpected::NoExpectation(expr_type);
|
||||
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
constrain_pattern(
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
pattern_expected,
|
||||
&mut state,
|
||||
);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn constrain_def(
|
||||
rigids: &Rigids,
|
||||
found_rigids: &mut SendMap<Variable, Lowercase>,
|
||||
def: &Def,
|
||||
flex_info: &mut Info,
|
||||
) {
|
||||
use crate::types::AnnotationSource;
|
||||
|
||||
let expr_var = def.body_var;
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
|
||||
flex_info.vars.push(expr_var);
|
||||
|
||||
let pattern_state = constrain_def_pattern(&def.loc_pattern, expr_type.clone());
|
||||
|
||||
for (k, v) in &pattern_state.headers {
|
||||
flex_info.def_types.insert(k.clone(), v.clone());
|
||||
}
|
||||
|
||||
let ret_constraint = match &def.annotation {
|
||||
Some((annotation, seen_rigids)) => {
|
||||
let mut ftv: Rigids = rigids.clone();
|
||||
|
||||
for (var, name) in seen_rigids {
|
||||
// if the rigid is known already, nothing needs to happen
|
||||
// otherwise register it.
|
||||
if !rigids.contains_key(name) {
|
||||
// possible use this rigid in nested def's
|
||||
ftv.insert(name.clone(), Type::Variable(*var));
|
||||
|
||||
// mark this variable as a rigid
|
||||
found_rigids.insert(*var, name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let annotation_expected = FromAnnotation(
|
||||
def.loc_pattern.clone(),
|
||||
annotation.arity(),
|
||||
AnnotationSource::TypedBody,
|
||||
annotation.clone(),
|
||||
);
|
||||
|
||||
// ensure expected type unifies with annotated type
|
||||
flex_info.constraints.push(Eq(
|
||||
expr_type,
|
||||
annotation_expected.clone(),
|
||||
def.loc_expr.region,
|
||||
));
|
||||
|
||||
constrain_expr(
|
||||
&ftv,
|
||||
def.loc_expr.region,
|
||||
&def.loc_expr.value,
|
||||
annotation_expected,
|
||||
)
|
||||
}
|
||||
None => constrain_expr(
|
||||
rigids,
|
||||
def.loc_expr.region,
|
||||
&def.loc_expr.value,
|
||||
NoExpectation(expr_type),
|
||||
),
|
||||
};
|
||||
|
||||
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: pattern_state.vars,
|
||||
def_types: pattern_state.headers,
|
||||
defs_constraint: And(pattern_state.constraints),
|
||||
ret_constraint,
|
||||
})));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn constrain_defs_with_return<'a>(
|
||||
rigids: &Rigids,
|
||||
defs: &[Def],
|
||||
expected: Expected<Type>,
|
||||
mut flex_info: Info,
|
||||
rigid_info: Info,
|
||||
loc_ret: &'a Located<Expr>,
|
||||
) -> Constraint {
|
||||
let mut found_rigids = SendMap::default();
|
||||
|
||||
constrain_defs(rigids, &mut found_rigids, defs, &mut flex_info);
|
||||
|
||||
// The def as a whole is a tail call iff its return expression is a tail call.
|
||||
// Use its output as a starting point because its tail_call already has the right answer!
|
||||
let ret_con = constrain_expr(rigids, loc_ret.region, &loc_ret.value, expected);
|
||||
|
||||
// Rigid constraint for the def expr as a whole.
|
||||
// This is a "LetRec" constraint; it supports recursion.
|
||||
// (The only advantage of "Let" over "LetRec" is if you want to
|
||||
// shadow things, and Roc disallows shadowing anyway.)
|
||||
Let(Box::new(LetConstraint {
|
||||
rigid_vars: rigid_info.vars,
|
||||
flex_vars: Vec::new(),
|
||||
def_types: rigid_info.def_types,
|
||||
defs_constraint: True,
|
||||
ret_constraint: Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: flex_info.vars,
|
||||
def_types: flex_info.def_types.clone(),
|
||||
defs_constraint: Let(Box::new(LetConstraint {
|
||||
flex_vars: Vec::new(),
|
||||
rigid_vars: Vec::new(),
|
||||
def_types: flex_info.def_types,
|
||||
defs_constraint: True,
|
||||
ret_constraint: And(flex_info.constraints),
|
||||
})),
|
||||
ret_constraint: And(vec![And(rigid_info.constraints), ret_con]),
|
||||
})),
|
||||
}))
|
||||
}
|
|
@ -1,85 +1,4 @@
|
|||
use crate::collections::SendMap;
|
||||
use crate::region::Region;
|
||||
use crate::subs::{VarStore, Variable};
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::{self, *};
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::types::{self, LetConstraint, Reason};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
||||
Constraint::Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars,
|
||||
def_types: SendMap::default(),
|
||||
defs_constraint: constraint,
|
||||
ret_constraint: Constraint::True,
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn int_literal(var_store: &VarStore, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = number_literal_type("Int", "Integer");
|
||||
let reason = Reason::IntLiteral;
|
||||
|
||||
num_literal(var_store, typ, reason, expected, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_literal(var_store: &VarStore, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = number_literal_type("Float", "FloatingPoint");
|
||||
let reason = Reason::FloatLiteral;
|
||||
|
||||
num_literal(var_store, typ, reason, expected, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num_literal(
|
||||
var_store: &VarStore,
|
||||
literal_type: Type,
|
||||
reason: Reason,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let num_var = var_store.fresh();
|
||||
let num_type = Variable(num_var);
|
||||
let expected_literal = ForReason(reason, literal_type, region);
|
||||
|
||||
And(vec![
|
||||
Eq(num_type.clone(), expected_literal, region),
|
||||
Eq(num_type, expected, region),
|
||||
])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn number_literal_type(module_name: &str, type_name: &str) -> Type {
|
||||
builtin_type(
|
||||
types::MOD_NUM,
|
||||
types::TYPE_NUM,
|
||||
vec![builtin_type(module_name, type_name, Vec::new())],
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
|
||||
Type::Apply {
|
||||
module_name: module_name.into(),
|
||||
name: type_name.into(),
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn empty_list_type(var: Variable) -> Type {
|
||||
list_type(Type::Variable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn list_type(typ: Type) -> Type {
|
||||
builtin_type("List", "List", vec![typ])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn str_type() -> Type {
|
||||
builtin_type("Str", "Str", Vec::new())
|
||||
}
|
||||
pub mod builtins;
|
||||
pub mod expr;
|
||||
pub mod module;
|
||||
pub mod pattern;
|
||||
|
|
31
src/constrain/module.rs
Normal file
31
src/constrain/module.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use crate::can::def::Def;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, SendMap};
|
||||
use crate::constrain::expr::{constrain_defs, Info};
|
||||
use crate::region::Region;
|
||||
use crate::subs::Variable;
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::*;
|
||||
use crate::types::Type;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn constrain_module(defs: &[Def], lookups: Vec<(Symbol, Variable, Region)>) -> Constraint {
|
||||
let mut flex_info = Info::default();
|
||||
|
||||
for (symbol, expr_var, region) in lookups {
|
||||
// Add the usual Lookup constraint as if this were a normal def.
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
let expected = NoExpectation(expr_type.clone());
|
||||
|
||||
flex_info.constraints.push(Lookup(symbol, expected, region));
|
||||
}
|
||||
|
||||
constrain_defs(
|
||||
&ImMap::default(),
|
||||
&mut SendMap::default(),
|
||||
&defs,
|
||||
&mut flex_info,
|
||||
);
|
||||
|
||||
Constraint::And(flex_info.constraints)
|
||||
}
|
106
src/constrain/pattern.rs
Normal file
106
src/constrain/pattern.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use crate::can::ident::Lowercase;
|
||||
use crate::can::pattern::Pattern::{self, *};
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::SendMap;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::Variable;
|
||||
use crate::types::{Constraint, PExpected, PatternCategory, Type};
|
||||
|
||||
pub struct PatternState {
|
||||
pub headers: SendMap<Symbol, Located<Type>>,
|
||||
pub vars: Vec<Variable>,
|
||||
pub constraints: Vec<Constraint>,
|
||||
}
|
||||
|
||||
/// This accepts PatternState (rather than returning it) so that the caller can
|
||||
/// intiialize the Vecs in PatternState using with_capacity
|
||||
/// based on its knowledge of their lengths.
|
||||
pub fn constrain_pattern(
|
||||
pattern: &Pattern,
|
||||
region: Region,
|
||||
expected: PExpected<Type>,
|
||||
state: &mut PatternState,
|
||||
) {
|
||||
match pattern {
|
||||
Underscore | UnsupportedPattern(_) => {
|
||||
// Neither the _ pattern nor erroneous ones add any constraints.
|
||||
}
|
||||
Identifier(symbol) => {
|
||||
state.headers.insert(
|
||||
symbol.clone(),
|
||||
Located {
|
||||
region,
|
||||
value: expected.get_type(),
|
||||
},
|
||||
);
|
||||
}
|
||||
IntLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Int,
|
||||
Type::int(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
FloatLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Float,
|
||||
Type::float(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
StrLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Str,
|
||||
Type::string(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
RecordDestructure(ext_var, patterns) => {
|
||||
let ext_type = Type::Variable(*ext_var);
|
||||
|
||||
let mut field_types: SendMap<Lowercase, Type> = SendMap::default();
|
||||
|
||||
for (pat_var, label, symbol, opt_guard) in patterns {
|
||||
let pat_type = Type::Variable(*pat_var);
|
||||
let expected = PExpected::NoExpectation(pat_type.clone());
|
||||
|
||||
if !state.headers.contains_key(&symbol) {
|
||||
state
|
||||
.headers
|
||||
.insert(symbol.clone(), Located::at(region, pat_type.clone()));
|
||||
}
|
||||
|
||||
field_types.insert(label.clone(), pat_type.clone());
|
||||
|
||||
// TODO investigate: shouldn't guard_var be constrained somewhere?
|
||||
if let Some((_guard_var, loc_guard)) = opt_guard {
|
||||
constrain_pattern(&loc_guard.value, loc_guard.region, expected, state);
|
||||
}
|
||||
|
||||
state.vars.push(*pat_var);
|
||||
}
|
||||
|
||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
||||
let record_con =
|
||||
Constraint::Pattern(region, PatternCategory::Record, record_type, expected);
|
||||
|
||||
state.constraints.push(record_con);
|
||||
}
|
||||
|
||||
Tag(_) => {
|
||||
panic!("TODO constrain Tag pattern");
|
||||
}
|
||||
AppliedTag(_, _) => {
|
||||
panic!("TODO constrain AppliedTag pattern");
|
||||
}
|
||||
Shadowed(_) => {
|
||||
panic!("TODO constrain Shadowed pattern");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -118,20 +118,24 @@ fn compile_expr<'ctx, 'env>(
|
|||
match *expr {
|
||||
Int(_, num) => IntConst(env.context.i64_type().const_int(num as u64, false)),
|
||||
Float(_, num) => FloatConst(env.context.f64_type().const_float(num)),
|
||||
When(_, ref loc_cond_expr, ref branches) => {
|
||||
When {
|
||||
ref loc_cond,
|
||||
ref branches,
|
||||
..
|
||||
} => {
|
||||
if branches.len() < 2 {
|
||||
panic!("TODO support when-expressions of fewer than 2 branches.");
|
||||
}
|
||||
if branches.len() == 2 {
|
||||
let mut iter = branches.iter();
|
||||
|
||||
let ((_pattern_var, pattern), (_expr_var, branch_expr)) = iter.next().unwrap();
|
||||
let (_, (_, else_expr)) = iter.next().unwrap();
|
||||
let (pattern, branch_expr) = iter.next().unwrap();
|
||||
let (_, else_expr) = iter.next().unwrap();
|
||||
|
||||
compile_when_branch(
|
||||
env,
|
||||
parent,
|
||||
&loc_cond_expr.value,
|
||||
&loc_cond.value,
|
||||
pattern.value.clone(),
|
||||
&branch_expr.value,
|
||||
&else_expr.value,
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::can::module::{canonicalize_module_defs, Module};
|
|||
use crate::can::scope::Scope;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, SendMap, SendSet};
|
||||
use crate::constrain::module::constrain_module;
|
||||
use crate::ident::Ident;
|
||||
use crate::module::ModuleName;
|
||||
use crate::parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
|
||||
|
@ -12,6 +13,7 @@ use crate::region::{Located, Region};
|
|||
use crate::solve;
|
||||
use crate::subs::VarStore;
|
||||
use crate::subs::{Subs, Variable};
|
||||
use crate::types::Constraint;
|
||||
use crate::unify::Problems;
|
||||
use bumpalo::Bump;
|
||||
use futures::future::join_all;
|
||||
|
@ -217,8 +219,7 @@ fn load_filename(
|
|||
|
||||
let mut scope =
|
||||
Scope::new(format!("{}.", declared_name).into(), scope_from_imports);
|
||||
|
||||
let (defs, exposed_imports) = parse_and_canonicalize_defs(
|
||||
let (defs, exposed_imports, constraint) = process_defs(
|
||||
&arena,
|
||||
state,
|
||||
declared_name.clone(),
|
||||
|
@ -230,6 +231,7 @@ fn load_filename(
|
|||
name: Some(declared_name),
|
||||
defs,
|
||||
exposed_imports,
|
||||
constraint,
|
||||
};
|
||||
|
||||
LoadedModule::Valid(module)
|
||||
|
@ -258,7 +260,7 @@ fn load_filename(
|
|||
let mut scope = Scope::new(".".into(), scope_from_imports);
|
||||
|
||||
// The app module has no declared name. Pass it as "".
|
||||
let (defs, exposed_imports) = parse_and_canonicalize_defs(
|
||||
let (defs, exposed_imports, constraint) = process_defs(
|
||||
&arena,
|
||||
state,
|
||||
"".into(),
|
||||
|
@ -270,6 +272,7 @@ fn load_filename(
|
|||
name: None,
|
||||
defs,
|
||||
exposed_imports,
|
||||
constraint,
|
||||
};
|
||||
|
||||
LoadedModule::Valid(module)
|
||||
|
@ -286,14 +289,14 @@ fn load_filename(
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_and_canonicalize_defs<'a, I>(
|
||||
fn process_defs<'a, I>(
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
home: Box<str>,
|
||||
exposes: I,
|
||||
scope: &mut Scope,
|
||||
var_store: &VarStore,
|
||||
) -> (Vec<Def>, SendMap<Symbol, Variable>)
|
||||
) -> (Vec<Def>, SendMap<Symbol, Variable>, Constraint)
|
||||
where
|
||||
I: Iterator<Item = Located<ExposesEntry<'a>>>,
|
||||
{
|
||||
|
@ -301,7 +304,12 @@ where
|
|||
.parse(arena, state)
|
||||
.expect("TODO gracefully handle parse error on module defs");
|
||||
|
||||
canonicalize_module_defs(arena, parsed_defs, home, exposes, scope, var_store)
|
||||
let (defs, exposed_imports, lookups) =
|
||||
canonicalize_module_defs(arena, parsed_defs, home, exposes, scope, var_store);
|
||||
|
||||
let constraint = constrain_module(&defs, lookups);
|
||||
|
||||
(defs, exposed_imports, constraint)
|
||||
}
|
||||
|
||||
fn load_import(
|
||||
|
@ -360,21 +368,16 @@ pub fn solve_loaded(
|
|||
use LoadedModule::*;
|
||||
|
||||
let mut vars_by_symbol: ImMap<Symbol, Variable> = ImMap::default();
|
||||
let mut constraints = Vec::with_capacity(loaded_deps.len() + 1);
|
||||
let module_constraint = if true {
|
||||
panic!("TODO populate constraints for each module");
|
||||
} else {
|
||||
crate::types::Constraint::True
|
||||
};
|
||||
let mut dep_constraints = Vec::with_capacity(loaded_deps.len());
|
||||
|
||||
// All the exposed imports should be available in the solver's vars_by_symbol
|
||||
for (symbol, var) in module.exposed_imports.iter() {
|
||||
vars_by_symbol.insert(symbol.clone(), var.clone());
|
||||
for (symbol, expr_var) in module.exposed_imports.iter() {
|
||||
vars_by_symbol.insert(symbol.clone(), expr_var.clone());
|
||||
}
|
||||
|
||||
// All the top-level defs should also be available in vars_by_symbol
|
||||
for def in module.defs.iter() {
|
||||
for (symbol, var) in def.vars_by_symbol.iter() {
|
||||
for (symbol, var) in def.pattern_vars.iter() {
|
||||
vars_by_symbol.insert(symbol.clone(), var.clone());
|
||||
}
|
||||
}
|
||||
|
@ -391,16 +394,18 @@ pub fn solve_loaded(
|
|||
// in the solver's vars_by_symbol. (The map's keys are
|
||||
// fully qualified, so there won't be any collisions
|
||||
// with the primary module's exposed imports!)
|
||||
for (symbol, var) in valid_dep.exposed_imports {
|
||||
vars_by_symbol.insert(symbol, var);
|
||||
for (symbol, expr_var) in valid_dep.exposed_imports {
|
||||
vars_by_symbol.insert(symbol, expr_var);
|
||||
}
|
||||
|
||||
// All its top-level defs should also be available in vars_by_symbol
|
||||
for def in valid_dep.defs {
|
||||
for (symbol, var) in def.vars_by_symbol {
|
||||
for (symbol, var) in def.pattern_vars {
|
||||
vars_by_symbol.insert(symbol, var);
|
||||
}
|
||||
}
|
||||
|
||||
dep_constraints.push(valid_dep.constraint);
|
||||
}
|
||||
|
||||
broken @ FileProblem { .. } => {
|
||||
|
@ -413,9 +418,9 @@ pub fn solve_loaded(
|
|||
}
|
||||
}
|
||||
|
||||
for constraint in constraints {
|
||||
solve::run(&vars_by_symbol, problems, subs, &constraint);
|
||||
for dep_constraint in dep_constraints {
|
||||
solve::run(&vars_by_symbol, problems, subs, &dep_constraint);
|
||||
}
|
||||
|
||||
solve::run(&vars_by_symbol, problems, subs, &module_constraint);
|
||||
solve::run(&vars_by_symbol, problems, subs, &module.constraint);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ pub struct Env {
|
|||
pub procedures: ImMap<Symbol, Procedure>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn canonicalize_declaration(
|
||||
var_store: &VarStore,
|
||||
region: Region,
|
||||
|
@ -97,7 +96,7 @@ fn canonicalize_pattern(
|
|||
));
|
||||
}
|
||||
|
||||
ExactString(_) => {
|
||||
StrLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
pattern.region,
|
||||
PatternCategory::Str,
|
||||
|
@ -106,33 +105,33 @@ fn canonicalize_pattern(
|
|||
));
|
||||
}
|
||||
|
||||
RecordDestructure(patterns) => {
|
||||
let ext_var = var_store.fresh();
|
||||
let ext_type = Type::Variable(ext_var);
|
||||
RecordDestructure(ext_var, patterns) => {
|
||||
let ext_type = Type::Variable(*ext_var);
|
||||
|
||||
let mut field_types: SendMap<Lowercase, Type> = SendMap::default();
|
||||
for (pattern, maybe_guard) in patterns {
|
||||
let pat_var = var_store.fresh();
|
||||
let pat_type = Type::Variable(pat_var);
|
||||
for (pat_var, label, symbol, maybe_guard) in patterns {
|
||||
let pat_type = Type::Variable(*pat_var);
|
||||
let pattern_expected = PExpected::NoExpectation(pat_type.clone());
|
||||
|
||||
if let Some(loc_guard) = maybe_guard {
|
||||
canonicalize_pattern(var_store, state, pattern, pattern_expected.clone());
|
||||
canonicalize_pattern(var_store, state, loc_guard, pattern_expected);
|
||||
} else {
|
||||
canonicalize_pattern(var_store, state, pattern, pattern_expected);
|
||||
match maybe_guard {
|
||||
Some((_guard_var, loc_guard)) => {
|
||||
state.headers.insert(
|
||||
symbol.clone(),
|
||||
Located {
|
||||
region: pattern.region,
|
||||
value: pat_type.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
canonicalize_pattern(var_store, state, loc_guard, pattern_expected);
|
||||
}
|
||||
None => {
|
||||
canonicalize_pattern(var_store, state, pattern, pattern_expected);
|
||||
}
|
||||
}
|
||||
|
||||
let name = if let Identifier(n) = &pattern.value {
|
||||
let a: Box<str> = n.clone().into();
|
||||
let b: Lowercase = a.into();
|
||||
b
|
||||
} else {
|
||||
unreachable!("the lhs must be an identifier at this point");
|
||||
};
|
||||
|
||||
state.vars.push(pat_var);
|
||||
field_types.insert(name, pat_type);
|
||||
state.vars.push(*pat_var);
|
||||
field_types.insert(label.clone(), pat_type);
|
||||
}
|
||||
|
||||
let record_type =
|
||||
|
@ -292,22 +291,25 @@ pub fn canonicalize_expr(
|
|||
(output, And(constraints))
|
||||
}
|
||||
}
|
||||
Var(variable, symbol) => {
|
||||
var_usage.register(symbol);
|
||||
match var_usage.get_usage(symbol) {
|
||||
Var {
|
||||
symbol_for_lookup, ..
|
||||
} => {
|
||||
var_usage.register(symbol_for_lookup);
|
||||
match var_usage.get_usage(symbol_for_lookup) {
|
||||
Some(sharing::ReferenceCount::Shared) => {
|
||||
// the variable is used/consumed more than once, so it must be Shared
|
||||
let val_var = *variable;
|
||||
let val_var = var_store.fresh();
|
||||
let uniq_var = var_store.fresh();
|
||||
|
||||
let val_type = Variable(val_var);
|
||||
let uniq_type = Variable(uniq_var);
|
||||
|
||||
let attr_type = constrain::attr_type(uniq_type.clone(), val_type);
|
||||
|
||||
(
|
||||
Output::default(),
|
||||
And(vec![
|
||||
Lookup(symbol.clone(), expected.clone(), region),
|
||||
Lookup(symbol_for_lookup.clone(), expected.clone(), region),
|
||||
Eq(attr_type, expected, region),
|
||||
Eq(
|
||||
uniq_type,
|
||||
|
@ -321,24 +323,14 @@ pub fn canonicalize_expr(
|
|||
// no additional constraints, keep uniqueness unbound
|
||||
(
|
||||
Output::default(),
|
||||
Lookup(symbol.clone(), expected.clone(), region),
|
||||
Lookup(symbol_for_lookup.clone(), expected.clone(), region),
|
||||
)
|
||||
}
|
||||
None => panic!("symbol not analyzed"),
|
||||
}
|
||||
}
|
||||
/*
|
||||
FunctionPointer(_variable, symbol) => match env.bound_names.get(symbol) {
|
||||
// constraint expected ~ the type of this symbol in the environment
|
||||
None => panic!("FunctionPointer: no variable for {:?}", symbol),
|
||||
Some(var) => Output::new(Eq(Variable(*var), expected, Region::zero())),
|
||||
},
|
||||
*/
|
||||
FunctionPointer(_, _) => {
|
||||
panic!("TODO implement function pointer?");
|
||||
}
|
||||
Closure(_symbol, _recursion, args, boxed_body) => {
|
||||
let (ret_var, body) = &**boxed_body;
|
||||
let (body, ret_var) = &**boxed_body;
|
||||
|
||||
// first, generate constraints for the arguments
|
||||
let mut arg_types = Vec::new();
|
||||
|
@ -409,13 +401,12 @@ pub fn canonicalize_expr(
|
|||
(output, constraint)
|
||||
}
|
||||
|
||||
Call(fn_expr, loc_args, _) => {
|
||||
let fn_var = var_store.fresh();
|
||||
let fn_type = Variable(fn_var);
|
||||
let ret_var = var_store.fresh();
|
||||
let ret_type = Variable(ret_var);
|
||||
Call(boxed, loc_args, _) => {
|
||||
let (fn_var, fn_expr, ret_var) = &**boxed;
|
||||
let fn_type = Variable(*fn_var);
|
||||
let ret_type = Variable(*ret_var);
|
||||
let fn_expected = Expected::NoExpectation(fn_type.clone());
|
||||
let fn_region = Region::zero();
|
||||
let fn_region = fn_expr.region;
|
||||
|
||||
let mut vars = Vec::with_capacity(2 + loc_args.len());
|
||||
|
||||
|
@ -425,7 +416,7 @@ pub fn canonicalize_expr(
|
|||
var_store,
|
||||
var_usage,
|
||||
fn_region,
|
||||
&fn_expr,
|
||||
&fn_expr.value,
|
||||
fn_expected,
|
||||
);
|
||||
|
||||
|
@ -484,8 +475,13 @@ pub fn canonicalize_expr(
|
|||
Output::default(),
|
||||
can_defs(rigids, var_store, var_usage, defs, expected, loc_ret),
|
||||
),
|
||||
When(variable, loc_cond, branches) => {
|
||||
let cond_var = *variable;
|
||||
When {
|
||||
cond_var,
|
||||
loc_cond,
|
||||
branches,
|
||||
..
|
||||
} => {
|
||||
let cond_var = *cond_var;
|
||||
let cond_type = Variable(cond_var);
|
||||
let (mut output, expr_con) = canonicalize_expr(
|
||||
rigids,
|
||||
|
@ -502,9 +498,7 @@ pub fn canonicalize_expr(
|
|||
|
||||
match expected {
|
||||
Expected::FromAnnotation(name, arity, _, typ) => {
|
||||
for (index, ((_patter_var, loc_pattern), (_expr_var, loc_expr))) in
|
||||
branches.iter().enumerate()
|
||||
{
|
||||
for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() {
|
||||
let mut branch_var_usage = old_var_usage.clone();
|
||||
let branch_con = canonicalize_when_branch(
|
||||
var_store,
|
||||
|
@ -554,9 +548,7 @@ pub fn canonicalize_expr(
|
|||
let branch_type = Variable(branch_var);
|
||||
let mut branch_cons = Vec::with_capacity(branches.len());
|
||||
|
||||
for (index, ((_pattern_var, loc_pattern), (_expr_var, loc_expr))) in
|
||||
branches.iter().enumerate()
|
||||
{
|
||||
for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() {
|
||||
let mut branch_var_usage = old_var_usage.clone();
|
||||
let branch_con = canonicalize_when_branch(
|
||||
var_store,
|
||||
|
@ -778,7 +770,7 @@ fn can_defs(
|
|||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
canonicalize_pattern(var_store, &mut state, &def.pattern, pattern_expected);
|
||||
canonicalize_pattern(var_store, &mut state, &def.loc_pattern, pattern_expected);
|
||||
|
||||
flex_info.vars.push(pattern_var);
|
||||
|
||||
|
@ -788,19 +780,19 @@ fn can_defs(
|
|||
rigids,
|
||||
var_store,
|
||||
var_usage,
|
||||
def.expr.region,
|
||||
&def.expr.value,
|
||||
def.loc_expr.region,
|
||||
&def.loc_expr.value,
|
||||
Expected::NoExpectation(expr_type.clone()),
|
||||
);
|
||||
|
||||
add_pattern_to_lookup_types(
|
||||
// TODO can we we avoid this clone?
|
||||
def.pattern.clone(),
|
||||
def.loc_pattern.clone(),
|
||||
&mut flex_info.def_types,
|
||||
expr_type.clone(),
|
||||
);
|
||||
|
||||
bound_symbols.extend(pattern::symbols_from_pattern(&def.pattern.value));
|
||||
bound_symbols.extend(pattern::symbols_from_pattern(&def.loc_pattern.value));
|
||||
|
||||
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
|
|
|
@ -9,6 +9,7 @@ use roc::can::problem::Problem;
|
|||
use roc::can::scope::Scope;
|
||||
use roc::can::symbol::Symbol;
|
||||
use roc::collections::{ImMap, MutMap, SendSet};
|
||||
use roc::constrain::expr::constrain_expr;
|
||||
use roc::ident::Ident;
|
||||
use roc::parse;
|
||||
use roc::parse::ast::{self, Attempting};
|
||||
|
@ -191,13 +192,18 @@ pub fn can_expr_with(
|
|||
let scope_prefix = format!("{}.{}$", home, name).into();
|
||||
let mut scope = Scope::new(scope_prefix, declared_idents.clone());
|
||||
let mut env = Env::new(home.into());
|
||||
let (loc_expr, output, constraint) = canonicalize_expr(
|
||||
&ImMap::default(),
|
||||
let (loc_expr, output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&var_store,
|
||||
&mut scope,
|
||||
Region::zero(),
|
||||
&loc_expr.value,
|
||||
);
|
||||
|
||||
let constraint = constrain_expr(
|
||||
&ImMap::default(),
|
||||
loc_expr.region,
|
||||
&loc_expr.value,
|
||||
expected,
|
||||
);
|
||||
|
||||
|
|
|
@ -62,6 +62,33 @@ mod test_canonicalize {
|
|||
assert_eq!(actual.value, expected);
|
||||
}
|
||||
|
||||
fn assert_can_float(input: &str, expected: f64) {
|
||||
let arena = Bump::new();
|
||||
let (loc_actual, _, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default());
|
||||
|
||||
match loc_actual.value {
|
||||
Expr::Float(_, actual) => {
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
actual => {
|
||||
panic!("Expected a Float, but got: {:?}", actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn assert_can_int(input: &str, expected: i64) {
|
||||
let arena = Bump::new();
|
||||
let (loc_actual, _, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default());
|
||||
|
||||
match loc_actual.value {
|
||||
Expr::Int(_, actual) => {
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
actual => {
|
||||
panic!("Expected an Int, but got: {:?}", actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NUMBER LITERALS
|
||||
|
||||
#[test]
|
||||
|
@ -106,67 +133,67 @@ mod test_canonicalize {
|
|||
|
||||
#[test]
|
||||
fn zero() {
|
||||
assert_can("0", Int(0));
|
||||
assert_can_int("0", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_zero() {
|
||||
assert_can("-0", Int(0));
|
||||
assert_can_int("-0", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_point_zero() {
|
||||
assert_can("0.0", Float(0.0));
|
||||
assert_can_float("0.0", 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_zero_point_zero() {
|
||||
assert_can("-0.0", Float(-0.0));
|
||||
assert_can_float("-0.0", -0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_zero() {
|
||||
assert_can("0x0", Int(0x0));
|
||||
assert_can_int("0x0", 0x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_one_b() {
|
||||
assert_can("0x1b", Int(0x1b));
|
||||
assert_can_int("0x1b", 0x1b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_hex_one_b() {
|
||||
assert_can("-0x1b", Int(-0x1b));
|
||||
assert_can_int("-0x1b", -0x1b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn octal_zero() {
|
||||
assert_can("0o0", Int(0o0));
|
||||
assert_can_int("0o0", 0o0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn octal_one_two() {
|
||||
assert_can("0o12", Int(0o12));
|
||||
assert_can_int("0o12", 0o12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_octal_one_two() {
|
||||
assert_can("-0o12", Int(-0o12));
|
||||
assert_can_int("-0o12", -0o12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_zero() {
|
||||
assert_can("0b0", Int(0b0));
|
||||
assert_can_int("0b0", 0b0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_one_one() {
|
||||
assert_can("0b11", Int(0b11));
|
||||
assert_can_int("0b11", 0b11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_binary_one_one() {
|
||||
assert_can("-0b11", Int(-0b11));
|
||||
assert_can_int("-0b11", -0b11);
|
||||
}
|
||||
|
||||
// LOCALS
|
||||
|
@ -232,7 +259,7 @@ mod test_canonicalize {
|
|||
|
||||
fn get_closure(expr: &Expr, i: usize) -> roc::can::expr::Recursive {
|
||||
match expr {
|
||||
Defs(assignments, _) => match &assignments.get(i).map(|def| &def.expr.value) {
|
||||
Defs(assignments, _) => match &assignments.get(i).map(|def| &def.loc_expr.value) {
|
||||
Some(Closure(_, recursion, _, _)) => recursion.clone(),
|
||||
Some(other @ _) => {
|
||||
panic!("assignment at {} is not a closure, but a {:?}", i, other)
|
||||
|
|
|
@ -908,7 +908,7 @@ mod test_infer {
|
|||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : Int -> Bool
|
||||
foo: Int -> Bool
|
||||
|
||||
foo 2
|
||||
"#
|
||||
|
|
|
@ -171,7 +171,7 @@ mod test_load {
|
|||
assert_eq!(expected_types.len(), module.defs.len());
|
||||
|
||||
for def in module.defs {
|
||||
for (symbol, expr_var) in def.vars_by_symbol {
|
||||
for (symbol, expr_var) in def.pattern_vars {
|
||||
let content = subs.get(expr_var).content;
|
||||
|
||||
name_all_type_vars(expr_var, &mut subs);
|
||||
|
|
|
@ -888,9 +888,9 @@ mod test_infer_uniq {
|
|||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
{ x: 4 }-> x
|
||||
"#
|
||||
when foo is
|
||||
{ x: 4 }-> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue