Re-constrain closures

This commit is contained in:
Richard Feldman 2019-10-20 16:26:06 -04:00
parent 593811d27f
commit c7dd1978fb
7 changed files with 340 additions and 700 deletions

View file

@ -12,13 +12,12 @@ pub enum Expr {
Float(f64), Float(f64),
Str(Box<str>), Str(Box<str>),
List(Variable, Vec<Located<Expr>>), List(Variable, Vec<Located<Expr>>),
EmptyList,
// Lookups // Lookups
Var(Variable, Symbol), Var(Variable, Symbol),
/// Works the same as Var, but has an important marking purpose. /// Works the same as Var, but has an important marking purpose.
/// See 13623e3f5f65ea2d703cf155f16650c1e8246502 for the bug this fixed. /// See 13623e3f5f65ea2d703cf155f16650c1e8246502 for the bug this fixed.
FunctionPointer(Variable, Symbol), FunctionPointer(Symbol),
// Pattern Matching // Pattern Matching
Case( Case(
@ -26,7 +25,7 @@ pub enum Expr {
Box<Located<Expr>>, Box<Located<Expr>>,
Vec<(Located<Pattern>, Located<Expr>)>, Vec<(Located<Pattern>, Located<Expr>)>,
), ),
Define( Defs(
Variable, Variable,
Vec<(Located<Pattern>, Located<Expr>)>, Vec<(Located<Pattern>, Located<Expr>)>,
Box<Located<Expr>>, Box<Located<Expr>>,

View file

@ -129,13 +129,14 @@ fn canonicalize_expr(
} }
ast::Expr::List(loc_elems) => { ast::Expr::List(loc_elems) => {
if loc_elems.is_empty() { if loc_elems.is_empty() {
let list_var = subs.mk_flex_var();
let constraint = Eq( let constraint = Eq(
constrain::empty_list_type(subs.mk_flex_var()), constrain::empty_list_type(list_var),
expected, expected,
region.clone(), region.clone(),
); );
(EmptyList, Output::new(constraint)) (List(list_var, Vec::new()), Output::new(constraint))
} else { } else {
let mut can_elems = Vec::with_capacity(loc_elems.len()); let mut can_elems = Vec::with_capacity(loc_elems.len());
let list_var = subs.mk_flex_var(); // `v` in the type (List v) let list_var = subs.mk_flex_var(); // `v` in the type (List v)
@ -182,7 +183,7 @@ fn canonicalize_expr(
// A list literal is never a tail call! // A list literal is never a tail call!
output.tail_call = None; output.tail_call = None;
(List(subs.mk_flex_var(), can_elems), output) (List(list_var, can_elems), output)
} }
} }
@ -214,52 +215,101 @@ fn canonicalize_expr(
// (expr, output) // (expr, output)
//} //}
ast::Expr::Apply((loc_fn, loc_args)) => { ast::Expr::Apply((loc_fn, loc_args)) => {
panic!("TODO canonicalize Apply"); // The expression that evaluates to the function being called, e.g. `foo` in
// let expected_type = panic!("TODO expected type for Apply"); // (foo) bar baz
// // Canonicalize the function expression and its arguments let fn_var = subs.mk_flex_var();
// let (fn_expr, mut output) = canonicalize_expr( let fn_type = Variable(fn_var);
// env, let fn_region = loc_fn.region.clone();
// subs, let fn_expected = NoExpectation(fn_type.clone());
// scope, // TODO look up the name and use NamedFnArg if possible.
// loc_fn.region.clone(), let fn_reason = Reason::AnonymousFnCall(loc_args.len() as u8);
// &loc_fn.value,
// expected_type,
// );
// let mut args = Vec::new();
// let mut outputs = Vec::new();
// for loc_arg in loc_args.iter() { // Canonicalize the function expression and its arguments
// let expected_type = panic!("TODO expected type for Apply arg"); let (fn_expr, mut output) = canonicalize_expr(
// let (arg_expr, arg_out) = canonicalize_expr( env,
// env, subs,
// subs, scope,
// scope, loc_fn.region.clone(),
// loc_arg.region.clone(), &loc_fn.value,
// &loc_arg.value, fn_expected,
// expected_type, );
// ); let fn_con = output.constraint;
// args.push(arg_expr); // The function's return type
// outputs.push(arg_out); let ret_var = subs.mk_flex_var();
// } let ret_type = Variable(ret_var);
// match &fn_expr.value { // This will be used in the occurs check
// &Var(_, ref sym) => { let mut vars = Vec::with_capacity(2 + loc_args.len());
// output.references.calls.insert(sym.clone());
// }
// _ => (),
// };
// let expr = Call(subs.mk_flex_var(), Box::new(fn_expr), args); vars.push(fn_var);
vars.push(ret_var);
// for arg_out in outputs { let mut arg_types = Vec::with_capacity(loc_args.len());
// output.references = output.references.union(arg_out.references); let mut arg_cons = Vec::with_capacity(loc_args.len());
// }
// // We're not tail-calling a symbol (by name), we're tail-calling a function value. let mut args = Vec::new();
// output.tail_call = None; let mut outputs = Vec::new();
// (expr, output) for (index, loc_arg) in loc_args.iter().enumerate() {
let region = loc_arg.region.clone();
let arg_var = subs.mk_flex_var();
let arg_type = Variable(arg_var);
// TODO look up the name and use NamedFnArg if possible.
let reason = Reason::AnonymousFnArg(index as u8);
let expected_arg = ForReason(reason, arg_type.clone(), region.clone());
let (arg_expr, arg_out) = canonicalize_expr(
env,
subs,
scope,
loc_arg.region.clone(),
&loc_arg.value,
expected_arg,
);
let arg_con = arg_out.constraint.clone();
vars.push(arg_var);
arg_types.push(arg_type);
arg_cons.push(arg_con);
args.push(arg_expr);
outputs.push(arg_out);
}
match &fn_expr.value {
&Var(_, ref sym) => {
output.references.calls.insert(sym.clone());
}
_ => (),
};
let expr = Call(subs.mk_flex_var(), Box::new(fn_expr), args);
for arg_out in outputs {
output.references = output.references.union(arg_out.references);
}
// We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None;
// TODO occurs check!
// return $ exists vars $ CAnd ...
let expected_fn_type = ForReason(
fn_reason,
Function(arg_types, Box::new(ret_type.clone())),
region.clone(),
);
output.constraint = And(vec![
fn_con,
Eq(fn_type, expected_fn_type, fn_region),
And(arg_cons),
Eq(ret_type, expected, region.clone()),
]);
(expr, output)
} }
ast::Expr::Operator((loc_left, loc_op, loc_right)) => { ast::Expr::Operator((loc_left, loc_op, loc_right)) => {
let op = loc_op.value; let op = loc_op.value;
@ -468,170 +518,198 @@ fn canonicalize_expr(
can_defs(env, subs, scope.clone(), defs, expected, loc_ret) can_defs(env, subs, scope.clone(), defs, expected, loc_ret)
} }
ast::Expr::Closure((loc_arg_patterns, loc_body_expr)) => { ast::Expr::Closure((loc_arg_patterns, loc_body_expr)) => {
panic!("TODO constrain Closure"); // The globally unique symbol that will refer to this closure once it gets converted
// // The globally unique symbol that will refer to this closure once it gets converted // into a top-level procedure for code gen.
// // into a top-level procedure for code gen. //
// // // The symbol includes the module name, the top-level declaration name, and the
// // The symbol includes the module name, the top-level declaration name, and the // index (0-based) of the closure within that declaration.
// // index (0-based) of the closure within that declaration. //
// // // Example: "MyModule$main$3" if this is the 4th closure in MyModule.main.
// // Example: "MyModule$main$3" if this is the 4th closure in MyModule.main. let symbol = scope.gen_unique_symbol();
// let symbol = scope.gen_unique_symbol();
// // The body expression gets a new scope for canonicalization. // The body expression gets a new scope for canonicalization.
// // Shadow `scope` to make sure we don't accidentally use the original one for the // Shadow `scope` to make sure we don't accidentally use the original one for the
// // rest of this block. // rest of this block.
// let mut scope = scope.clone(); let mut scope = scope.clone();
// let arg_idents: Vec<(Ident, (Symbol, Region))> = let arg_idents: Vec<(Ident, (Symbol, Region))> =
// idents_from_patterns(loc_arg_patterns.iter(), &scope); idents_from_patterns(loc_arg_patterns.iter(), &scope);
// // Add the arguments' idents to scope.idents. If there's a collision, // Add the arguments' idents to scope.idents. If there's a collision,
// // it means there was shadowing, which will be handled later. // it means there was shadowing, which will be handled later.
// scope.idents = union_pairs(scope.idents, arg_idents.iter()); scope.idents = union_pairs(scope.idents, arg_idents.iter());
// let mut can_args: Vec<Located<Pattern>> = Vec::with_capacity(loc_arg_patterns.len()); let mut state = PatternState {
assignment_types: ImMap::default(),
vars: Vec::with_capacity(loc_arg_patterns.len()),
reversed_constraints: Vec::with_capacity(1),
};
let args = constrain_args(loc_arg_patterns.iter(), &scope, subs, &mut state);
let mut can_args: Vec<Located<Pattern>> = Vec::with_capacity(loc_arg_patterns.len());
// for loc_pattern in loc_arg_patterns.into_iter() { for loc_pattern in loc_arg_patterns.into_iter() {
// // Exclude the current ident from shadowable_idents; you can't shadow yourself! // 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.) // (However, still include it in scope, because you *can* recursively refer to yourself.)
// let mut shadowable_idents = scope.idents.clone(); let mut shadowable_idents = scope.idents.clone();
// remove_idents(&loc_pattern.value, &mut shadowable_idents); remove_idents(&loc_pattern.value, &mut shadowable_idents);
// can_args.push(canonicalize_pattern( can_args.push(canonicalize_pattern(
// env, env,
// subs, subs,
// constraints, &mut scope,
// &mut scope, &FunctionArg,
// &FunctionArg, &loc_pattern,
// &loc_pattern, &mut shadowable_idents,
// &mut shadowable_idents, ))
// )) }
// }
// let (loc_body_expr, mut output) = canonicalize_expr( let body_type = NoExpectation(args.ret_type);
// env, let (loc_body_expr, mut output) = canonicalize_expr(
// constraints, env,
// subs, subs,
// &mut scope, &mut scope,
// loc_body_expr.region.clone(), loc_body_expr.region.clone(),
// &loc_body_expr.value, &loc_body_expr.value,
// panic!("TODO expected type"), body_type,
// ); );
// // Now that we've collected all the references, check to see if any of the args we defined state.reversed_constraints.reverse();
// // went unreferenced. If any did, report them as unused arguments.
// for (ident, (arg_symbol, region)) in arg_idents {
// if !output.references.has_local(&arg_symbol) {
// // The body never referenced this argument we declared. It's an unused argument!
// env.problem(Problem::UnusedArgument(Located {
// region,
// value: ident,
// }));
// }
// // We shouldn't ultimately count arguments as referenced locals. Otherwise, let assignments_constraint = And(state.reversed_constraints);
// // we end up with weird conclusions like the expression (\x -> x + 1) let ret_constraint = output.constraint;
// // references the (nonexistant) local variable x!
// output.references.locals.remove(&arg_symbol);
// }
// let var = subs.mk_flex_var(); // TODO constrain this! // panic!("TODO occurs check");
// // We've finished analyzing the closure. Its references.locals are now the values it closes over, // We need a var to record in the procedure for later.
// // since we removed the only locals it shouldn't close over (its arguments). // We'll set it equal to the overall `expected` we've been passed.
// // Register it as a top-level procedure in the Env! let var = subs.mk_flex_var();
// env.register_closure( let typ = Variable(var);
// symbol.clone(),
// can_args,
// loc_body_expr,
// region.clone(),
// output.references.clone(),
// var,
// );
// // Always return a function pointer, in case that's how the closure is being used (e.g. with Apply). output.constraint = And(vec![
// (FunctionPointer(subs.mk_flex_var(), symbol), output) Let(Box::new(LetConstraint {
//} rigid_vars: Vec::new(),
flex_vars: state.vars,
assignment_types: state.assignment_types,
assignments_constraint,
ret_constraint,
})),
// "the closure's type is equal to the var we've stored for later use in the proc"
Eq(args.typ, NoExpectation(typ.clone()), region.clone()),
// "the var we've stored for later is equal to the overall expected type"
Eq(typ, expected, region.clone()),
]);
//ast::Expr::Case(loc_cond, branches) => { // Now that we've collected all the references, check to see if any of the args we defined
// // Canonicalize the conditional // went unreferenced. If any did, report them as unused arguments.
// let (can_cond, mut output) = canonicalize(env, scope, *loc_cond); for (ident, (arg_symbol, region)) in arg_idents {
// let mut can_branches = Vec::with_capacity(branches.len()); if !output.references.has_local(&arg_symbol) {
// let mut recorded_tail_call = false; // The body never referenced this argument we declared. It's an unused argument!
env.problem(Problem::UnusedArgument(Located {
region,
value: ident,
}));
}
// for (loc_pattern, loc_expr) in branches { // We shouldn't ultimately count arguments as referenced locals. Otherwise,
// // Each case branch gets a new scope for canonicalization. // we end up with weird conclusions like the expression (\x -> x + 1)
// // Shadow `scope` to make sure we don't accidentally use the original one for the // references the (nonexistant) local variable x!
// // rest of this block. output.references.locals.remove(&arg_symbol);
// let mut scope = scope.clone(); }
// // Exclude the current ident from shadowable_idents; you can't shadow yourself! // We've finished analyzing the closure. Its references.locals are now the values it closes over,
// // (However, still include it in scope, because you *can* recursively refer to yourself.) // since we removed the only locals it shouldn't close over (its arguments).
// let mut shadowable_idents = scope.idents.clone(); // Register it as a top-level procedure in the Env!
// remove_idents(loc_pattern.value.clone(), &mut shadowable_idents); env.register_closure(
symbol.clone(),
can_args,
loc_body_expr,
region.clone(),
output.references.clone(),
var,
);
// let loc_can_pattern = canonicalize_pattern( // Always return a function pointer, in case that's how the closure is being used (e.g. with Apply).
// env, (FunctionPointer(symbol), output)
// &mut scope,
// &CaseBranch,
// &loc_pattern,
// &mut shadowable_idents,
// );
// // Patterns introduce new idents to the scope!
// // Add the assigned identifiers to scope. If there's a collision, it means there
// // was shadowing, which will be handled later.
// let assigned_idents: Vec<(Ident, (Symbol, Region))> =
// idents_from_patterns(std::iter::once(&loc_pattern), &scope);
// scope.idents = union_pairs(scope.idents, assigned_idents.iter());
// let (can_expr, branch_output) = canonicalize(env, &mut scope, loc_expr);
// output.references = output.references.union(branch_output.references);
// // If all branches are tail calling the same symbol, then so is the conditional as a whole.
// if !recorded_tail_call {
// // If we haven't recorded output.tail_call yet, record it.
// output.tail_call = branch_output.tail_call;
// recorded_tail_call = true;
// } else if branch_output.tail_call != output.tail_call {
// // If we recorded output.tail_call, but what we recorded differs from what we just saw,
// // then game over. This can't possibly be a self tail call!
// output.tail_call = None;
// }
// // Now that we've collected all the references for this branch, check to see if
// // any of the new idents it defined were unused. If any were, report it.
// for (ident, (symbol, region)) in assigned_idents {
// if !output.references.has_local(&symbol) {
// let loc_ident = Located {
// region: region.clone(),
// value: ident.clone(),
// };
// env.problem(Problem::UnusedAssignment(loc_ident));
// }
// }
// can_branches.push((loc_can_pattern, can_expr));
// }
// // One of the branches should have flipped this, so this should only happen
// // in the situation where the case had no branches. That can come up, though!
// // A case with no branches is a runtime error, but it will mess things up
// // if code gen mistakenly thinks this is a tail call just because its condition
// // happend to be one. (The condition gave us our initial output value.)
// if !recorded_tail_call {
// output.tail_call = None;
// }
// // Incorporate all three expressions into a combined Output value.
// let expr = Case(Box::new(can_cond), can_branches);
// (expr, output)
} }
// ast::Expr::Case(loc_cond, branches) => {
// // Canonicalize the conditional
// let (can_cond, mut output) = canonicalize(env, scope, *loc_cond);
// let mut can_branches = Vec::with_capacity(branches.len());
// let mut recorded_tail_call = false;
// for (loc_pattern, loc_expr) in branches {
// // Each case branch gets a new scope for canonicalization.
// // Shadow `scope` to make sure we don't accidentally use the original one for the
// // rest of this block.
// let mut scope = scope.clone();
// // 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 mut shadowable_idents = scope.idents.clone();
// remove_idents(loc_pattern.value.clone(), &mut shadowable_idents);
// let loc_can_pattern = canonicalize_pattern(
// env,
// &mut scope,
// &CaseBranch,
// &loc_pattern,
// &mut shadowable_idents,
// );
// // Patterns introduce new idents to the scope!
// // Add the assigned identifiers to scope. If there's a collision, it means there
// // was shadowing, which will be handled later.
// let assigned_idents: Vec<(Ident, (Symbol, Region))> =
// idents_from_patterns(std::iter::once(&loc_pattern), &scope);
// scope.idents = union_pairs(scope.idents, assigned_idents.iter());
// let (can_expr, branch_output) = canonicalize(env, &mut scope, loc_expr);
// output.references = output.references.union(branch_output.references);
// // If all branches are tail calling the same symbol, then so is the conditional as a whole.
// if !recorded_tail_call {
// // If we haven't recorded output.tail_call yet, record it.
// output.tail_call = branch_output.tail_call;
// recorded_tail_call = true;
// } else if branch_output.tail_call != output.tail_call {
// // If we recorded output.tail_call, but what we recorded differs from what we just saw,
// // then game over. This can't possibly be a self tail call!
// output.tail_call = None;
// }
// // Now that we've collected all the references for this branch, check to see if
// // any of the new idents it defined were unused. If any were, report it.
// for (ident, (symbol, region)) in assigned_idents {
// if !output.references.has_local(&symbol) {
// let loc_ident = Located {
// region: region.clone(),
// value: ident.clone(),
// };
// env.problem(Problem::UnusedAssignment(loc_ident));
// }
// }
// can_branches.push((loc_can_pattern, can_expr));
// }
// // One of the branches should have flipped this, so this should only happen
// // in the situation where the case had no branches. That can come up, though!
// // A case with no branches is a runtime error, but it will mess things up
// // if code gen mistakenly thinks this is a tail call just because its condition
// // happend to be one. (The condition gave us our initial output value.)
// if !recorded_tail_call {
// output.tail_call = None;
// }
// // Incorporate all three expressions into a combined Output value.
// let expr = Case(Box::new(can_cond), can_branches);
// (expr, output)
// }
ast::Expr::SpaceBefore(sub_expr, _spaces) => { ast::Expr::SpaceBefore(sub_expr, _spaces) => {
// Continue on; spaces don't do anything. // Continue on; spaces don't do anything.
return canonicalize_expr(env, subs, scope, region, sub_expr, expected); return canonicalize_expr(env, subs, scope, region, sub_expr, expected);
@ -1185,7 +1263,16 @@ impl Info {
} }
} }
type BoundTypeVars = ImMap<Box<str>, Type>; /// This lets us share bound type variables between nested annotations, e.g.
///
/// blah : Map k v -> Int
/// blah mapping =
/// nested : Map k v # <-- the same k and v from the top-level annotation
/// nested = mapping
/// 42
///
/// In elm/compiler this is called RTV - the "Rigid Type Variables" dictionary.
// type BoundTypeVars = ImMap<Box<str>, Type>;
struct PatternState { struct PatternState {
assignment_types: ImMap<Symbol, Located<Type>>, assignment_types: ImMap<Symbol, Located<Type>>,
@ -1387,7 +1474,7 @@ fn can_defs<'a>(
( (
&ast::Pattern::Identifier(ref name), &ast::Pattern::Identifier(ref name),
&Pattern::Identifier(_, ref assigned_symbol), &Pattern::Identifier(_, ref assigned_symbol),
&FunctionPointer(_, ref symbol), &FunctionPointer(ref symbol),
) => { ) => {
// Since everywhere in the code it'll be referred to by its assigned name, // Since everywhere in the code it'll be referred to by its assigned name,
// remove its generated name from the procedure map. (We'll re-insert it later.) // remove its generated name from the procedure map. (We'll re-insert it later.)
@ -1589,7 +1676,7 @@ fn can_defs<'a>(
} }
( (
Define(subs.mk_flex_var(), can_assignments, Box::new(ret_expr)), Defs(subs.mk_flex_var(), can_assignments, Box::new(ret_expr)),
output, output,
) )
} }
@ -1631,3 +1718,58 @@ fn can_defs<'a>(
} }
} }
} }
struct Args {
pub vars: Vec<Variable>,
pub typ: Type,
pub ret_type: Type,
}
fn constrain_args<'a, I>(args: I, scope: &Scope, subs: &mut Subs, state: &mut PatternState) -> Args
where
I: Iterator<Item = &'a Located<ast::Pattern<'a>>>,
{
let (mut vars, arg_types) = patterns_to_variables(args.into_iter(), scope, subs, state);
let ret_var = subs.mk_flex_var();
let ret_type = Type::Variable(ret_var);
vars.push(ret_var);
let typ = Type::Function(arg_types, Box::new(ret_type.clone()));
Args {
vars,
typ,
ret_type,
}
}
fn patterns_to_variables<'a, I>(
patterns: I,
scope: &Scope,
subs: &mut Subs,
state: &mut PatternState,
) -> (Vec<Variable>, Vec<Type>)
where
I: Iterator<Item = &'a Located<ast::Pattern<'a>>>,
{
let mut vars = Vec::with_capacity(state.vars.capacity());
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
for loc_pattern in patterns {
let pattern_var = subs.mk_flex_var();
let pattern_type = Type::Variable(pattern_var);
state.add_pattern(
scope,
loc_pattern.clone(),
NoExpectation(pattern_type.clone()),
);
vars.push(pattern_var);
pattern_types.push(pattern_type);
}
(vars, pattern_types)
}

View file

@ -3,13 +3,11 @@ use can::problem::Problem;
use can::scope::Scope; use can::scope::Scope;
use can::symbol::Symbol; use can::symbol::Symbol;
use collections::ImMap; use collections::ImMap;
use constrain::{self, Constraints};
use ident::{Ident, VariantName}; use ident::{Ident, VariantName};
use parse::ast; use parse::ast;
use region::{Located, Region}; use region::{Located, Region};
use subs::Subs; use subs::Subs;
use subs::Variable; use subs::Variable;
use types::Constraint;
/// A pattern, including possible problems (e.g. shadowing) so that /// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached. /// codegen can generate a runtime error if this pattern is reached.

View file

@ -1,48 +1,10 @@
use can::expr::Expr; use region::Region;
// use can::pattern::Pattern;
use can::procedure::Procedure;
// use can::symbol::Symbol;
use collections::ImMap;
use operator::{ArgSide, Operator};
use region::{Located, Region};
use subs::{Subs, Variable}; use subs::{Subs, Variable};
use types::Constraint::{self, *}; use types::Constraint::{self, *};
use types::Expected::{self, *}; use types::Expected::{self, *};
use types::Type::{self, *}; use types::Type::{self, *};
use types::{self, Reason}; use types::{self, Reason};
/// This lets us share bound type variables between nested annotations, e.g.
///
/// blah : Map k v -> Int
/// blah mapping =
/// nested : Map k v # <-- the same k and v from the top-level annotation
/// nested = mapping
/// 42
///
/// In elm/compiler this is called RTV - the "Rigid Type Variables" dictionary.
type BoundTypeVars<'a> = ImMap<&'a str, Type>;
#[derive(Debug)]
pub struct Constraints(Vec<Constraint>);
impl Constraints {
pub fn new() -> Self {
Constraints(Vec::new())
}
pub fn add(&mut self, constraint: Constraint) {
self.0.push(constraint)
}
pub fn iter(&self) -> std::slice::Iter<'_, Constraint> {
self.0.iter()
}
pub fn into_iter(self) -> std::vec::IntoIter<Constraint> {
self.0.into_iter()
}
}
pub fn int_literal(subs: &mut Subs, expected: Expected<Type>, region: Region) -> Constraint { pub fn int_literal(subs: &mut Subs, expected: Expected<Type>, region: Region) -> Constraint {
let typ = number_literal_type("Int", "Integer"); let typ = number_literal_type("Int", "Integer");
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
@ -94,277 +56,6 @@ fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
} }
} }
pub fn constrain<'a>(
bound_vars: &'a BoundTypeVars<'a>,
subs: &'a mut Subs,
loc_expr: Located<Expr>,
expected: Expected<Type>,
) -> Constraint {
panic!("TODO inline constrain");
// let region = loc_expr.region;
// match loc_expr.value {
// List(elems) => list(elems, bound_vars, subs, expected, region),
// Var(sym) | FunctionPointer(sym) => Lookup(sym, expected, region),
// Assign(assignments, ret_expr) => {
// let ret_con = constrain(bound_vars, subs, *ret_expr, expected);
// if assignments.len() == 1 {
// // Don't bother allocating a Vec of them if there's only one!
// let (loc_pattern, loc_expr) = assignments.into_iter().next().unwrap();
// constrain_def(loc_pattern, loc_expr, bound_vars, subs, ret_con)
// } else {
// constrain_defs(assignments, bound_vars, subs, ret_con)
// }
// }
// Call(box_loc_fn_expr, args) => {
// constrain_call(bound_vars, subs, *box_loc_fn_expr, args, expected, region)
// }
// Expr::Operator(l_box_loc_expr, loc_op, r_box_loc_expr) => constrain_op(
// bound_vars,
// subs,
// *l_box_loc_expr,
// loc_op,
// *r_box_loc_expr,
// expected,
// region,
// ),
// _ => panic!("TODO constraints for {:?}", loc_expr.value),
// }
}
// fn constrain_op(
// bound_vars: &BoundTypeVars,
// subs: &mut Subs,
// l_loc_expr: Located<Expr>,
// loc_op: Located<Operator>,
// r_loc_expr: Located<Expr>,
// expected: Expected<Type>,
// region: Region,
// ) -> Constraint {
// let op = loc_op.value;
// let op_types = Type::for_operator(op);
// // TODO use fn_var
// let _fn_var = subs.mk_flex_var();
// let ret_var = subs.mk_flex_var();
// let ret_type = Variable(ret_var);
// let ret_reason = Reason::OperatorRet(op);
// let expected_ret_type = ForReason(ret_reason, op_types.ret, region.clone());
// let (_l_var, l_con) = constrain_op_arg(
// ArgSide::Left,
// bound_vars,
// subs,
// op,
// op_types.left,
// l_loc_expr,
// );
// let (_r_var, r_con) = constrain_op_arg(
// ArgSide::Right,
// bound_vars,
// subs,
// op,
// op_types.right,
// r_loc_expr,
// );
// // TODO occurs check!
// // let vars = vec![fn_var, ret_var, l_var, r_var];
// // return $ exists (funcVar:resultVar:argVars) $ CAnd ...
// And(vec![
// // the constraint from constrain on l_expr, expecting its hardcoded type
// l_con,
// // the constraint from constrain on r_expr, expecting its hardcoded type
// r_con,
// // The operator's args and return type should be its hardcoded types
// Eq(ret_type.clone(), expected_ret_type, region.clone()),
// // Finally, link the operator's return type to the given expected type
// Eq(ret_type, expected, region),
// ])
// }
// #[inline(always)]
// pub fn constrain_op_arg(
// arg_side: ArgSide,
// bound_vars: &BoundTypeVars,
// subs: &mut Subs,
// op: Operator,
// typ: Type,
// loc_arg: Located<Expr>,
// ) -> (Variable, Constraint) {
// let region = loc_arg.region.clone();
// let arg_var = subs.mk_flex_var();
// let arg_type = Variable(arg_var);
// let reason = Reason::OperatorArg(op, arg_side);
// let expected_arg = ForReason(reason, typ, region.clone());
// let arg_con = And(vec![
// // Recursively constrain the variable
// constrain(bound_vars, subs, loc_arg, NoExpectation(arg_type.clone())),
// // The variable should ultimately equal the hardcoded expected type
// Eq(arg_type, expected_arg, region),
// ]);
// (arg_var, arg_con)
// }
// fn constrain_call(
// bound_vars: &BoundTypeVars,
// subs: &mut Subs,
// loc_expr: Located<Expr>,
// args: Vec<Located<Expr>>,
// expected: Expected<Type>,
// region: Region,
// ) -> Constraint {
// // The expression that evaluates to the function being called, e.g. `foo` in
// // (foo) bar baz
// let fn_var = subs.mk_flex_var();
// let fn_type = Variable(fn_var);
// let fn_region = loc_expr.region.clone();
// let fn_expected = NoExpectation(fn_type.clone());
// let fn_con = constrain(bound_vars, subs, loc_expr, fn_expected);
// let fn_reason =
// // TODO look up the name and use NamedFnArg if possible.
// Reason::AnonymousFnCall(args.len() as u8);
// // The function's return type
// let ret_var = subs.mk_flex_var();
// let ret_type = Variable(ret_var);
// // This will be used in the occurs check
// let mut vars = Vec::with_capacity(2 + args.len());
// vars.push(fn_var);
// vars.push(ret_var);
// let mut arg_types = Vec::with_capacity(args.len());
// let mut arg_cons = Vec::with_capacity(args.len());
// for (index, loc_arg) in args.into_iter().enumerate() {
// let region = loc_arg.region.clone();
// let arg_var = subs.mk_flex_var();
// let arg_type = Variable(arg_var);
// let reason =
// // TODO look up the name and use NamedFnArg if possible.
// Reason::AnonymousFnArg(index as u8);
// let expected_arg = ForReason(reason, arg_type.clone(), region.clone());
// let arg_con = constrain(bound_vars, subs, loc_arg, expected_arg);
// vars.push(arg_var);
// arg_types.push(arg_type);
// arg_cons.push(arg_con);
// }
// // TODO occurs check!
// // return $ exists vars $ CAnd ...
// let expected_fn_type = ForReason(
// fn_reason,
// Function(arg_types, Box::new(ret_type.clone())),
// region.clone(),
// );
// And(vec![
// fn_con,
// Eq(fn_type, expected_fn_type, fn_region),
// And(arg_cons),
// Eq(ret_type, expected, region),
// ])
// }
// pub fn constrain_defs(
// assignments: Vec<(Located<Pattern>, Located<Expr>)>,
// bound_vars: &BoundTypeVars,
// subs: &mut Subs,
// ret_con: Constraint,
// ) -> Constraint {
// let rigid_info = Info::with_capacity(assignments.len());
// let mut flex_info = Info::with_capacity(assignments.len());
// for (loc_pattern, loc_expr) in assignments {
// let mut state = PatternState {
// assignment_types: ImMap::default(),
// vars: Vec::with_capacity(1),
// reversed_constraints: Vec::with_capacity(1),
// };
// let pattern_var = subs.mk_flex_var();
// let pattern_type = Type::Variable(pattern_var);
// flex_info.vars.push(pattern_var);
// state.add_pattern(loc_pattern.clone(), NoExpectation(pattern_type.clone()));
// state.reversed_constraints.reverse();
// let assignments_constraint = And(state.reversed_constraints);
// // Set up types for the expr we're assigned to.
// let expr_var = subs.mk_flex_var();
// let expr_type = Type::Variable(expr_var);
// // Any time there's a lookup on this symbol in the outer Let,
// // it should result in this expression's type. After all, this
// // is the type to which this symbol is assigned!
// add_pattern_to_lookup_types(
// loc_pattern,
// &mut flex_info.assignment_types,
// expr_type.clone(),
// );
// let expr_con = constrain(bound_vars, subs, loc_expr, NoExpectation(expr_type));
// let def_con = Let(Box::new(LetConstraint {
// rigid_vars: Vec::new(),
// flex_vars: state.vars,
// assignment_types: state.assignment_types,
// assignments_constraint,
// ret_constraint: expr_con,
// }));
// flex_info.constraints.push(def_con);
// }
// // Rigid constraint
// Let(Box::new(LetConstraint {
// rigid_vars: rigid_info.vars,
// flex_vars: Vec::new(),
// assignment_types: rigid_info.assignment_types,
// assignments_constraint:
// // Flex constraint
// Let(Box::new(LetConstraint {
// rigid_vars: Vec::new(),
// flex_vars: flex_info.vars,
// assignment_types: flex_info.assignment_types.clone(),
// assignments_constraint:
// // Final flex constraints
// Let(Box::new(LetConstraint {
// rigid_vars: Vec::new(),
// flex_vars: Vec::new(),
// assignment_types: flex_info.assignment_types,
// assignments_constraint: True,
// ret_constraint: And(flex_info.constraints)
// })),
// ret_constraint: And(vec![And(rigid_info.constraints), ret_con])
// })),
// ret_constraint: True,
// }))
// }
// struct Info {
// pub vars: Vec<Variable>,
// pub constraints: Vec<Constraint>,
// pub assignment_types: ImMap<Symbol, Located<Type>>,
// }
// impl Info {
// pub fn with_capacity(capacity: usize) -> Self {
// Info {
// vars: Vec::with_capacity(capacity),
// constraints: Vec::with_capacity(capacity),
// assignment_types: ImMap::default(),
// }
// }
// }
pub fn empty_list_type(var: Variable) -> Type { pub fn empty_list_type(var: Variable) -> Type {
list_type(Type::Variable(var)) list_type(Type::Variable(var))
} }
@ -376,190 +67,3 @@ pub fn list_type(typ: Type) -> Type {
pub fn str_type() -> Type { pub fn str_type() -> Type {
builtin_type("Str", "Str", Vec::new()) builtin_type("Str", "Str", Vec::new())
} }
// pub fn constrain_def(
// loc_pattern: Located<Pattern>,
// loc_expr: Located<Expr>,
// bound_vars: &BoundTypeVars,
// subs: &mut Subs,
// ret_constraint: Constraint,
// ) -> Constraint {
// let mut state = PatternState {
// assignment_types: ImMap::default(),
// vars: Vec::with_capacity(1),
// reversed_constraints: Vec::with_capacity(1),
// };
// let mut vars = Vec::with_capacity(state.vars.capacity());
// let pattern_var = subs.mk_flex_var();
// let pattern_type = Type::Variable(pattern_var);
// state.add_pattern(loc_pattern.clone(), NoExpectation(pattern_type.clone()));
// vars.push(pattern_var);
// // Set up types for the expr we're assigned to.
// let expr_var = subs.mk_flex_var();
// let expr_type = Type::Variable(expr_var);
// // These types are *only* for the current pattern. In contrast, the ones
// // in PatternState represent
// let mut lookup_types: ImMap<Symbol, Located<Type>> = ImMap::default();
// // Any time there's a lookup on this symbol in the outer Let,
// // it should result in this expression's type. After all, this
// // is the type to which this symbol is assigned!
// add_pattern_to_lookup_types(loc_pattern, &mut lookup_types, expr_type.clone());
// state.reversed_constraints.reverse();
// Let(Box::new(LetConstraint {
// rigid_vars: Vec::new(),
// flex_vars: vars,
// assignments_constraint:
// // This nested constraint represents the actually constrained expr.
// Let(Box::new(LetConstraint {
// rigid_vars: Vec::new(),
// flex_vars: state.vars,
// assignment_types: state.assignment_types,
// assignments_constraint: And(state.reversed_constraints),
// ret_constraint: constrain(bound_vars, subs, loc_expr, NoExpectation(expr_type))
// })),
// assignment_types: lookup_types,
// ret_constraint,
// }))
// }
// fn add_pattern_to_lookup_types(
// loc_pattern: Located<Pattern>,
// lookup_types: &mut ImMap<Symbol, Located<Type>>,
// expr_type: Type,
// ) {
// let region = loc_pattern.region;
// match loc_pattern.value {
// Pattern::Identifier(symbol) => {
// let loc_type = Located {
// region,
// value: expr_type,
// };
// lookup_types.insert(symbol, loc_type);
// }
// _ => panic!("TODO constrain patterns other than Identifier"),
// }
// }
pub fn constrain_procedure<'a>(
bound_vars: &'a BoundTypeVars<'a>,
constraints: &'a mut Constraints,
subs: &'a mut Subs,
proc: Procedure,
expected: Expected<Type>,
) -> Constraint {
panic!("TODO inline constrain_procedure");
// let mut state = PatternState {
// assignment_types: ImMap::default(),
// vars: Vec::with_capacity(proc.args.len()),
// reversed_constraints: Vec::with_capacity(1),
// };
// let args = constrain_args(proc.args.into_iter(), subs, &mut state);
// let body_type = NoExpectation(args.ret_type);
// let ret_constraint = constrain(bound_vars, subs, proc.body, body_type);
// state.reversed_constraints.reverse();
// let assignments_constraint = And(state.reversed_constraints);
// // panic!("TODO occurs check");
// And(vec![
// Let(Box::new(LetConstraint {
// rigid_vars: Vec::new(),
// flex_vars: state.vars,
// assignment_types: state.assignment_types,
// assignments_constraint,
// ret_constraint,
// })),
// Eq(args.typ, expected, proc.definition),
// ])
}
// struct Args {
// pub vars: Vec<Variable>,
// pub typ: Type,
// pub ret_type: Type,
// }
// fn constrain_args<I>(args: I, subs: &mut Subs, state: &mut PatternState) -> Args
// where
// I: Iterator<Item = Located<Pattern>>,
// {
// let (mut vars, arg_types) = patterns_to_variables(args.into_iter(), subs, state);
// let ret_var = subs.mk_flex_var();
// let ret_type = Type::Variable(ret_var);
// vars.push(ret_var);
// let typ = Type::Function(arg_types, Box::new(ret_type.clone()));
// Args {
// vars,
// typ,
// ret_type,
// }
// }
// fn patterns_to_variables<I>(
// patterns: I,
// subs: &mut Subs,
// state: &mut PatternState,
// ) -> (Vec<Variable>, Vec<Type>)
// where
// I: Iterator<Item = Located<Pattern>>,
// {
// let mut vars = Vec::with_capacity(state.vars.capacity());
// let mut pattern_types = Vec::with_capacity(state.vars.capacity());
// for loc_pattern in patterns {
// let pattern_var = subs.mk_flex_var();
// let pattern_type = Type::Variable(pattern_var);
// state.add_pattern(loc_pattern, NoExpectation(pattern_type.clone()));
// vars.push(pattern_var);
// pattern_types.push(pattern_type);
// }
// (vars, pattern_types)
// }
// impl PatternState {
// pub fn add_pattern(&mut self, loc_pattern: Located<Pattern>, expected: Expected<Type>) {
// use can::pattern::Pattern::*;
// let region = loc_pattern.region;
// match loc_pattern.value {
// Identifier(symbol) => self.add_to_assignment_types(region, symbol, expected),
// Underscore => (),
// _ => panic!("TODO other patterns"),
// }
// }
// fn add_to_assignment_types(
// &mut self,
// region: Region,
// symbol: Symbol,
// expected: Expected<Type>,
// ) {
// self.assignment_types.insert(
// symbol,
// Located {
// region,
// value: expected.get_type(),
// },
// );
// }
// }

