mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
wip canonicalize case
This commit is contained in:
parent
d8e0e77110
commit
09c7d75f0d
3 changed files with 145 additions and 62 deletions
180
src/can/mod.rs
180
src/can/mod.rs
|
@ -9,7 +9,7 @@ use self::scope::Scope;
|
||||||
use self::symbol::Symbol;
|
use self::symbol::Symbol;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use collections::{ImMap, ImSet, MutMap, MutSet};
|
use collections::{ImMap, ImSet, MutMap, MutSet};
|
||||||
use constrain;
|
use constrain::{self, exists};
|
||||||
use graph::{strongly_connected_component, topological_sort};
|
use graph::{strongly_connected_component, topological_sort};
|
||||||
use ident::Ident;
|
use ident::Ident;
|
||||||
use parse::ast::{self, Def};
|
use parse::ast::{self, Def};
|
||||||
|
@ -298,12 +298,16 @@ fn canonicalize_expr(
|
||||||
region,
|
region,
|
||||||
);
|
);
|
||||||
|
|
||||||
output.constraint = And(vec![
|
let mut exists_vars = Vec::with_capacity(arg_vars.len() + 2);
|
||||||
|
|
||||||
|
exists_vars = funcVar:resultVar:argVars;
|
||||||
|
|
||||||
|
output.constraint = exists(exists_vars, And(vec![
|
||||||
fn_con,
|
fn_con,
|
||||||
Eq(fn_type, expected_fn_type, fn_region),
|
Eq(fn_type, expected_fn_type, fn_region),
|
||||||
And(arg_cons),
|
And(arg_cons),
|
||||||
Eq(ret_type, expected, region),
|
Eq(ret_type, expected, region),
|
||||||
]);
|
]));
|
||||||
|
|
||||||
(expr, output)
|
(expr, output)
|
||||||
}
|
}
|
||||||
|
@ -487,7 +491,7 @@ fn canonicalize_expr(
|
||||||
let var = subs.mk_flex_var();
|
let var = subs.mk_flex_var();
|
||||||
let typ = Variable(var);
|
let typ = Variable(var);
|
||||||
|
|
||||||
output.constraint = And(vec![
|
output.constraint = exists(args.vars, And(vec![
|
||||||
Let(Box::new(LetConstraint {
|
Let(Box::new(LetConstraint {
|
||||||
rigid_vars: Vec::new(),
|
rigid_vars: Vec::new(),
|
||||||
flex_vars: state.vars,
|
flex_vars: state.vars,
|
||||||
|
@ -499,7 +503,7 @@ fn canonicalize_expr(
|
||||||
Eq(args.typ, NoExpectation(typ.clone()), region),
|
Eq(args.typ, NoExpectation(typ.clone()), region),
|
||||||
// "the var we've stored for later is equal to the overall expected type"
|
// "the var we've stored for later is equal to the overall expected type"
|
||||||
Eq(typ, expected, region),
|
Eq(typ, expected, region),
|
||||||
]);
|
]));
|
||||||
|
|
||||||
// Now that we've collected all the references, check to see if any of the args we defined
|
// Now that we've collected all the references, check to see if any of the args we defined
|
||||||
// went unreferenced. If any did, report them as unused arguments.
|
// went unreferenced. If any did, report them as unused arguments.
|
||||||
|
@ -536,67 +540,53 @@ fn canonicalize_expr(
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::Expr::Case(loc_cond, branches) => {
|
ast::Expr::Case(loc_cond, branches) => {
|
||||||
// Canonicalize the conditional
|
// Each pattern must have the same type as the condition expression.
|
||||||
let (can_cond, mut output) = canonicalize(env, scope, *loc_cond);
|
let pattern_var = subs.mk_flex_var();
|
||||||
let mut can_branches = Vec::with_capacity(branches.len());
|
let pattern_type = Variable(pattern_var);
|
||||||
let mut recorded_tail_call = false;
|
let (can_cond, mut output) = canonicalize_expr(
|
||||||
|
|
||||||
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,
|
env,
|
||||||
&mut scope,
|
subs,
|
||||||
&CaseBranch,
|
scope,
|
||||||
&loc_pattern,
|
region,
|
||||||
&mut shadowable_idents,
|
&loc_cond.value,
|
||||||
|
NoExpectation(pattern_type),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Patterns introduce new idents to the scope!
|
let mut recorded_tail_call = false;
|
||||||
// Add the assigned identifiers to scope. If there's a collision, it means there
|
let mut can_branches = Vec::with_capacity(branches.len());
|
||||||
// was shadowing, which will be handled later.
|
let mut constraints = Vec::with_capacity(branches.len() + 1);
|
||||||
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());
|
constraints.push(output.constraint);
|
||||||
|
|
||||||
let (can_expr, branch_output) = canonicalize(env, &mut scope, loc_expr);
|
match expected {
|
||||||
|
FromAnnotation(name, arity, _, typ) => {
|
||||||
output.references = output.references.union(branch_output.references);
|
for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() {
|
||||||
|
canonicalize_case_branch(loc_expr);
|
||||||
// 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
|
// TODO exists check
|
||||||
// any of the new idents it defined were unused. If any were, report it.
|
constraints.push(constrain_case_branch(
|
||||||
for (ident, (symbol, region)) in assigned_idents {
|
rtv,
|
||||||
if !output.references.has_local(&symbol) {
|
branch,
|
||||||
let loc_ident = Located {
|
PFromContext(region, PCaseMatch(index), ptrnType),
|
||||||
region: region,
|
FromAnnotation(name, arity, TypedCaseBranch(index), tipe),
|
||||||
value: ident.clone(),
|
));
|
||||||
};
|
|
||||||
|
|
||||||
env.problem(Problem::UnusedAssignment(loc_ident));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
can_branches.push((loc_can_pattern, can_expr));
|
_ => {
|
||||||
|
let branch_var = subs.mk_flex_var();
|
||||||
|
let branch_type = Variable(branch_var);
|
||||||
|
|
||||||
|
constraints.push(constrain_case_branch(
|
||||||
|
rtv,
|
||||||
|
branch,
|
||||||
|
PFromContext(region, PCaseMatch(index), ptrnType),
|
||||||
|
FromContext(region, CaseBranch(index), branch_type),
|
||||||
|
));
|
||||||
|
|
||||||
|
// TODO exists check
|
||||||
|
constraints.push(Eq(Case, region, branch_type, expected));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// One of the branches should have flipped this, so this should only happen
|
// One of the branches should have flipped this, so this should only happen
|
||||||
|
@ -608,8 +598,10 @@ fn canonicalize_expr(
|
||||||
output.tail_call = None;
|
output.tail_call = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let var = panic!("TODO create a var for case and make constraints");
|
||||||
|
|
||||||
// Incorporate all three expressions into a combined Output value.
|
// Incorporate all three expressions into a combined Output value.
|
||||||
let expr = Case(Box::new(can_cond), can_branches);
|
let expr = Case(var, Box::new(can_cond), can_branches);
|
||||||
|
|
||||||
(expr, output)
|
(expr, output)
|
||||||
}
|
}
|
||||||
|
@ -677,6 +669,78 @@ fn canonicalize_expr(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonicalize_case_branch(rtv, pattern: ast::Pattern<'a>, loc_expr: Located<ast::Expr<'a>>, pExpect: PExpected<Type>, bExpect: Expected<Type>) {
|
||||||
|
// 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, &mut shadowable_idents);
|
||||||
|
|
||||||
|
Pattern.State(headers, pvars, rev_cons) = Pattern.add(pattern, pExpect, Pattern.emptyState);
|
||||||
|
|
||||||
|
Constraint::Let([], pvars, headers, (And(rev_cons.reverse())))
|
||||||
|
<$> constrain rtv expr bExpect
|
||||||
|
|
||||||
|
|
||||||
|
let loc_can_pattern = canonicalize_pattern(
|
||||||
|
env,
|
||||||
|
subs,
|
||||||
|
&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_expr(
|
||||||
|
env,
|
||||||
|
subs,
|
||||||
|
&mut scope,
|
||||||
|
region,
|
||||||
|
&loc_expr.value,
|
||||||
|
expected.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
value: ident.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
env.problem(Problem::UnusedAssignment(loc_ident));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
can_branches.push((loc_can_pattern, can_expr));
|
||||||
|
}
|
||||||
|
|
||||||
fn union_pairs<'a, K, V, I>(mut map: ImMap<K, V>, pairs: I) -> ImMap<K, V>
|
fn union_pairs<'a, K, V, I>(mut map: ImMap<K, V>, pairs: I) -> ImMap<K, V>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a (K, V)>,
|
I: Iterator<Item = &'a (K, V)>,
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
|
use collections::ImMap;
|
||||||
use region::Region;
|
use region::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, LetConstraint, Reason};
|
||||||
|
|
||||||
|
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
||||||
|
Constraint::Let(Box::new(LetConstraint {
|
||||||
|
rigid_vars: Vec::new(),
|
||||||
|
flex_vars,
|
||||||
|
assignment_types: ImMap::default(),
|
||||||
|
assignments_constraint: constraint,
|
||||||
|
ret_constraint: Constraint::True,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
|
|
|
@ -111,6 +111,7 @@ pub struct OperatorType {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Expected<T> {
|
pub enum Expected<T> {
|
||||||
NoExpectation(T),
|
NoExpectation(T),
|
||||||
|
FromAnnotation(String, usize, AnnotationSource, T),
|
||||||
ForReason(Reason, T, Region),
|
ForReason(Reason, T, Region),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +124,13 @@ impl<T> Expected<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum AnnotationSource {
|
||||||
|
TypedIfBranch(usize /* index */),
|
||||||
|
TypedCaseBranch(usize /* index */),
|
||||||
|
TypedBody,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Reason {
|
pub enum Reason {
|
||||||
AnonymousFnArg(u8 /* arg index */),
|
AnonymousFnArg(u8 /* arg index */),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue