Constrain canonical exprs and modules

This commit is contained in:
Richard Feldman 2019-12-29 19:48:06 -08:00
parent be2d20162c
commit ffd3cc4211
17 changed files with 1130 additions and 431 deletions

View file

@ -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),

View file

@ -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,

View file

@ -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)
}

View file

@ -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
View 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
View 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]),
})),
}))
}

View file

@ -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
View 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
View 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");
}
}
}

View file

@ -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,

View file

@ -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);
}

View file

@ -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(),

View file

@ -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,
);

View file

@ -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)

View file

@ -908,7 +908,7 @@ mod test_infer {
infer_eq(
indoc!(
r#"
foo : Int -> Bool
foo: Int -> Bool
foo 2
"#

View file

@ -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);

View file

@ -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",
);