View file

@ -5,8 +5,6 @@ use region::Located;
use region::Region; use region::Region;
use subs::Variable; use subs::Variable;
type ModuleName = String;
// The standard modules // The standard modules
pub const MOD_FLOAT: &'static str = "Float"; pub const MOD_FLOAT: &'static str = "Float";
pub const MOD_INT: &'static str = "Int"; pub const MOD_INT: &'static str = "Int";

View file

@ -9,7 +9,6 @@ use roc::can::procedure::Procedure;
use roc::can::symbol::Symbol; use roc::can::symbol::Symbol;
use roc::can::Output; use roc::can::Output;
use roc::collections::{ImMap, MutMap}; use roc::collections::{ImMap, MutMap};
use roc::constrain::Constraints;
use roc::ident::Ident; use roc::ident::Ident;
use roc::parse; use roc::parse;
use roc::parse::ast::{self, Attempting}; use roc::parse::ast::{self, Attempting};
@ -19,10 +18,12 @@ use roc::region::{Located, Region};
use roc::subs::{Subs, Variable}; use roc::subs::{Subs, Variable};
use roc::types::{Expected, Type}; use roc::types::{Expected, Type};
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(&input, Attempting::Module); let state = State::new(&input, Attempting::Module);
let parser = space0_before(loc(parse::expr(0)), 0); let parser = space0_before(loc(parse::expr(0)), 0);

View file

@ -13,13 +13,11 @@ mod test_infer {
use helpers::can_expr; use helpers::can_expr;
use roc::infer::infer_expr; use roc::infer::infer_expr;
use roc::pretty_print_types::content_to_string; use roc::pretty_print_types::content_to_string;
use roc::region::Located;
use roc::subs::Subs;
// HELPERS // HELPERS
fn infer_eq(src: &str, expected: &str) { fn infer_eq(src: &str, expected: &str) {
let (expr, output, _, procedures, mut subs, variable) = can_expr(src); let (_, output, _, procedures, mut subs, variable) = can_expr(src);
let content = infer_expr(&mut subs, procedures, &output.constraint, variable); let content = infer_expr(&mut subs, procedures, &output.constraint, variable);
let actual_str = content_to_string(content, &mut subs); let actual_str = content_to_string(content, &mut subs);