mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge pull request #2 from rtfeldman/fix-identity
Generate type var names when pretty printing
This commit is contained in:
commit
1e8423a368
9 changed files with 470 additions and 255 deletions
235
src/can/mod.rs
235
src/can/mod.rs
|
@ -4,6 +4,7 @@ use self::num::{
|
|||
finish_parsing_bin, finish_parsing_float, finish_parsing_hex, finish_parsing_int,
|
||||
finish_parsing_oct, float_expr_from_result, int_expr_from_result,
|
||||
};
|
||||
use self::pattern::PatternState;
|
||||
use self::pattern::PatternType::*;
|
||||
use self::pattern::{canonicalize_pattern, Pattern};
|
||||
use self::problem::Problem;
|
||||
|
@ -235,7 +236,9 @@ fn canonicalize_expr(
|
|||
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(loc_args.len() as u8);
|
||||
let fn_reason = Reason::AnonymousFnCall {
|
||||
arity: loc_args.len() as u8,
|
||||
};
|
||||
|
||||
// Canonicalize the function expression and its arguments
|
||||
let (fn_expr, mut output) = canonicalize_expr(
|
||||
|
@ -269,7 +272,9 @@ fn canonicalize_expr(
|
|||
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 reason = Reason::AnonymousFnArg {
|
||||
arg_index: index as u8,
|
||||
};
|
||||
let expected_arg = ForReason(reason, arg_type.clone(), region);
|
||||
let (arg_expr, arg_out) = canonicalize_expr(
|
||||
rigids,
|
||||
|
@ -471,12 +476,13 @@ fn canonicalize_expr(
|
|||
scope.idents = union_pairs(scope.idents, arg_idents.iter());
|
||||
|
||||
let mut state = PatternState {
|
||||
assignment_types: ImMap::default(),
|
||||
headers: ImMap::default(),
|
||||
vars: Vec::with_capacity(loc_arg_patterns.len()),
|
||||
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());
|
||||
let mut vars = Vec::with_capacity(state.vars.capacity());
|
||||
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
|
||||
|
||||
for loc_pattern in loc_arg_patterns.into_iter() {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
|
@ -485,10 +491,14 @@ fn canonicalize_expr(
|
|||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
||||
let pattern_var = subs.mk_flex_var();
|
||||
let pattern_expected = PExpected::NoExpectation(Type::Variable(pattern_var));
|
||||
let pattern_type = Type::Variable(pattern_var);
|
||||
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
|
||||
|
||||
let (can_arg, _state) = canonicalize_pattern(
|
||||
pattern_types.push(pattern_type);
|
||||
|
||||
let can_arg = canonicalize_pattern(
|
||||
env,
|
||||
&mut state,
|
||||
subs,
|
||||
&mut scope,
|
||||
FunctionArg,
|
||||
|
@ -498,10 +508,19 @@ fn canonicalize_expr(
|
|||
pattern_expected,
|
||||
);
|
||||
|
||||
vars.push(pattern_var);
|
||||
|
||||
can_args.push(can_arg);
|
||||
}
|
||||
|
||||
let body_type = NoExpectation(args.ret_type);
|
||||
let ret_var = subs.mk_flex_var();
|
||||
let ret_type = Type::Variable(ret_var);
|
||||
|
||||
state.vars.push(ret_var);
|
||||
|
||||
let fn_typ = Type::Function(pattern_types, Box::new(ret_type.clone()));
|
||||
|
||||
let body_type = NoExpectation(ret_type);
|
||||
let (loc_body_expr, mut output) = canonicalize_expr(
|
||||
rigids,
|
||||
env,
|
||||
|
@ -523,17 +542,17 @@ fn canonicalize_expr(
|
|||
let typ = Variable(var);
|
||||
|
||||
output.constraint = exists(
|
||||
args.vars,
|
||||
state.vars.clone(),
|
||||
And(vec![
|
||||
Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: state.vars,
|
||||
assignment_types: state.assignment_types,
|
||||
assignment_types: state.headers,
|
||||
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),
|
||||
Eq(fn_typ, NoExpectation(typ.clone()), region),
|
||||
// "the var we've stored for later is equal to the overall expected type"
|
||||
Eq(typ, expected, region),
|
||||
]),
|
||||
|
@ -566,7 +585,7 @@ fn canonicalize_expr(
|
|||
region,
|
||||
output.references.clone(),
|
||||
var,
|
||||
args.ret_var,
|
||||
ret_var,
|
||||
);
|
||||
|
||||
// Always return a function pointer, in case that's how the closure is being used (e.g. with Apply).
|
||||
|
@ -847,8 +866,15 @@ fn canonicalize_case_branch<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
let (loc_can_pattern, state) = canonicalize_pattern(
|
||||
let mut state = PatternState {
|
||||
headers: ImMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
let loc_can_pattern = canonicalize_pattern(
|
||||
env,
|
||||
&mut state,
|
||||
subs,
|
||||
&mut scope,
|
||||
CaseBranch,
|
||||
|
@ -1258,48 +1284,6 @@ impl Info {
|
|||
/// In elm/compiler this is called RTV - the "Rigid Type Variables" dictionary.
|
||||
// type BoundTypeVars = ImMap<Box<str>, Type>;
|
||||
|
||||
struct PatternState {
|
||||
assignment_types: ImMap<Symbol, Located<Type>>,
|
||||
vars: Vec<Variable>,
|
||||
constraints: Vec<Constraint>,
|
||||
}
|
||||
|
||||
impl PatternState {
|
||||
pub fn add_pattern(
|
||||
&mut self,
|
||||
scope: &Scope,
|
||||
loc_pattern: Located<ast::Pattern>,
|
||||
expected: Expected<Type>,
|
||||
) {
|
||||
let region = loc_pattern.region;
|
||||
|
||||
match loc_pattern.value {
|
||||
ast::Pattern::Identifier(name) => {
|
||||
let symbol = scope.symbol(&name);
|
||||
|
||||
self.add_to_assignment_types(region, symbol, expected)
|
||||
}
|
||||
ast::Pattern::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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_pattern_to_lookup_types<'a>(
|
||||
scope: &Scope,
|
||||
loc_pattern: Located<ast::Pattern<'a>>,
|
||||
|
@ -1367,6 +1351,10 @@ fn can_defs<'a>(
|
|||
let iter = defs.iter();
|
||||
|
||||
for loc_def in iter {
|
||||
// Make types for the body expr, even if we won't end up having a body.
|
||||
let expr_var = subs.mk_flex_var();
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
|
||||
// Each assignment gets to have all the idents in scope that are assigned in this
|
||||
// block. Order of assignments doesn't matter, thanks to referential transparency!
|
||||
let (opt_loc_pattern, (loc_can_expr, can_output)) = match loc_def.value {
|
||||
|
@ -1400,37 +1388,6 @@ fn can_defs<'a>(
|
|||
(None, (loc_expr, Output::new(True)))
|
||||
}
|
||||
Def::Body(ref loc_pattern, loc_expr) => {
|
||||
// Make types for the pattern and the body expr.
|
||||
let expr_var = subs.mk_flex_var();
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
let pattern_var = subs.mk_flex_var();
|
||||
let pattern_type = Type::Variable(pattern_var);
|
||||
|
||||
flex_info.vars.push(pattern_var);
|
||||
|
||||
let mut state = PatternState {
|
||||
assignment_types: ImMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
state.add_pattern(
|
||||
&scope,
|
||||
loc_pattern.clone(),
|
||||
NoExpectation(pattern_type.clone()),
|
||||
);
|
||||
let def_constraint = And(state.constraints);
|
||||
|
||||
// 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(
|
||||
&scope,
|
||||
loc_pattern.clone(),
|
||||
&mut flex_info.assignment_types,
|
||||
expr_type.clone(),
|
||||
);
|
||||
|
||||
let (loc_can_expr, output) = canonicalize_expr(
|
||||
rigids,
|
||||
env,
|
||||
|
@ -1438,17 +1395,9 @@ fn can_defs<'a>(
|
|||
&mut scope,
|
||||
loc_expr.region,
|
||||
&loc_expr.value,
|
||||
NoExpectation(expr_type),
|
||||
NoExpectation(expr_type.clone()),
|
||||
);
|
||||
|
||||
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: state.vars,
|
||||
assignment_types: state.assignment_types,
|
||||
assignments_constraint: def_constraint,
|
||||
ret_constraint: output.constraint.clone(),
|
||||
})));
|
||||
|
||||
(Some(loc_pattern), (loc_can_expr, output))
|
||||
}
|
||||
Def::CustomType(_, _) => {
|
||||
|
@ -1470,10 +1419,18 @@ fn can_defs<'a>(
|
|||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
||||
let pattern_var = subs.mk_flex_var();
|
||||
let pattern_expected = PExpected::NoExpectation(Type::Variable(pattern_var));
|
||||
let pattern_type = Type::Variable(pattern_var);
|
||||
let pattern_expected = PExpected::NoExpectation(pattern_type);
|
||||
|
||||
let (loc_can_pattern, _state) = canonicalize_pattern(
|
||||
let mut state = PatternState {
|
||||
headers: ImMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
let loc_can_pattern = canonicalize_pattern(
|
||||
env,
|
||||
&mut state,
|
||||
subs,
|
||||
&mut scope,
|
||||
Assignment,
|
||||
|
@ -1482,6 +1439,28 @@ fn can_defs<'a>(
|
|||
&mut shadowable_idents,
|
||||
pattern_expected,
|
||||
);
|
||||
|
||||
flex_info.vars.push(pattern_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(
|
||||
&scope,
|
||||
// TODO can we we avoid this clone?
|
||||
loc_pattern.clone(),
|
||||
&mut flex_info.assignment_types,
|
||||
expr_type.clone(),
|
||||
);
|
||||
|
||||
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: state.vars,
|
||||
assignment_types: state.headers,
|
||||
assignments_constraint: And(state.constraints),
|
||||
ret_constraint: can_output.constraint.clone(),
|
||||
})));
|
||||
|
||||
let mut renamed_closure_assignment: Option<&Symbol> = None;
|
||||
|
||||
// Give closures names (and tail-recursive status) where appropriate.
|
||||
|
@ -1679,7 +1658,12 @@ fn can_defs<'a>(
|
|||
// As a bonus, the topological sort also reveals any cycles between the assignments, allowing
|
||||
// us to give a CircularAssignment error.
|
||||
let successors = |symbol: &Symbol| -> ImSet<Symbol> {
|
||||
let (_, references) = refs_by_assignment.get(symbol).unwrap();
|
||||
let (_, references) = refs_by_assignment.get(symbol).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Could not find symbol {:?} in refs_by_assignment, which had: {:?}",
|
||||
symbol, refs_by_assignment
|
||||
)
|
||||
});
|
||||
|
||||
local_successors(&references, &env.procedures)
|
||||
};
|
||||
|
@ -1745,60 +1729,3 @@ fn can_defs<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Args {
|
||||
pub vars: Vec<Variable>,
|
||||
pub typ: Type,
|
||||
pub ret_type: Type,
|
||||
pub ret_var: Variable,
|
||||
}
|
||||
|
||||
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, 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,
|
||||
ret_var,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ pub enum PatternType {
|
|||
|
||||
pub fn canonicalize_pattern<'a>(
|
||||
env: &'a mut Env,
|
||||
state: &'a mut PatternState,
|
||||
subs: &mut Subs,
|
||||
scope: &mut Scope,
|
||||
pattern_type: PatternType,
|
||||
|
@ -54,7 +55,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
region: Region,
|
||||
shadowable_idents: &'a mut ImMap<Ident, (Symbol, Region)>,
|
||||
expected: PExpected<Type>,
|
||||
) -> (Located<Pattern>, State) {
|
||||
) -> Located<Pattern> {
|
||||
use self::PatternType::*;
|
||||
use can::ast::Pattern::*;
|
||||
|
||||
|
@ -245,6 +246,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
&SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
|
||||
return canonicalize_pattern(
|
||||
env,
|
||||
state,
|
||||
subs,
|
||||
scope,
|
||||
pattern_type,
|
||||
|
@ -257,21 +259,12 @@ pub fn canonicalize_pattern<'a>(
|
|||
_ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern),
|
||||
};
|
||||
|
||||
let mut state = State {
|
||||
headers: ImMap::default(),
|
||||
vars: Vec::new(),
|
||||
constraints: Vec::new(),
|
||||
};
|
||||
add_constraints(&pattern, &scope, region, expected, state);
|
||||
|
||||
add_constraints(&pattern, region, expected, &mut state);
|
||||
|
||||
(
|
||||
Located {
|
||||
region,
|
||||
value: can_pattern,
|
||||
},
|
||||
state,
|
||||
)
|
||||
Located {
|
||||
region,
|
||||
value: can_pattern,
|
||||
}
|
||||
}
|
||||
|
||||
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
|
||||
|
@ -284,7 +277,7 @@ fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region)
|
|||
|
||||
// CONSTRAIN
|
||||
|
||||
pub struct State {
|
||||
pub struct PatternState {
|
||||
pub headers: ImMap<Symbol, Located<Type>>,
|
||||
pub vars: Vec<Variable>,
|
||||
pub constraints: Vec<Constraint>,
|
||||
|
@ -292,9 +285,10 @@ pub struct State {
|
|||
|
||||
fn add_constraints<'a>(
|
||||
pattern: &'a ast::Pattern<'a>,
|
||||
scope: &'a Scope,
|
||||
region: Region,
|
||||
expected: PExpected<Type>,
|
||||
state: &'a mut State,
|
||||
state: &'a mut PatternState,
|
||||
) {
|
||||
use parse::ast::Pattern::*;
|
||||
|
||||
|
@ -304,7 +298,7 @@ fn add_constraints<'a>(
|
|||
}
|
||||
Identifier(name) => {
|
||||
state.headers.insert(
|
||||
Symbol::new("TODO pass home into add_constraints, or improve Symbol to not need it for idents in patterns", name),
|
||||
scope.symbol(name),
|
||||
Located {
|
||||
region,
|
||||
value: expected.get_type(),
|
||||
|
@ -339,7 +333,7 @@ fn add_constraints<'a>(
|
|||
}
|
||||
|
||||
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) => {
|
||||
add_constraints(pattern, region, expected, state)
|
||||
add_constraints(pattern, scope, region, expected, state)
|
||||
}
|
||||
|
||||
Variant(_, _) | Apply(_, _) | RecordDestructure(_) | EmptyRecordLiteral => {
|
||||
|
|
|
@ -37,8 +37,13 @@ pub enum UndoLog<D: SnapshotVecDelegate> {
|
|||
Other(D::Undo),
|
||||
}
|
||||
|
||||
/// A Vec where we have Debug overridden to render the indices like
|
||||
/// a hashmap, since we really care about those when debugging one of these.
|
||||
#[derive(Clone)]
|
||||
struct BackingVec<T>(Vec<T>);
|
||||
|
||||
pub struct SnapshotVec<D: SnapshotVecDelegate> {
|
||||
values: Vec<D::Value>,
|
||||
values: BackingVec<D::Value>,
|
||||
undo_log: Vec<UndoLog<D>>,
|
||||
num_open_snapshots: usize,
|
||||
}
|
||||
|
@ -78,13 +83,32 @@ pub trait SnapshotVecDelegate {
|
|||
impl<D: SnapshotVecDelegate> Default for SnapshotVec<D> {
|
||||
fn default() -> Self {
|
||||
SnapshotVec {
|
||||
values: Vec::new(),
|
||||
values: BackingVec(Vec::new()),
|
||||
undo_log: Vec::new(),
|
||||
num_open_snapshots: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for BackingVec<T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{{{{")?;
|
||||
|
||||
for (index, elem) in self.0.iter().enumerate() {
|
||||
write!(f, "\n {} => {:?},", index, elem)?;
|
||||
}
|
||||
|
||||
if !self.0.is_empty() {
|
||||
write!(f, "\n")?;
|
||||
}
|
||||
|
||||
write!(f, "}}}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
|
@ -92,7 +116,7 @@ impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|||
|
||||
pub fn with_capacity(c: usize) -> SnapshotVec<D> {
|
||||
SnapshotVec {
|
||||
values: Vec::with_capacity(c),
|
||||
values: BackingVec(Vec::with_capacity(c)),
|
||||
undo_log: Vec::new(),
|
||||
num_open_snapshots: 0,
|
||||
}
|
||||
|
@ -109,16 +133,16 @@ impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
self.values.0.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.values.len() == 0
|
||||
self.values.0.len() == 0
|
||||
}
|
||||
|
||||
pub fn push(&mut self, elem: D::Value) -> usize {
|
||||
let len = self.values.len();
|
||||
self.values.push(elem);
|
||||
let len = self.values.0.len();
|
||||
self.values.0.push(elem);
|
||||
|
||||
if self.in_snapshot() {
|
||||
self.undo_log.push(NewElem(len));
|
||||
|
@ -128,26 +152,26 @@ impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> &D::Value {
|
||||
&self.values[index]
|
||||
&self.values.0[index]
|
||||
}
|
||||
|
||||
/// Reserve space for new values, just like an ordinary vec.
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
// This is not affected by snapshots or anything.
|
||||
self.values.reserve(additional);
|
||||
self.values.0.reserve(additional);
|
||||
}
|
||||
|
||||
/// Returns a mutable pointer into the vec; whatever changes you make here cannot be undone
|
||||
/// automatically, so you should be sure call `record()` with some sort of suitable undo
|
||||
/// action.
|
||||
pub fn get_mut(&mut self, index: usize) -> &mut D::Value {
|
||||
&mut self.values[index]
|
||||
&mut self.values.0[index]
|
||||
}
|
||||
|
||||
/// Updates the element at the given index. The old value will saved (and perhaps restored) if
|
||||
/// a snapshot is active.
|
||||
pub fn set(&mut self, index: usize, new_elem: D::Value) {
|
||||
let old_elem = mem::replace(&mut self.values[index], new_elem);
|
||||
let old_elem = mem::replace(&mut self.values.0[index], new_elem);
|
||||
if self.in_snapshot() {
|
||||
self.undo_log.push(SetElem(index, old_elem));
|
||||
}
|
||||
|
@ -157,11 +181,11 @@ impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|||
/// otherwise equivalent to -- invoking `set` for each element.
|
||||
pub fn set_all(&mut self, mut new_elems: impl FnMut(usize) -> D::Value) {
|
||||
if !self.in_snapshot() {
|
||||
for (index, slot) in self.values.iter_mut().enumerate() {
|
||||
for (index, slot) in self.values.0.iter_mut().enumerate() {
|
||||
*slot = new_elems(index);
|
||||
}
|
||||
} else {
|
||||
for i in 0..self.values.len() {
|
||||
for i in 0..self.values.0.len() {
|
||||
self.set(i, new_elems(i));
|
||||
}
|
||||
}
|
||||
|
@ -173,16 +197,16 @@ impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|||
D::Value: Clone,
|
||||
{
|
||||
if self.in_snapshot() {
|
||||
let old_elem = self.values[index].clone();
|
||||
let old_elem = self.values.0[index].clone();
|
||||
self.undo_log.push(SetElem(index, old_elem));
|
||||
}
|
||||
op(&mut self.values[index]);
|
||||
op(&mut self.values.0[index]);
|
||||
}
|
||||
|
||||
pub fn start_snapshot(&mut self) -> Snapshot {
|
||||
self.num_open_snapshots += 1;
|
||||
Snapshot {
|
||||
value_count: self.values.len(),
|
||||
value_count: self.values.0.len(),
|
||||
undo_len: self.undo_log.len(),
|
||||
}
|
||||
}
|
||||
|
@ -205,16 +229,16 @@ impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|||
while self.undo_log.len() > snapshot.undo_len {
|
||||
match self.undo_log.pop().unwrap() {
|
||||
NewElem(i) => {
|
||||
self.values.pop();
|
||||
assert!(self.values.len() == i);
|
||||
self.values.0.pop();
|
||||
assert!(self.values.0.len() == i);
|
||||
}
|
||||
|
||||
SetElem(i, v) => {
|
||||
self.values[i] = v;
|
||||
self.values.0[i] = v;
|
||||
}
|
||||
|
||||
Other(u) => {
|
||||
D::reverse(&mut self.values, u);
|
||||
D::reverse(&mut self.values.0, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -244,13 +268,13 @@ impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|||
impl<D: SnapshotVecDelegate> ops::Deref for SnapshotVec<D> {
|
||||
type Target = [D::Value];
|
||||
fn deref(&self) -> &[D::Value] {
|
||||
&*self.values
|
||||
&*self.values.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: SnapshotVecDelegate> ops::DerefMut for SnapshotVec<D> {
|
||||
fn deref_mut(&mut self) -> &mut [D::Value] {
|
||||
&mut *self.values
|
||||
&mut *self.values.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,9 +296,9 @@ impl<D: SnapshotVecDelegate> Extend<D::Value> for SnapshotVec<D> {
|
|||
where
|
||||
T: IntoIterator<Item = D::Value>,
|
||||
{
|
||||
let initial_len = self.values.len();
|
||||
self.values.extend(iterable);
|
||||
let final_len = self.values.len();
|
||||
let initial_len = self.values.0.len();
|
||||
self.values.0.extend(iterable);
|
||||
let final_len = self.values.0.len();
|
||||
|
||||
if self.in_snapshot() {
|
||||
self.undo_log.extend((initial_len..final_len).map(NewElem));
|
||||
|
|
|
@ -1,30 +1,192 @@
|
|||
use collections::{MutMap, MutSet};
|
||||
use subs::{Content, FlatType, Subs, Variable};
|
||||
use types;
|
||||
|
||||
static WILDCARD: &str = "*";
|
||||
static EMPTY_RECORD: &str = "{}";
|
||||
static THE_LETTER_A: u32 = 'a' as u32;
|
||||
|
||||
/// Rerquirements for parentheses.
|
||||
///
|
||||
/// If we're inside a function (that is, this is either an argument or a return
|
||||
/// value), we may need to use parens. Examples:
|
||||
///
|
||||
/// a -> (* -> a)
|
||||
/// (* -> a) -> a
|
||||
///
|
||||
/// Separately, if we're inside a type parameter, we may need to use parens:
|
||||
///
|
||||
/// List Int
|
||||
/// List (List Int)
|
||||
///
|
||||
/// Otherwise, parens are unnecessary.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Parens {
|
||||
InFn,
|
||||
InTypeParam,
|
||||
Unnecessary,
|
||||
}
|
||||
|
||||
/// How many times a root variable appeared in Subs.
|
||||
///
|
||||
/// We only care about whether it was a single time or multiple times,
|
||||
/// because single appearances get a wildcard (*) and multiple times
|
||||
/// get a generated letter ("a" etc).
|
||||
enum Appearances {
|
||||
Single,
|
||||
Multiple,
|
||||
}
|
||||
|
||||
/// Generate names for all type variables, replacing FlexVar(None) with
|
||||
/// FlexVar(Some(name)) where appropriate. Example: for the identity
|
||||
/// function, generate a name of "a" for both its argument and return
|
||||
/// type variables.
|
||||
///
|
||||
/// It's important that we track insertion order, which is why
|
||||
/// names_needed is a Vec. We also want to count how many times a root
|
||||
/// appears, because we should only generate a name for it if it appears
|
||||
/// more than once.
|
||||
fn find_names_needed(
|
||||
variable: Variable,
|
||||
subs: &mut Subs,
|
||||
roots: &mut Vec<Variable>,
|
||||
root_appearances: &mut MutMap<Variable, Appearances>,
|
||||
names_taken: &mut MutSet<String>,
|
||||
) {
|
||||
use subs::Content::*;
|
||||
use subs::FlatType::*;
|
||||
|
||||
match subs.get(variable).content {
|
||||
FlexVar(None) => {
|
||||
let root = subs.get_root_key(variable);
|
||||
|
||||
// If this var is *not* its own root, then the
|
||||
// root var necessarily appears in multiple places.
|
||||
// We need a name for it!
|
||||
match root_appearances.get(&root) {
|
||||
Some(Appearances::Single) => {
|
||||
root_appearances.insert(root, Appearances::Multiple);
|
||||
}
|
||||
Some(Appearances::Multiple) => {
|
||||
// It's already multiple, so do nothing!
|
||||
}
|
||||
None => {
|
||||
roots.push(root);
|
||||
root_appearances.insert(root, Appearances::Single);
|
||||
}
|
||||
}
|
||||
}
|
||||
FlexVar(Some(_)) => {
|
||||
// This root already has a name. Nothing to do here!
|
||||
}
|
||||
Structure(Apply {
|
||||
module_name: _,
|
||||
name: _,
|
||||
args,
|
||||
}) => {
|
||||
for var in args {
|
||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
}
|
||||
Structure(Func(arg_vars, ret_var)) => {
|
||||
for var in arg_vars {
|
||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
|
||||
find_names_needed(ret_var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
RigidVar(name) => {
|
||||
// User-defined names are already taken.
|
||||
// We must not accidentally generate names that collide with them!
|
||||
names_taken.insert(name.to_string());
|
||||
}
|
||||
Error(_) | Structure(Erroneous(_)) | Structure(EmptyRecord) => {
|
||||
// Errors and empty records don't need names.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name_all_type_vars(variable: Variable, subs: &mut Subs) {
|
||||
let mut roots = Vec::new();
|
||||
let mut letters_used = 0;
|
||||
let mut appearances = MutMap::default();
|
||||
let mut taken = MutSet::default();
|
||||
|
||||
// Populate names_needed
|
||||
find_names_needed(variable, subs, &mut roots, &mut appearances, &mut taken);
|
||||
|
||||
for root in roots {
|
||||
match appearances.get(&root) {
|
||||
Some(Appearances::Multiple) => {
|
||||
letters_used = name_root(letters_used, root, subs, &taken);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn name_root(letters_used: u32, root: Variable, subs: &mut Subs, taken: &MutSet<String>) -> u32 {
|
||||
// TODO we should arena-allocate this String,
|
||||
// so all the strings in the entire pass only require ~1 allocation.
|
||||
let generated_name = if letters_used < 26 {
|
||||
// This should generate "a", then "b", etc.
|
||||
std::char::from_u32(THE_LETTER_A + letters_used)
|
||||
.unwrap_or_else(|| panic!("Tried to convert {} to a char", THE_LETTER_A + letters_used))
|
||||
.to_string()
|
||||
} else {
|
||||
panic!("TODO generate aa, ab, ac, ...");
|
||||
};
|
||||
|
||||
if taken.contains(&generated_name) {
|
||||
// If the generated name is already taken, try again.
|
||||
name_root(letters_used + 1, root, subs, taken)
|
||||
} else {
|
||||
set_root_name(root, &generated_name, subs);
|
||||
|
||||
letters_used + 1
|
||||
}
|
||||
}
|
||||
|
||||
fn set_root_name(root: Variable, name: &str, subs: &mut Subs) {
|
||||
use subs::Content::*;
|
||||
|
||||
let mut descriptor = subs.get(root);
|
||||
|
||||
match descriptor.content {
|
||||
FlexVar(None) => {
|
||||
descriptor.content = FlexVar(Some(name.into()));
|
||||
|
||||
// TODO is this necessary, or was mutating descriptor in place sufficient?
|
||||
subs.set(root, descriptor);
|
||||
}
|
||||
FlexVar(Some(_existing)) => {
|
||||
panic!("TODO FIXME - make sure the generated name does not clash with any bound vars! In other words, if the user decided to name a type variable 'a', make sure we don't generate 'a' to name a different one!");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_to_string(content: Content, subs: &mut Subs) -> String {
|
||||
let mut buf = String::new();
|
||||
|
||||
write_content(content, subs, &mut buf, false);
|
||||
write_content(content, subs, &mut buf, Parens::Unnecessary);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn write_content(content: Content, subs: &mut Subs, buf: &mut String, use_parens: bool) {
|
||||
fn write_content(content: Content, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
use subs::Content::*;
|
||||
|
||||
match content {
|
||||
FlexVar(Some(name)) => buf.push_str(&name),
|
||||
FlexVar(None) => buf.push_str(WILDCARD),
|
||||
RigidVar(name) => buf.push_str(&name),
|
||||
Structure(flat_type) => write_flat_type(flat_type, subs, buf, use_parens),
|
||||
Structure(flat_type) => write_flat_type(flat_type, subs, buf, parens),
|
||||
Error(_) => buf.push_str("<type mismatch>"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, use_parens: bool) {
|
||||
fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
use subs::FlatType::*;
|
||||
|
||||
match flat_type {
|
||||
|
@ -38,11 +200,10 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, use_p
|
|||
args,
|
||||
subs,
|
||||
buf,
|
||||
use_parens,
|
||||
parens,
|
||||
),
|
||||
EmptyRecord => buf.push_str(EMPTY_RECORD),
|
||||
Func(args, ret) => write_fn(args, ret, subs, buf, use_parens),
|
||||
BinOp(l_arg, r_arg, ret) => write_fn(vec![l_arg, r_arg], ret, subs, buf, use_parens),
|
||||
Func(args, ret) => write_fn(args, ret, subs, buf, parens),
|
||||
Erroneous(problem) => {
|
||||
buf.push_str(&format!("<Type Mismatch: {:?}>", problem));
|
||||
}
|
||||
|
@ -55,9 +216,9 @@ fn write_apply(
|
|||
args: Vec<Variable>,
|
||||
subs: &mut Subs,
|
||||
buf: &mut String,
|
||||
use_parens: bool,
|
||||
parens: Parens,
|
||||
) {
|
||||
let write_parens = use_parens && !args.is_empty();
|
||||
let write_parens = parens == Parens::InTypeParam && !args.is_empty();
|
||||
|
||||
// Hardcoded type aliases
|
||||
if module_name == "Str" && type_name == "Str" {
|
||||
|
@ -70,7 +231,7 @@ fn write_apply(
|
|||
let arg_content = subs.get(arg).content;
|
||||
let mut arg_param = String::new();
|
||||
|
||||
write_content(arg_content, subs, &mut arg_param, true);
|
||||
write_content(arg_content, subs, &mut arg_param, Parens::InTypeParam);
|
||||
|
||||
if arg_param == "Int.Integer" {
|
||||
buf.push_str("Int");
|
||||
|
@ -101,7 +262,7 @@ fn write_apply(
|
|||
.unwrap_or_else(|| panic!("List did not have any type parameters somehow."));
|
||||
let arg_content = subs.get(arg).content;
|
||||
|
||||
write_content(arg_content, subs, buf, true);
|
||||
write_content(arg_content, subs, buf, Parens::InTypeParam);
|
||||
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
|
@ -115,7 +276,7 @@ fn write_apply(
|
|||
|
||||
for arg in args {
|
||||
buf.push_str(" ");
|
||||
write_content(subs.get(arg).content, subs, buf, true);
|
||||
write_content(subs.get(arg).content, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
|
||||
if write_parens {
|
||||
|
@ -124,14 +285,9 @@ fn write_apply(
|
|||
}
|
||||
}
|
||||
|
||||
fn write_fn(
|
||||
args: Vec<Variable>,
|
||||
ret: Variable,
|
||||
subs: &mut Subs,
|
||||
buf: &mut String,
|
||||
use_parens: bool,
|
||||
) {
|
||||
fn write_fn(args: Vec<Variable>, ret: Variable, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
let mut needs_comma = false;
|
||||
let use_parens = parens != Parens::Unnecessary;
|
||||
|
||||
if use_parens {
|
||||
buf.push_str("(");
|
||||
|
@ -144,11 +300,11 @@ fn write_fn(
|
|||
needs_comma = true;
|
||||
}
|
||||
|
||||
write_content(subs.get(arg).content, subs, buf, false);
|
||||
write_content(subs.get(arg).content, subs, buf, Parens::InFn);
|
||||
}
|
||||
|
||||
buf.push_str(" -> ");
|
||||
write_content(subs.get(ret).content, subs, buf, false);
|
||||
write_content(subs.get(ret).content, subs, buf, Parens::InFn);
|
||||
|
||||
if use_parens {
|
||||
buf.push_str(")");
|
||||
|
|
|
@ -39,13 +39,13 @@ pub fn solve(env: &Env, subs: &mut Subs, constraint: &Constraint) {
|
|||
subs.union(actual, expected);
|
||||
}
|
||||
Let(let_con) => {
|
||||
match let_con.ret_constraint {
|
||||
match &let_con.ret_constraint {
|
||||
True => {
|
||||
// If the return expression is guaranteed to solve,
|
||||
// solve the assignments themselves and move on.
|
||||
solve(env, subs, &let_con.assignments_constraint)
|
||||
}
|
||||
ref ret_con => {
|
||||
ret_con => {
|
||||
// Solve the assignments' constraints first.
|
||||
solve(env, subs, &let_con.assignments_constraint);
|
||||
|
||||
|
|
14
src/subs.rs
14
src/subs.rs
|
@ -8,7 +8,7 @@ pub struct Subs {
|
|||
utable: UnificationTable<InPlace<Variable>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, PartialEq, Eq, Clone)]
|
||||
#[derive(Copy, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct Variable(u32);
|
||||
|
||||
impl Variable {
|
||||
|
@ -67,6 +67,10 @@ impl Subs {
|
|||
self.utable.probe_value(key)
|
||||
}
|
||||
|
||||
pub fn get_root_key(&mut self, key: Variable) -> Variable {
|
||||
self.utable.get_root_key(key)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, key: Variable, r_value: Descriptor) {
|
||||
let l_key = self.utable.get_root_key(key);
|
||||
let unified = unify::unify_var_val(self, l_key, &r_value);
|
||||
|
@ -131,8 +135,13 @@ impl From<Content> for Descriptor {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Content {
|
||||
/// A type variable which the user did not name in an annotation,
|
||||
///
|
||||
/// When we auto-generate a type var name, e.g. the "a" in (a -> a), we
|
||||
/// change the Option in here from None to Some.
|
||||
FlexVar(Option<Box<str>> /* name - e.g. in pattern matching */),
|
||||
RigidVar(String /* name given in a user-written annotation */),
|
||||
/// name given in a user-written annotation
|
||||
RigidVar(Box<str>),
|
||||
Structure(FlatType),
|
||||
Error(Problem),
|
||||
}
|
||||
|
@ -145,7 +154,6 @@ pub enum FlatType {
|
|||
args: Vec<Variable>,
|
||||
},
|
||||
Func(Vec<Variable>, Variable),
|
||||
BinOp(Variable, Variable, Variable),
|
||||
Erroneous(Problem),
|
||||
EmptyRecord,
|
||||
}
|
||||
|
|
|
@ -184,9 +184,9 @@ pub enum AnnotationSource {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Reason {
|
||||
AnonymousFnArg(u8 /* arg index */),
|
||||
AnonymousFnArg { arg_index: u8 },
|
||||
NamedFnArg(String /* function name */, u8 /* arg index */),
|
||||
AnonymousFnCall(u8 /* arity */),
|
||||
AnonymousFnCall { arity: u8 },
|
||||
NamedFnCall(String /* function name */, u8 /* arity */),
|
||||
BinOpArg(BinOp, ArgSide),
|
||||
BinOpRet(BinOp),
|
||||
|
|
10
src/unify.rs
10
src/unify.rs
|
@ -90,14 +90,6 @@ fn unify_flat_type(subs: &mut Subs, left: &FlatType, right: &FlatType) -> Descri
|
|||
from_content(Error(Problem::MissingArguments))
|
||||
}
|
||||
}
|
||||
(BinOp(l_l_arg, l_r_arg, l_ret), BinOp(r_l_arg, r_r_arg, r_ret)) => {
|
||||
let l_arg = union_vars(subs, *l_l_arg, *r_l_arg);
|
||||
let r_arg = union_vars(subs, *l_r_arg, *r_r_arg);
|
||||
let ret = union_vars(subs, *l_ret, *r_ret);
|
||||
let flat_type = BinOp(l_arg, r_arg, ret);
|
||||
|
||||
from_content(Structure(flat_type))
|
||||
}
|
||||
_ => from_content(Error(Problem::GenericMismatch)),
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +129,7 @@ fn unify_rigid(name: &str, other: &Content) -> Descriptor {
|
|||
match other {
|
||||
FlexVar(_) => {
|
||||
// If the other is flex, rigid wins!
|
||||
from_content(RigidVar(name.to_string()))
|
||||
from_content(RigidVar(name.into()))
|
||||
}
|
||||
RigidVar(_) | Structure(_) => {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
|
|
|
@ -12,7 +12,7 @@ mod helpers;
|
|||
mod test_infer {
|
||||
use helpers::can_expr;
|
||||
use roc::infer::infer_expr;
|
||||
use roc::pretty_print_types::content_to_string;
|
||||
use roc::pretty_print_types::{content_to_string, name_all_type_vars};
|
||||
|
||||
// HELPERS
|
||||
|
||||
|
@ -20,6 +20,9 @@ mod test_infer {
|
|||
let (_, output, _, procedures, mut subs, variable) = can_expr(src);
|
||||
|
||||
let content = infer_expr(&mut subs, procedures, &output.constraint, variable);
|
||||
|
||||
name_all_type_vars(variable, &mut subs);
|
||||
|
||||
let actual_str = content_to_string(content, &mut subs);
|
||||
|
||||
assert_eq!(actual_str, expected.to_string());
|
||||
|
@ -486,7 +489,6 @@ mod test_infer {
|
|||
}
|
||||
|
||||
// TODO type annotations
|
||||
// TODO fix identity inference
|
||||
// TODO BoundTypeVariables
|
||||
// TODO conditionals
|
||||
|
||||
|
@ -530,25 +532,137 @@ mod test_infer {
|
|||
"Int",
|
||||
);
|
||||
}
|
||||
// #[test]
|
||||
// fn identity() {
|
||||
// infer_eq(
|
||||
// indoc!(r#"
|
||||
// \val -> val
|
||||
// "#),
|
||||
// "a -> a"
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn always_function() {
|
||||
// infer_eq(
|
||||
// indoc!(r#"
|
||||
// \val -> \_ -> val
|
||||
// "#),
|
||||
// "a -> (* -> a)"
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn anonymous_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
(\a -> a) 3.14
|
||||
"#
|
||||
),
|
||||
"Float",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_of_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
(\val -> val) (\val -> val)
|
||||
"#
|
||||
),
|
||||
"a -> a",
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// TODO FIXME this should work, but instead causes a stack overflow!
|
||||
// fn recursive_identity() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// identity = \val -> val
|
||||
|
||||
// identity identity
|
||||
// "#
|
||||
// ),
|
||||
// "a -> a",
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn identity_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\val -> val
|
||||
"#
|
||||
),
|
||||
"a -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_apply() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
apply = \f x -> f x
|
||||
identity = \a -> a
|
||||
|
||||
apply identity 5
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\f x -> f x
|
||||
"#
|
||||
),
|
||||
"(a -> b), a -> b",
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// TODO FIXME this should pass, but instead fails to canonicalize
|
||||
// fn use_flip() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// flip = \f -> (\a b -> f b a)
|
||||
// neverendingInt = \f int -> f int
|
||||
// x = neverendingInt (\a -> a) 5
|
||||
|
||||
// flip neverendingInt
|
||||
// "#
|
||||
// ),
|
||||
// "(Int, (a -> a)) -> Int",
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn flip_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\f -> (\a b -> f b a),
|
||||
"#
|
||||
),
|
||||
"(a, b -> c) -> (b, a -> c)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn always_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\val -> \_ -> val
|
||||
"#
|
||||
),
|
||||
"a -> (* -> a)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_a_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\f -> f {}
|
||||
"#
|
||||
),
|
||||
"({} -> a) -> a",
|
||||
);
|
||||
}
|
||||
|
||||
// OPERATORS
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue