mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge remote-tracking branch 'origin/trunk' into improve-errors
This commit is contained in:
commit
2f61455061
43 changed files with 3015 additions and 1675 deletions
|
@ -8,7 +8,7 @@ Bool : [ False, True ]
|
||||||
## Returns #False when given #True, and vice versa.
|
## Returns #False when given #True, and vice versa.
|
||||||
not : Bool -> Bool
|
not : Bool -> Bool
|
||||||
not = \bool ->
|
not = \bool ->
|
||||||
case bool when
|
when bool is
|
||||||
False -> True
|
False -> True
|
||||||
True -> False
|
True -> False
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ interface Float
|
||||||
|
|
||||||
#round : Float -> Int
|
#round : Float -> Int
|
||||||
round = \num ->
|
round = \num ->
|
||||||
case num when
|
when num is
|
||||||
0.0 -> 0
|
0.0 -> 0
|
||||||
_ -> 1
|
_ -> 1
|
||||||
|
|
||||||
|
@ -100,17 +100,17 @@ round = \num ->
|
||||||
##
|
##
|
||||||
## `a / b` is shorthand for `Float.div a b`.
|
## `a / b` is shorthand for `Float.div a b`.
|
||||||
##
|
##
|
||||||
## >>>>> 5.0 / 7.0
|
## >>> 5.0 / 7.0
|
||||||
##
|
##
|
||||||
## >>>>> Float.div 5 7
|
## >>> Float.div 5 7
|
||||||
##
|
##
|
||||||
## `Float.div` can be convenient in pipelines.
|
## `Float.div` can be convenient in pipelines.
|
||||||
##
|
##
|
||||||
## >>>>> Float.pi
|
## >>> Float.pi
|
||||||
## >>>>> |> Float.div 2.0
|
## >>> |> Float.div 2.0
|
||||||
#div : Float, Float -> Result Float DivByZero
|
#div : Float, Float -> Result Float DivByZero
|
||||||
div = \numerator, denominator ->
|
div = \numerator, denominator ->
|
||||||
case numerator when
|
when numerator is
|
||||||
0.0 -> 0.0 # TODO return Result!
|
0.0 -> 0.0 # TODO return Result!
|
||||||
_ -> denominator
|
_ -> denominator
|
||||||
|
|
||||||
|
@ -123,14 +123,14 @@ div = \numerator, denominator ->
|
||||||
##
|
##
|
||||||
## `a % b` is shorthand for `Float.mod a b`.
|
## `a % b` is shorthand for `Float.mod a b`.
|
||||||
##
|
##
|
||||||
## >>>>> 5.0 % 7.0
|
## >>> 5.0 % 7.0
|
||||||
##
|
##
|
||||||
## >>>>> Float.mod 5 7
|
## >>> Float.mod 5 7
|
||||||
##
|
##
|
||||||
## `Float.mod` can be convenient in pipelines.
|
## `Float.mod` can be convenient in pipelines.
|
||||||
##
|
##
|
||||||
## >>>>> Float.pi
|
## >>> Float.pi
|
||||||
## >>>>> |> Float.mod 2.0
|
## >>> |> Float.mod 2.0
|
||||||
#mod : Float, Float -> Result Float DivByZero
|
#mod : Float, Float -> Result Float DivByZero
|
||||||
|
|
||||||
## Return the reciprocal of the #Float.
|
## Return the reciprocal of the #Float.
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::can::ident::Lowercase;
|
||||||
use crate::collections::{ImMap, SendMap};
|
use crate::collections::{ImMap, SendMap};
|
||||||
use crate::parse::ast::{AssignedField, TypeAnnotation};
|
use crate::parse::ast::{AssignedField, TypeAnnotation};
|
||||||
use crate::subs::{VarStore, Variable};
|
use crate::subs::{VarStore, Variable};
|
||||||
|
use crate::types::RecordFieldLabel;
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
|
|
||||||
pub fn canonicalize_annotation(
|
pub fn canonicalize_annotation(
|
||||||
|
@ -78,9 +79,8 @@ fn can_annotation_help(
|
||||||
can_assigned_field(&field.value, var_store, rigids, &mut field_types);
|
can_assigned_field(&field.value, var_store, rigids, &mut field_types);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fragment variable is free in this case
|
// This is a closed record, so the fragment must be {}
|
||||||
let fragment_var = var_store.fresh();
|
let fragment_type = Type::EmptyRec;
|
||||||
let fragment_type = Type::Variable(fragment_var);
|
|
||||||
|
|
||||||
Type::Record(field_types, Box::new(fragment_type))
|
Type::Record(field_types, Box::new(fragment_type))
|
||||||
}
|
}
|
||||||
|
@ -108,14 +108,20 @@ fn can_assigned_field<'a>(
|
||||||
field: &AssignedField<'a, TypeAnnotation<'a>>,
|
field: &AssignedField<'a, TypeAnnotation<'a>>,
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
rigids: &mut ImMap<Lowercase, Variable>,
|
rigids: &mut ImMap<Lowercase, Variable>,
|
||||||
field_types: &mut SendMap<Lowercase, Type>,
|
field_types: &mut SendMap<RecordFieldLabel, Type>,
|
||||||
) {
|
) {
|
||||||
use crate::parse::ast::AssignedField::*;
|
use crate::parse::ast::AssignedField::*;
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
LabeledValue(field_name, _, annotation) => {
|
LabeledValue(field_name, _, annotation) => {
|
||||||
let field_type = can_annotation_help(&annotation.value, var_store, rigids);
|
let field_type = can_annotation_help(&annotation.value, var_store, rigids);
|
||||||
field_types.insert(Lowercase::from(field_name.value), field_type);
|
let label = RecordFieldLabel::Required(Lowercase::from(field_name.value));
|
||||||
|
field_types.insert(label, field_type);
|
||||||
|
}
|
||||||
|
OptionalField(field_name, _, annotation) => {
|
||||||
|
let field_type = can_annotation_help(&annotation.value, var_store, rigids);
|
||||||
|
let label = RecordFieldLabel::Optional(Lowercase::from(field_name.value));
|
||||||
|
field_types.insert(label, field_type);
|
||||||
}
|
}
|
||||||
LabelOnly(loc_field_name) => {
|
LabelOnly(loc_field_name) => {
|
||||||
// Interpret { a, b } as { a : a, b : b }
|
// Interpret { a, b } as { a : a, b : b }
|
||||||
|
@ -129,7 +135,8 @@ fn can_assigned_field<'a>(
|
||||||
Type::Variable(field_var)
|
Type::Variable(field_var)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
field_types.insert(field_name, field_type);
|
let label = RecordFieldLabel::Required(field_name);
|
||||||
|
field_types.insert(label, field_type);
|
||||||
}
|
}
|
||||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||||
can_assigned_field(nested, var_store, rigids, field_types)
|
can_assigned_field(nested, var_store, rigids, field_types)
|
||||||
|
|
533
src/can/def.rs
533
src/can/def.rs
|
@ -3,36 +3,35 @@ use crate::can::env::Env;
|
||||||
use crate::can::expr::Expr::{self, *};
|
use crate::can::expr::Expr::{self, *};
|
||||||
use crate::can::expr::{
|
use crate::can::expr::{
|
||||||
canonicalize_expr, local_successors, references_from_call, references_from_local, union_pairs,
|
canonicalize_expr, local_successors, references_from_call, references_from_local, union_pairs,
|
||||||
Output, Recursive, Rigids,
|
Output, Recursive,
|
||||||
};
|
};
|
||||||
use crate::can::ident::Lowercase;
|
use crate::can::ident::Lowercase;
|
||||||
|
use crate::can::pattern::remove_idents;
|
||||||
use crate::can::pattern::PatternType::*;
|
use crate::can::pattern::PatternType::*;
|
||||||
use crate::can::pattern::{canonicalize_pattern, idents_from_patterns, Pattern};
|
use crate::can::pattern::{canonicalize_pattern, idents_from_patterns, Pattern};
|
||||||
use crate::can::pattern::{remove_idents, PatternState};
|
|
||||||
use crate::can::problem::Problem;
|
use crate::can::problem::Problem;
|
||||||
use crate::can::problem::RuntimeError;
|
use crate::can::problem::RuntimeError;
|
||||||
use crate::can::problem::RuntimeError::*;
|
use crate::can::problem::RuntimeError::*;
|
||||||
use crate::can::procedure::References;
|
use crate::can::procedure::References;
|
||||||
use crate::can::scope::Scope;
|
use crate::can::scope::Scope;
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
use crate::collections::{ImSet, MutMap, MutSet, SendMap};
|
||||||
use crate::graph::{strongly_connected_component, topological_sort};
|
use crate::graph::{strongly_connected_component, topological_sort};
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::parse::ast;
|
use crate::parse::ast;
|
||||||
use crate::region::{Located, Region};
|
use crate::region::{Located, Region};
|
||||||
use crate::subs::{VarStore, Variable};
|
use crate::subs::{VarStore, Variable};
|
||||||
use crate::types::Constraint::{self, *};
|
|
||||||
use crate::types::Expected::{self, *};
|
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
use crate::types::{LetConstraint, PExpected};
|
|
||||||
use im_rc::Vector;
|
use im_rc::Vector;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Def {
|
pub struct Def {
|
||||||
pub pattern: Located<Pattern>,
|
pub loc_pattern: Located<Pattern>,
|
||||||
pub expr: Located<Expr>,
|
pub loc_expr: Located<Expr>,
|
||||||
pub variables_by_symbol: SendMap<Symbol, Variable>,
|
pub expr_var: Variable,
|
||||||
|
pub pattern_vars: SendMap<Symbol, Variable>,
|
||||||
|
pub annotation: Option<(Type, SendMap<Variable, Lowercase>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -42,32 +41,13 @@ pub struct CanDefs {
|
||||||
pub defined_idents: Vector<(Ident, (Symbol, Region))>,
|
pub defined_idents: Vector<(Ident, (Symbol, Region))>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)]
|
#[inline(always)]
|
||||||
pub fn canonicalize_defs<'a>(
|
pub fn canonicalize_defs<'a>(
|
||||||
rigids: &Rigids,
|
|
||||||
found_rigids: &mut SendMap<Variable, Lowercase>,
|
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
|
found_rigids: &mut SendMap<Variable, Lowercase>,
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
|
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
|
||||||
flex_info: &mut Info,
|
|
||||||
) -> CanDefs {
|
) -> CanDefs {
|
||||||
use crate::parse::ast::Def::*;
|
use crate::parse::ast::Def::*;
|
||||||
|
|
||||||
|
@ -89,100 +69,56 @@ pub fn canonicalize_defs<'a>(
|
||||||
|
|
||||||
while let Some(loc_def) = it.next() {
|
while let Some(loc_def) = it.next() {
|
||||||
match &loc_def.value {
|
match &loc_def.value {
|
||||||
Annotation(pattern, annotation) => match it.peek() {
|
Nested(Annotation(pattern, annotation)) | Annotation(pattern, annotation) => {
|
||||||
|
match it.peek() {
|
||||||
Some(Located {
|
Some(Located {
|
||||||
value: Body(body_pattern, body_expr),
|
value: Body(body_pattern, body_expr),
|
||||||
..
|
..
|
||||||
}) if &pattern == body_pattern => {
|
}) if pattern.value.equivalent(&body_pattern.value) => {
|
||||||
it.next();
|
it.next();
|
||||||
|
|
||||||
let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
|
let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
|
||||||
|
|
||||||
canonicalize_def(
|
canonicalize_def(
|
||||||
rigids,
|
|
||||||
found_rigids,
|
|
||||||
env,
|
env,
|
||||||
|
found_rigids,
|
||||||
Located {
|
Located {
|
||||||
region: loc_def.region,
|
region: loc_def.region,
|
||||||
value: &typed,
|
value: &typed,
|
||||||
},
|
},
|
||||||
scope,
|
scope,
|
||||||
&mut can_defs_by_symbol,
|
&mut can_defs_by_symbol,
|
||||||
flex_info,
|
|
||||||
var_store,
|
var_store,
|
||||||
&mut refs_by_symbol,
|
&mut refs_by_symbol,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
canonicalize_def(
|
canonicalize_def(
|
||||||
rigids,
|
|
||||||
found_rigids,
|
|
||||||
env,
|
env,
|
||||||
|
found_rigids,
|
||||||
Located {
|
Located {
|
||||||
region: loc_def.region,
|
region: loc_def.region,
|
||||||
value: &loc_def.value,
|
value: &loc_def.value,
|
||||||
},
|
},
|
||||||
scope,
|
scope,
|
||||||
&mut can_defs_by_symbol,
|
&mut can_defs_by_symbol,
|
||||||
flex_info,
|
|
||||||
var_store,
|
var_store,
|
||||||
&mut refs_by_symbol,
|
&mut refs_by_symbol,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
Nested(Annotation(pattern, annotation)) => match it.peek() {
|
|
||||||
Some(Located {
|
|
||||||
value: Body(body_pattern, body_expr),
|
|
||||||
..
|
|
||||||
}) if pattern.value == body_pattern.value => {
|
|
||||||
it.next();
|
|
||||||
|
|
||||||
let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
|
|
||||||
canonicalize_def(
|
|
||||||
rigids,
|
|
||||||
found_rigids,
|
|
||||||
env,
|
|
||||||
Located {
|
|
||||||
region: loc_def.region,
|
|
||||||
value: &typed,
|
|
||||||
},
|
|
||||||
scope,
|
|
||||||
&mut can_defs_by_symbol,
|
|
||||||
flex_info,
|
|
||||||
var_store,
|
|
||||||
&mut refs_by_symbol,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
canonicalize_def(
|
|
||||||
rigids,
|
|
||||||
found_rigids,
|
|
||||||
env,
|
|
||||||
Located {
|
|
||||||
region: loc_def.region,
|
|
||||||
value: &loc_def.value,
|
|
||||||
},
|
|
||||||
scope,
|
|
||||||
&mut can_defs_by_symbol,
|
|
||||||
flex_info,
|
|
||||||
var_store,
|
|
||||||
&mut refs_by_symbol,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
canonicalize_def(
|
canonicalize_def(
|
||||||
rigids,
|
|
||||||
found_rigids,
|
|
||||||
env,
|
env,
|
||||||
|
found_rigids,
|
||||||
Located {
|
Located {
|
||||||
region: loc_def.region,
|
region: loc_def.region,
|
||||||
value: &loc_def.value,
|
value: &loc_def.value,
|
||||||
},
|
},
|
||||||
scope,
|
scope,
|
||||||
&mut can_defs_by_symbol,
|
&mut can_defs_by_symbol,
|
||||||
flex_info,
|
|
||||||
var_store,
|
var_store,
|
||||||
&mut refs_by_symbol,
|
&mut refs_by_symbol,
|
||||||
);
|
);
|
||||||
|
@ -213,6 +149,7 @@ pub fn sort_can_defs(
|
||||||
let mut visited_symbols = MutSet::default();
|
let mut visited_symbols = MutSet::default();
|
||||||
|
|
||||||
let returned_locals = ImSet::clone(&output.references.locals);
|
let returned_locals = ImSet::clone(&output.references.locals);
|
||||||
|
|
||||||
// Start with the return expression's referenced locals. They're the only ones that count!
|
// Start with the return expression's referenced locals. They're the only ones that count!
|
||||||
//
|
//
|
||||||
// If I have two defs which reference each other, but neither of them is referenced
|
// If I have two defs which reference each other, but neither of them is referenced
|
||||||
|
@ -299,10 +236,12 @@ pub fn sort_can_defs(
|
||||||
let mut new_def = can_def.clone();
|
let mut new_def = can_def.clone();
|
||||||
|
|
||||||
// Determine recursivity of closures that are not tail-recursive
|
// 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);
|
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);
|
can_defs.push(new_def);
|
||||||
|
@ -342,7 +281,7 @@ pub fn sort_can_defs(
|
||||||
let mut regions = Vec::with_capacity(can_defs_by_symbol.len());
|
let mut regions = Vec::with_capacity(can_defs_by_symbol.len());
|
||||||
|
|
||||||
for def in can_defs_by_symbol.values() {
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -353,173 +292,143 @@ pub fn sort_can_defs(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
fn canonicalize_def_pattern(
|
||||||
fn canonicalize_def<'a>(
|
|
||||||
rigids: &Rigids,
|
|
||||||
found_rigids: &mut SendMap<Variable, Lowercase>,
|
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
|
loc_pattern: &Located<ast::Pattern>,
|
||||||
|
scope: &mut Scope,
|
||||||
|
var_store: &VarStore,
|
||||||
|
) -> 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.)
|
||||||
|
let mut shadowable_idents = scope.idents.clone();
|
||||||
|
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||||
|
|
||||||
|
canonicalize_pattern(
|
||||||
|
env,
|
||||||
|
var_store,
|
||||||
|
scope,
|
||||||
|
Assignment,
|
||||||
|
&loc_pattern.value,
|
||||||
|
loc_pattern.region,
|
||||||
|
&mut shadowable_idents,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonicalize_def<'a>(
|
||||||
|
env: &mut Env,
|
||||||
|
found_rigids: &mut SendMap<Variable, Lowercase>,
|
||||||
loc_def: Located<&'a ast::Def<'a>>,
|
loc_def: Located<&'a ast::Def<'a>>,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
||||||
flex_info: &mut Info,
|
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
refs_by_symbol: &mut MutMap<Symbol, (Located<Ident>, References)>,
|
refs_by_symbol: &mut MutMap<Symbol, (Located<Ident>, References)>,
|
||||||
) {
|
) {
|
||||||
use crate::parse::ast::Def::*;
|
use crate::parse::ast::Def::*;
|
||||||
use crate::types::AnnotationSource;
|
|
||||||
|
|
||||||
// Make types for the body expr, even if we won't end up having a body.
|
// Make types for the body expr, even if we won't end up having a body.
|
||||||
let expr_var = var_store.fresh();
|
let expr_var = var_store.fresh();
|
||||||
let expr_type = Type::Variable(expr_var);
|
let mut vars_by_symbol = SendMap::default();
|
||||||
let mut variables_by_symbol = SendMap::default();
|
|
||||||
|
|
||||||
// Each def gets to have all the idents in scope that are defined in this
|
// 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!
|
// block. Order of defs doesn't matter, thanks to referential transparency!
|
||||||
let (_opt_loc_pattern, (_loc_can_expr, _can_output)) = match loc_def.value {
|
match loc_def.value {
|
||||||
Annotation(_loc_pattern, loc_annotation) => {
|
Annotation(loc_pattern, loc_annotation) => {
|
||||||
// TODO implement this:
|
let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store);
|
||||||
//
|
|
||||||
// Is this a standalone annotation, or is it annotating the
|
|
||||||
// next def? This is annotating the next def iff:
|
|
||||||
//
|
|
||||||
// 1. There is a next def.
|
|
||||||
// 2. It is a Def::Body.
|
|
||||||
// 3. Its Pattern contains at least one SpaceBefore.
|
|
||||||
// 4. The count of all Newlines across all of its SpaceBefores is exactly 1.
|
|
||||||
//
|
|
||||||
// This tells us we're an annotation in the following scenario:
|
|
||||||
//
|
|
||||||
// foo : String
|
|
||||||
// foo = "blah"
|
|
||||||
//
|
|
||||||
// Knowing that, we then need to incorporate the annotation's type constraints
|
|
||||||
// into the next def's. To do this, we extract the next def from the iterator
|
|
||||||
// immediately, then canonicalize it to get its Variable, then use that
|
|
||||||
// Variable to generate the extra constraints.
|
|
||||||
|
|
||||||
|
// 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 (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();
|
||||||
|
|
||||||
|
// Fabricate a body for this annotation, that will error at runtime
|
||||||
let value = Expr::RuntimeError(NoImplementation);
|
let value = Expr::RuntimeError(NoImplementation);
|
||||||
let loc_expr = Located {
|
let loc_can_expr = if arity > 0 {
|
||||||
|
Located {
|
||||||
|
value,
|
||||||
|
region: loc_annotation.region,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let symbol = scope.gen_unique_symbol();
|
||||||
|
|
||||||
|
// generate a fake pattern for each argument. this makes signatures
|
||||||
|
// that are functions only crash when they are applied.
|
||||||
|
let mut underscores = Vec::with_capacity(arity);
|
||||||
|
for _ in 0..arity {
|
||||||
|
let underscore: Located<Pattern> = Located {
|
||||||
|
value: Pattern::Underscore,
|
||||||
|
region: Region::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
underscores.push((var_store.fresh(), underscore));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body_expr = Located {
|
||||||
value,
|
value,
|
||||||
region: loc_annotation.region,
|
region: loc_annotation.region,
|
||||||
};
|
};
|
||||||
|
|
||||||
(None, (loc_expr, Output::default()))
|
let body = Box::new((body_expr, var_store.fresh()));
|
||||||
|
|
||||||
|
Located {
|
||||||
|
value: Closure(symbol, Recursive::NotRecursive, underscores, body),
|
||||||
|
region: loc_annotation.region,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, (symbol, _)) in idents_from_patterns(std::iter::once(loc_pattern), &scope) {
|
||||||
|
can_defs_by_symbol.insert(
|
||||||
|
symbol,
|
||||||
|
Def {
|
||||||
|
expr_var,
|
||||||
|
// TODO try to remove this .clone()!
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
pattern_vars: im::HashMap::clone(&vars_by_symbol),
|
||||||
|
annotation: Some((can_annotation.clone(), found_rigids.clone())),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedDef(loc_pattern, loc_annotation, loc_expr) => {
|
TypedDef(loc_pattern, loc_annotation, loc_expr) => {
|
||||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
let (seen_rigids, can_annotation) =
|
||||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
canonicalize_annotation(&loc_annotation.value, var_store);
|
||||||
let mut shadowable_idents = scope.idents.clone();
|
|
||||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
|
||||||
|
|
||||||
let pattern_var = var_store.fresh();
|
// union seen rigids with already found ones
|
||||||
let pattern_type = Type::Variable(pattern_var);
|
for (k, v) in seen_rigids {
|
||||||
let pattern_expected = PExpected::NoExpectation(pattern_type);
|
found_rigids.insert(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
let mut state = PatternState {
|
let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store);
|
||||||
headers: SendMap::default(),
|
|
||||||
vars: Vec::with_capacity(1),
|
|
||||||
constraints: Vec::with_capacity(1),
|
|
||||||
};
|
|
||||||
|
|
||||||
let loc_can_pattern = canonicalize_pattern(
|
|
||||||
env,
|
|
||||||
&mut state,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
Assignment,
|
|
||||||
&loc_pattern.value,
|
|
||||||
loc_pattern.region,
|
|
||||||
&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 defined!
|
|
||||||
add_pattern_to_lookup_types(
|
|
||||||
&scope,
|
|
||||||
&loc_pattern,
|
|
||||||
&mut flex_info.def_types,
|
|
||||||
expr_type.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// bookkeeping for tail-call detection. If we're assigning to an
|
// bookkeeping for tail-call detection. If we're assigning to an
|
||||||
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
||||||
let outer_identifier = env.tailcallable_symbol.clone();
|
let outer_identifier = env.tailcallable_symbol.clone();
|
||||||
|
|
||||||
// TODO ensure TypedDef has a pattern identifier?
|
if let (&ast::Pattern::Identifier(_), &Pattern::Identifier(ref defined_symbol)) =
|
||||||
// e.g. in elm, you can't type
|
(&loc_pattern.value, &loc_can_pattern.value)
|
||||||
//
|
|
||||||
// (foo, bar) : (Int, Bool)
|
|
||||||
// implicitly, that is the case here too
|
|
||||||
let mut fname = "invalid name".to_string();
|
|
||||||
|
|
||||||
if let (
|
|
||||||
&ast::Pattern::Identifier(ref name),
|
|
||||||
&Pattern::Identifier(_, ref defined_symbol),
|
|
||||||
) = (&loc_pattern.value, &loc_can_pattern.value)
|
|
||||||
{
|
{
|
||||||
fname = (*name).to_string();
|
|
||||||
env.tailcallable_symbol = Some(defined_symbol.clone());
|
env.tailcallable_symbol = Some(defined_symbol.clone());
|
||||||
variables_by_symbol.insert(defined_symbol.clone(), expr_var);
|
vars_by_symbol.insert(defined_symbol.clone(), expr_var);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (ftv_sendmap, can_annotation) =
|
let (mut loc_can_expr, can_output) =
|
||||||
canonicalize_annotation(&loc_annotation.value, var_store);
|
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
||||||
|
|
||||||
let mut ftv: Rigids = ImMap::default();
|
|
||||||
|
|
||||||
for (var, name) in ftv_sendmap.clone() {
|
|
||||||
ftv.insert(name.into(), Type::Variable(var));
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the known type variables (TODO can clone be prevented?)
|
|
||||||
let new_rigids = ftv.difference(rigids.clone());
|
|
||||||
|
|
||||||
let new_rtv = rigids.clone().union(new_rigids);
|
|
||||||
|
|
||||||
let arity = if let crate::types::Type::Function(args, _) = &can_annotation {
|
|
||||||
args.len()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let annotation_expected =
|
|
||||||
FromAnnotation(fname, arity, AnnotationSource::TypedBody, can_annotation);
|
|
||||||
|
|
||||||
let (mut loc_can_expr, mut can_output, ret_constraint) = canonicalize_expr(
|
|
||||||
// rigids,
|
|
||||||
&new_rtv,
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
loc_expr.region,
|
|
||||||
&loc_expr.value,
|
|
||||||
annotation_expected.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
*found_rigids = found_rigids.clone().union(ftv_sendmap.clone());
|
|
||||||
can_output.rigids = ftv_sendmap;
|
|
||||||
|
|
||||||
// ensure expected type unifies with annotated type
|
|
||||||
state
|
|
||||||
.constraints
|
|
||||||
.push(Eq(expr_type, annotation_expected, loc_def.region));
|
|
||||||
|
|
||||||
// reset the tailcallable_symbol
|
// reset the tailcallable_symbol
|
||||||
env.tailcallable_symbol = outer_identifier;
|
env.tailcallable_symbol = outer_identifier;
|
||||||
|
|
||||||
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
|
||||||
rigid_vars: Vec::new(),
|
|
||||||
flex_vars: state.vars,
|
|
||||||
def_types: state.headers,
|
|
||||||
defs_constraint: And(state.constraints),
|
|
||||||
ret_constraint,
|
|
||||||
})));
|
|
||||||
|
|
||||||
// see below: a closure needs a fresh References!
|
// see below: a closure needs a fresh References!
|
||||||
let mut is_closure = false;
|
let mut is_closure = false;
|
||||||
|
|
||||||
|
@ -531,7 +440,7 @@ fn canonicalize_def<'a>(
|
||||||
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
||||||
if let (
|
if let (
|
||||||
&ast::Pattern::Identifier(ref _name),
|
&ast::Pattern::Identifier(ref _name),
|
||||||
&Pattern::Identifier(_, ref defined_symbol),
|
&Pattern::Identifier(ref defined_symbol),
|
||||||
&Closure(ref symbol, _, ref arguments, ref body),
|
&Closure(ref symbol, _, ref arguments, ref body),
|
||||||
) = (
|
) = (
|
||||||
&loc_pattern.value,
|
&loc_pattern.value,
|
||||||
|
@ -578,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
|
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
||||||
// which defined names reference each other.
|
// which defined names reference each other.
|
||||||
for (ident, (symbol, region)) in
|
for (ident, (symbol, region)) in
|
||||||
|
@ -606,68 +513,27 @@ fn canonicalize_def<'a>(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
defined_symbols.push(symbol.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
for symbol in defined_symbols {
|
|
||||||
can_defs_by_symbol.insert(
|
can_defs_by_symbol.insert(
|
||||||
symbol,
|
symbol,
|
||||||
Def {
|
Def {
|
||||||
|
expr_var,
|
||||||
// TODO try to remove this .clone()!
|
// TODO try to remove this .clone()!
|
||||||
pattern: loc_can_pattern.clone(),
|
loc_pattern: loc_can_pattern.clone(),
|
||||||
expr: Located {
|
loc_expr: Located {
|
||||||
region: loc_can_expr.region,
|
region: loc_can_expr.region,
|
||||||
// TODO try to remove this .clone()!
|
// TODO try to remove this .clone()!
|
||||||
value: loc_can_expr.value.clone(),
|
value: loc_can_expr.value.clone(),
|
||||||
},
|
},
|
||||||
variables_by_symbol: im::HashMap::clone(&variables_by_symbol),
|
pattern_vars: im::HashMap::clone(&vars_by_symbol),
|
||||||
|
annotation: Some((can_annotation.clone(), found_rigids.clone())),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(loc_pattern), (loc_can_expr, can_output))
|
|
||||||
}
|
}
|
||||||
// If we have a pattern, then the def has a body (that is, it's not 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.
|
// standalone annotation), so we need to canonicalize the pattern and expr.
|
||||||
Body(loc_pattern, loc_expr) => {
|
Body(loc_pattern, loc_expr) => {
|
||||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
let loc_can_pattern = canonicalize_def_pattern(env, loc_pattern, scope, var_store);
|
||||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
|
||||||
let mut shadowable_idents = scope.idents.clone();
|
|
||||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
|
||||||
|
|
||||||
let pattern_var = var_store.fresh();
|
|
||||||
let pattern_type = Type::Variable(pattern_var);
|
|
||||||
let pattern_expected = PExpected::NoExpectation(pattern_type);
|
|
||||||
|
|
||||||
let mut state = PatternState {
|
|
||||||
headers: SendMap::default(),
|
|
||||||
vars: Vec::with_capacity(1),
|
|
||||||
constraints: Vec::with_capacity(1),
|
|
||||||
};
|
|
||||||
|
|
||||||
let loc_can_pattern = canonicalize_pattern(
|
|
||||||
env,
|
|
||||||
&mut state,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
Assignment,
|
|
||||||
&loc_pattern.value,
|
|
||||||
loc_pattern.region,
|
|
||||||
&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 defined!
|
|
||||||
add_pattern_to_lookup_types(
|
|
||||||
&scope,
|
|
||||||
&loc_pattern,
|
|
||||||
&mut flex_info.def_types,
|
|
||||||
expr_type.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// bookkeeping for tail-call detection. If we're assigning to an
|
// bookkeeping for tail-call detection. If we're assigning to an
|
||||||
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
||||||
|
@ -675,34 +541,21 @@ fn canonicalize_def<'a>(
|
||||||
|
|
||||||
if let (
|
if let (
|
||||||
&ast::Pattern::Identifier(ref _name),
|
&ast::Pattern::Identifier(ref _name),
|
||||||
&Pattern::Identifier(_, ref defined_symbol),
|
&Pattern::Identifier(ref defined_symbol),
|
||||||
) = (&loc_pattern.value, &loc_can_pattern.value)
|
) = (&loc_pattern.value, &loc_can_pattern.value)
|
||||||
{
|
{
|
||||||
env.tailcallable_symbol = Some(defined_symbol.clone());
|
env.tailcallable_symbol = Some(defined_symbol.clone());
|
||||||
variables_by_symbol.insert(defined_symbol.clone(), expr_var);
|
|
||||||
|
// TODO isn't types_by_symbol enough? Do we need vars_by_symbol too?
|
||||||
|
vars_by_symbol.insert(defined_symbol.clone(), expr_var);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut loc_can_expr, can_output, ret_constraint) = canonicalize_expr(
|
let (mut loc_can_expr, can_output) =
|
||||||
rigids,
|
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
loc_expr.region,
|
|
||||||
&loc_expr.value,
|
|
||||||
NoExpectation(expr_type),
|
|
||||||
);
|
|
||||||
|
|
||||||
// reset the tailcallable_symbol
|
// reset the tailcallable_symbol
|
||||||
env.tailcallable_symbol = outer_identifier;
|
env.tailcallable_symbol = outer_identifier;
|
||||||
|
|
||||||
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
|
||||||
rigid_vars: Vec::new(),
|
|
||||||
flex_vars: state.vars,
|
|
||||||
def_types: state.headers,
|
|
||||||
defs_constraint: And(state.constraints),
|
|
||||||
ret_constraint,
|
|
||||||
})));
|
|
||||||
|
|
||||||
// see below: a closure needs a fresh References!
|
// see below: a closure needs a fresh References!
|
||||||
let mut is_closure = false;
|
let mut is_closure = false;
|
||||||
|
|
||||||
|
@ -714,7 +567,7 @@ fn canonicalize_def<'a>(
|
||||||
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
||||||
if let (
|
if let (
|
||||||
&ast::Pattern::Identifier(ref _name),
|
&ast::Pattern::Identifier(ref _name),
|
||||||
&Pattern::Identifier(_, ref defined_symbol),
|
&Pattern::Identifier(ref defined_symbol),
|
||||||
&Closure(ref symbol, _, ref arguments, ref body),
|
&Closure(ref symbol, _, ref arguments, ref body),
|
||||||
) = (
|
) = (
|
||||||
&loc_pattern.value,
|
&loc_pattern.value,
|
||||||
|
@ -761,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
|
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
||||||
// which defined names reference each other.
|
// which defined names reference each other.
|
||||||
for (ident, (symbol, region)) in
|
for (ident, (symbol, region)) in
|
||||||
|
@ -789,39 +640,34 @@ fn canonicalize_def<'a>(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
defined_symbols.push(symbol.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
for symbol in defined_symbols {
|
|
||||||
can_defs_by_symbol.insert(
|
can_defs_by_symbol.insert(
|
||||||
symbol,
|
symbol,
|
||||||
Def {
|
Def {
|
||||||
|
expr_var,
|
||||||
// TODO try to remove this .clone()!
|
// TODO try to remove this .clone()!
|
||||||
pattern: loc_can_pattern.clone(),
|
loc_pattern: loc_can_pattern.clone(),
|
||||||
expr: Located {
|
loc_expr: Located {
|
||||||
region: loc_can_expr.region,
|
region: loc_can_expr.region,
|
||||||
// TODO try to remove this .clone()!
|
// TODO try to remove this .clone()!
|
||||||
value: loc_can_expr.value.clone(),
|
value: loc_can_expr.value.clone(),
|
||||||
},
|
},
|
||||||
variables_by_symbol: im::HashMap::clone(&variables_by_symbol),
|
pattern_vars: im::HashMap::clone(&vars_by_symbol),
|
||||||
|
annotation: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(loc_pattern), (loc_can_expr, can_output))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Nested(value) => {
|
Nested(value) => {
|
||||||
return canonicalize_def(
|
canonicalize_def(
|
||||||
rigids,
|
|
||||||
found_rigids,
|
|
||||||
env,
|
env,
|
||||||
|
found_rigids,
|
||||||
Located {
|
Located {
|
||||||
value,
|
value,
|
||||||
region: loc_def.region,
|
region: loc_def.region,
|
||||||
},
|
},
|
||||||
scope,
|
scope,
|
||||||
can_defs_by_symbol,
|
can_defs_by_symbol,
|
||||||
flex_info,
|
|
||||||
var_store,
|
var_store,
|
||||||
refs_by_symbol,
|
refs_by_symbol,
|
||||||
);
|
);
|
||||||
|
@ -881,109 +727,28 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn can_defs_with_return<'a>(
|
pub fn can_defs_with_return<'a>(
|
||||||
rigids: &Rigids,
|
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
mut scope: Scope,
|
mut scope: Scope,
|
||||||
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
|
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
|
||||||
expected: Expected<Type>,
|
|
||||||
mut flex_info: Info,
|
|
||||||
rigid_info: Info,
|
|
||||||
loc_ret: &'a Located<ast::Expr<'a>>,
|
loc_ret: &'a Located<ast::Expr<'a>>,
|
||||||
) -> (Expr, Output, Constraint) {
|
) -> (Expr, Output) {
|
||||||
let mut found_rigids = SendMap::default();
|
let mut found_rigids = SendMap::default();
|
||||||
let unsorted = canonicalize_defs(
|
let unsorted = canonicalize_defs(env, &mut found_rigids, var_store, &mut scope, loc_defs);
|
||||||
rigids,
|
|
||||||
&mut found_rigids,
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
&mut scope,
|
|
||||||
loc_defs,
|
|
||||||
&mut flex_info,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The def as a whole is a tail call iff its return expression is a tail call.
|
// 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!
|
// Use its output as a starting point because its tail_call already has the right answer!
|
||||||
let (ret_expr, output, ret_con) = canonicalize_expr(
|
let (ret_expr, output) =
|
||||||
rigids,
|
canonicalize_expr(env, var_store, &mut scope, loc_ret.region, &loc_ret.value);
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
&mut scope,
|
|
||||||
loc_ret.region,
|
|
||||||
&loc_ret.value,
|
|
||||||
expected,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (can_defs, mut output) = sort_can_defs(env, unsorted, output);
|
let (can_defs, mut output) = sort_can_defs(env, unsorted, output);
|
||||||
|
|
||||||
output.rigids = output.rigids.union(found_rigids);
|
output.rigids = output.rigids.union(found_rigids);
|
||||||
|
|
||||||
// Rigid constraint for the def expr as a whole
|
|
||||||
let constraint = Let(Box::new(LetConstraint {
|
|
||||||
rigid_vars: rigid_info.vars,
|
|
||||||
flex_vars: Vec::new(),
|
|
||||||
def_types: rigid_info.def_types,
|
|
||||||
defs_constraint:
|
|
||||||
// Flex constraint
|
|
||||||
Let(Box::new(LetConstraint {
|
|
||||||
rigid_vars: Vec::new(),
|
|
||||||
flex_vars: flex_info.vars,
|
|
||||||
def_types: flex_info.def_types.clone(),
|
|
||||||
defs_constraint:
|
|
||||||
// Final flex constraints
|
|
||||||
Let(Box::new(LetConstraint {
|
|
||||||
rigid_vars: Vec::new(),
|
|
||||||
flex_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])
|
|
||||||
})),
|
|
||||||
ret_constraint: True,
|
|
||||||
}));
|
|
||||||
|
|
||||||
match can_defs {
|
match can_defs {
|
||||||
Ok(defs) => (
|
Ok(defs) => (Defs(defs, Box::new(ret_expr)), output),
|
||||||
Defs(var_store.fresh(), defs, Box::new(ret_expr)),
|
Err(err) => (RuntimeError(err), output),
|
||||||
output,
|
|
||||||
constraint,
|
|
||||||
),
|
|
||||||
Err(err) => (RuntimeError(err), output, constraint),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This lets us share bound type variables between nested annotations, e.g.
|
|
||||||
///
|
|
||||||
/// blah : Map k v -> Int
|
|
||||||
/// blah mapping =
|
|
||||||
/// nested : Map k v # <-- the same k and v from the top-level annotation
|
|
||||||
/// nested = mapping
|
|
||||||
/// 42
|
|
||||||
///
|
|
||||||
/// In elm/compiler this is called RTV - the "Rigid Type Variables" dictionary.
|
|
||||||
/// type BoundTypeVars = ImMap<Box<str>, Type>;
|
|
||||||
fn add_pattern_to_lookup_types<'a>(
|
|
||||||
scope: &Scope,
|
|
||||||
loc_pattern: &'a Located<ast::Pattern<'a>>,
|
|
||||||
lookup_types: &mut SendMap<Symbol, Located<Type>>,
|
|
||||||
expr_type: Type,
|
|
||||||
) {
|
|
||||||
let region = loc_pattern.region;
|
|
||||||
|
|
||||||
match loc_pattern.value {
|
|
||||||
ast::Pattern::Identifier(name) => {
|
|
||||||
let symbol = scope.symbol(&name);
|
|
||||||
let loc_type = Located {
|
|
||||||
region,
|
|
||||||
value: expr_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
lookup_types.insert(symbol, loc_type);
|
|
||||||
}
|
|
||||||
_ => panic!("TODO constrain patterns other than Identifier"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
626
src/can/expr.rs
626
src/can/expr.rs
|
@ -1,13 +1,13 @@
|
||||||
use crate::can::def::{can_defs_with_return, Def, Info};
|
use crate::can::def::{can_defs_with_return, Def};
|
||||||
use crate::can::env::Env;
|
use crate::can::env::Env;
|
||||||
use crate::can::ident::Lowercase;
|
use crate::can::ident::Lowercase;
|
||||||
use crate::can::num::{
|
use crate::can::num::{
|
||||||
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result,
|
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result,
|
||||||
int_expr_from_result,
|
int_expr_from_result,
|
||||||
};
|
};
|
||||||
|
use crate::can::pattern::idents_from_patterns;
|
||||||
use crate::can::pattern::PatternType::*;
|
use crate::can::pattern::PatternType::*;
|
||||||
use crate::can::pattern::{canonicalize_pattern, remove_idents, Pattern};
|
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::Problem;
|
||||||
use crate::can::problem::RuntimeError;
|
use crate::can::problem::RuntimeError;
|
||||||
use crate::can::problem::RuntimeError::*;
|
use crate::can::problem::RuntimeError::*;
|
||||||
|
@ -15,27 +15,16 @@ use crate::can::procedure::References;
|
||||||
use crate::can::scope::Scope;
|
use crate::can::scope::Scope;
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
use crate::collections::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||||
use crate::constrain::{self, exists};
|
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::operator::CalledVia;
|
use crate::operator::CalledVia;
|
||||||
use crate::parse::ast;
|
use crate::parse::ast;
|
||||||
use crate::region::{Located, Region};
|
use crate::region::{Located, Region};
|
||||||
use crate::subs::{VarStore, Variable};
|
use crate::subs::{VarStore, Variable};
|
||||||
use crate::types::AnnotationSource::*;
|
|
||||||
use crate::types::Constraint::{self, *};
|
|
||||||
use crate::types::Expected::{self, *};
|
|
||||||
use crate::types::Type::{self, *};
|
|
||||||
use crate::types::{LetConstraint, PExpected, PReason, Reason};
|
|
||||||
use im_rc::Vector;
|
use im_rc::Vector;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::i64;
|
use std::i64;
|
||||||
use std::ops::Neg;
|
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)]
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
pub references: References,
|
pub references: References,
|
||||||
|
@ -46,46 +35,67 @@ pub struct Output {
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
// Literals
|
// Literals
|
||||||
Int(i64),
|
Int(Variable, i64),
|
||||||
Float(f64),
|
Float(Variable, f64),
|
||||||
Str(Box<str>),
|
Str(Box<str>),
|
||||||
BlockStr(Box<str>),
|
BlockStr(Box<str>),
|
||||||
List(Variable, Vec<Located<Expr>>),
|
List(Variable, Vec<(Variable, Located<Expr>)>),
|
||||||
|
|
||||||
// Lookups
|
// Lookups
|
||||||
Var(Variable, Symbol),
|
Var {
|
||||||
/// Works the same as Var, but has an important marking purpose.
|
symbol_for_lookup: Symbol,
|
||||||
/// See 13623e3f5f65ea2d703cf155f16650c1e8246502 for the bug this fixed.
|
resolved_symbol: Symbol,
|
||||||
FunctionPointer(Variable, Symbol),
|
},
|
||||||
|
|
||||||
/// Look up exactly one field on a record, e.g. (expr).foo.
|
|
||||||
Access(Box<Located<Expr>>, Box<str>),
|
|
||||||
/// field accessor as a function, e.g. (.foo) expr
|
|
||||||
Accessor(Box<str>),
|
|
||||||
|
|
||||||
Tag(Box<str>, Vec<Expr>),
|
|
||||||
|
|
||||||
// Pattern Matching
|
// Pattern Matching
|
||||||
/// Case is guaranteed to be exhaustive at this point. (If it wasn't, then
|
/// 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.)
|
/// a _ branch was added at the end that will throw a runtime error.)
|
||||||
/// Also, `If` is desugared into `Case` matching on `False` and `_` at this point.
|
/// Also, `If` is desugared into `When` matching on `False` and `_` at this point.
|
||||||
Case(
|
When {
|
||||||
Variable,
|
cond_var: Variable,
|
||||||
Box<Located<Expr>>,
|
expr_var: Variable,
|
||||||
Vec<(Located<Pattern>, Located<Expr>)>,
|
loc_cond: Box<Located<Expr>>,
|
||||||
),
|
branches: Vec<(Located<Pattern>, Located<Expr>)>,
|
||||||
Defs(Variable, Vec<Def>, Box<Located<Expr>>),
|
},
|
||||||
|
Defs(Vec<Def>, Box<Located<Expr>>),
|
||||||
|
|
||||||
/// This is *only* for calling functions, not for tag application.
|
/// This is *only* for calling functions, not for tag application.
|
||||||
/// The Tag variant contains any applied values inside it.
|
/// The Tag variant contains any applied values inside it.
|
||||||
Call(Box<Expr>, Vec<Located<Expr>>, CalledVia),
|
Call(
|
||||||
|
Box<(Variable, Located<Expr>, Variable)>,
|
||||||
|
Vec<(Variable, Located<Expr>)>,
|
||||||
|
CalledVia,
|
||||||
|
),
|
||||||
|
|
||||||
Closure(Symbol, Recursive, Vec<Located<Pattern>>, Box<Located<Expr>>),
|
Closure(
|
||||||
|
Symbol,
|
||||||
|
Recursive,
|
||||||
|
Vec<(Variable, Located<Pattern>)>,
|
||||||
|
Box<(Located<Expr>, Variable)>,
|
||||||
|
),
|
||||||
|
|
||||||
// Product Types
|
// Product Types
|
||||||
Record(Variable, SendMap<Lowercase, Located<Expr>>),
|
Record(Variable, SendMap<Lowercase, (Variable, Located<Expr>)>),
|
||||||
|
|
||||||
|
/// Empty record constant
|
||||||
EmptyRecord,
|
EmptyRecord,
|
||||||
|
|
||||||
|
/// Look up exactly one field on a record, e.g. (expr).foo.
|
||||||
|
Access {
|
||||||
|
ext_var: Variable,
|
||||||
|
field_var: Variable,
|
||||||
|
loc_expr: Box<Located<Expr>>,
|
||||||
|
field: Lowercase,
|
||||||
|
},
|
||||||
|
/// field accessor as a function, e.g. (.foo) expr
|
||||||
|
Accessor {
|
||||||
|
ext_var: Variable,
|
||||||
|
field_var: Variable,
|
||||||
|
field: Lowercase,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sum Types
|
||||||
|
Tag(Box<str>, Vec<Expr>),
|
||||||
|
|
||||||
// Compiles, but will crash if reached
|
// Compiles, but will crash if reached
|
||||||
RuntimeError(RuntimeError),
|
RuntimeError(RuntimeError),
|
||||||
}
|
}
|
||||||
|
@ -98,52 +108,34 @@ pub enum Recursive {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canonicalize_expr(
|
pub fn canonicalize_expr(
|
||||||
rigids: &Rigids,
|
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
region: Region,
|
region: Region,
|
||||||
expr: &ast::Expr,
|
expr: &ast::Expr,
|
||||||
expected: Expected<Type>,
|
) -> (Located<Expr>, Output) {
|
||||||
) -> (Located<Expr>, Output, Constraint) {
|
|
||||||
use self::Expr::*;
|
use self::Expr::*;
|
||||||
|
|
||||||
let (expr, output, constraint) = match expr {
|
let (expr, output) = match expr {
|
||||||
ast::Expr::Int(string) => {
|
ast::Expr::Int(string) => {
|
||||||
let (constraint, answer) =
|
let answer = int_expr_from_result(var_store, finish_parsing_int(string), env);
|
||||||
int_expr_from_result(var_store, finish_parsing_int(string), env, expected, region);
|
|
||||||
|
|
||||||
(answer, Output::default(), constraint)
|
(answer, Output::default())
|
||||||
}
|
}
|
||||||
ast::Expr::Float(string) => {
|
ast::Expr::Float(string) => {
|
||||||
let (constraint, answer) = float_expr_from_result(
|
let answer = float_expr_from_result(var_store, finish_parsing_float(string), env);
|
||||||
var_store,
|
|
||||||
finish_parsing_float(string),
|
|
||||||
env,
|
|
||||||
expected,
|
|
||||||
region,
|
|
||||||
);
|
|
||||||
|
|
||||||
(answer, Output::default(), constraint)
|
(answer, Output::default())
|
||||||
}
|
}
|
||||||
ast::Expr::Record(fields) => {
|
ast::Expr::Record(fields) => {
|
||||||
if fields.is_empty() {
|
if fields.is_empty() {
|
||||||
let constraint = Eq(EmptyRec, expected, region);
|
(EmptyRecord, Output::default())
|
||||||
|
|
||||||
(EmptyRecord, Output::default(), constraint)
|
|
||||||
} else {
|
} else {
|
||||||
let mut field_exprs = SendMap::default();
|
let mut can_fields = 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());
|
|
||||||
let mut output = Output::default();
|
let mut output = Output::default();
|
||||||
|
|
||||||
for loc_field in fields.iter() {
|
for loc_field in fields.iter() {
|
||||||
let (label, field_expr, field_out, field_var, field_type, field_con) =
|
let (label, field_expr, field_out, field_var) = canonicalize_field(
|
||||||
canonicalize_field(
|
|
||||||
rigids,
|
|
||||||
env,
|
env,
|
||||||
var_store,
|
var_store,
|
||||||
scope,
|
scope,
|
||||||
|
@ -151,84 +143,36 @@ pub fn canonicalize_expr(
|
||||||
loc_field.region,
|
loc_field.region,
|
||||||
);
|
);
|
||||||
|
|
||||||
field_vars.push(field_var);
|
can_fields.insert(label, (field_var, field_expr));
|
||||||
field_exprs.insert(label.clone(), field_expr);
|
|
||||||
field_types.insert(label, field_type);
|
|
||||||
|
|
||||||
constraints.push(field_con);
|
|
||||||
output.references = output.references.union(field_out.references);
|
output.references = output.references.union(field_out.references);
|
||||||
}
|
}
|
||||||
|
|
||||||
let record_var = var_store.fresh();
|
(Record(var_store.fresh(), can_fields), output)
|
||||||
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, region);
|
|
||||||
|
|
||||||
constraints.push(record_con);
|
|
||||||
|
|
||||||
let constraint = exists(field_vars, And(constraints));
|
|
||||||
|
|
||||||
(Record(record_var, field_exprs), output, constraint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::Expr::Str(string) => {
|
ast::Expr::Str(string) => (Str((*string).into()), Output::default()),
|
||||||
let constraint = Eq(constrain::str_type(), expected, region);
|
|
||||||
|
|
||||||
(Str((*string).into()), Output::default(), constraint)
|
|
||||||
}
|
|
||||||
ast::Expr::BlockStr(lines) => {
|
ast::Expr::BlockStr(lines) => {
|
||||||
let constraint = Eq(constrain::str_type(), expected, region);
|
|
||||||
let joined = lines.iter().copied().collect::<Vec<&str>>().join("\n");
|
let joined = lines.iter().copied().collect::<Vec<&str>>().join("\n");
|
||||||
|
|
||||||
(BlockStr(joined.into()), Output::default(), constraint)
|
(BlockStr(joined.into()), Output::default())
|
||||||
}
|
}
|
||||||
ast::Expr::List(loc_elems) => {
|
ast::Expr::List(loc_elems) => {
|
||||||
if loc_elems.is_empty() {
|
if loc_elems.is_empty() {
|
||||||
let list_var = var_store.fresh();
|
(List(var_store.fresh(), Vec::new()), Output::default())
|
||||||
let constraint = Eq(constrain::empty_list_type(list_var), expected, region);
|
|
||||||
|
|
||||||
(List(list_var, Vec::new()), Output::default(), constraint)
|
|
||||||
} else {
|
} else {
|
||||||
let mut can_elems = Vec::with_capacity(loc_elems.len());
|
let mut can_elems = Vec::with_capacity(loc_elems.len());
|
||||||
let list_var = var_store.fresh(); // `v` in the type (List v)
|
|
||||||
let list_type = Type::Variable(list_var);
|
|
||||||
let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2));
|
|
||||||
let mut references = References::new();
|
let mut references = References::new();
|
||||||
|
|
||||||
for loc_elem in loc_elems.iter() {
|
for loc_elem in loc_elems.iter() {
|
||||||
let elem_var = var_store.fresh();
|
let (can_expr, elem_out) =
|
||||||
let elem_type = Variable(elem_var);
|
canonicalize_expr(env, var_store, scope, loc_elem.region, &loc_elem.value);
|
||||||
let elem_expected = NoExpectation(elem_type.clone());
|
|
||||||
let list_elem_constraint = Eq(
|
|
||||||
list_type.clone(),
|
|
||||||
ForReason(Reason::ElemInList, elem_type, region),
|
|
||||||
region,
|
|
||||||
);
|
|
||||||
let (can_expr, elem_out, constraint) = canonicalize_expr(
|
|
||||||
rigids,
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
loc_elem.region,
|
|
||||||
&loc_elem.value,
|
|
||||||
elem_expected,
|
|
||||||
);
|
|
||||||
|
|
||||||
constraints.push(list_elem_constraint);
|
|
||||||
constraints.push(constraint);
|
|
||||||
|
|
||||||
references = references.union(elem_out.references);
|
references = references.union(elem_out.references);
|
||||||
|
|
||||||
can_elems.push(can_expr);
|
can_elems.push((var_store.fresh(), can_expr));
|
||||||
}
|
}
|
||||||
|
|
||||||
constraints.push(Eq(constrain::list_type(list_type), expected, region));
|
|
||||||
|
|
||||||
let mut output = Output::default();
|
let mut output = Output::default();
|
||||||
|
|
||||||
output.references = references;
|
output.references = references;
|
||||||
|
@ -236,72 +180,27 @@ pub fn canonicalize_expr(
|
||||||
// A list literal is never a tail call!
|
// A list literal is never a tail call!
|
||||||
output.tail_call = None;
|
output.tail_call = None;
|
||||||
|
|
||||||
(List(list_var, can_elems), output, And(constraints))
|
(List(var_store.fresh(), can_elems), output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::Expr::Apply(loc_fn, loc_args, application_style) => {
|
ast::Expr::Apply(loc_fn, loc_args, application_style) => {
|
||||||
// The expression that evaluates to the function being called, e.g. `foo` in
|
// The expression that evaluates to the function being called, e.g. `foo` in
|
||||||
// (foo) bar baz
|
// (foo) bar baz
|
||||||
let fn_var = var_store.fresh();
|
|
||||||
let fn_type = Variable(fn_var);
|
|
||||||
let fn_region = loc_fn.region;
|
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Canonicalize the function expression and its arguments
|
// Canonicalize the function expression and its arguments
|
||||||
let (fn_expr, mut output, fn_con) = canonicalize_expr(
|
let (fn_expr, mut output) =
|
||||||
rigids,
|
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
loc_fn.region,
|
|
||||||
&loc_fn.value,
|
|
||||||
fn_expected,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The function's return type
|
// The function's return type
|
||||||
let ret_var = var_store.fresh();
|
|
||||||
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());
|
|
||||||
|
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
let mut outputs = Vec::new();
|
let mut outputs = Vec::new();
|
||||||
|
|
||||||
for (index, loc_arg) in loc_args.iter().enumerate() {
|
for loc_arg in loc_args {
|
||||||
let region = loc_arg.region;
|
let (arg_expr, arg_out) =
|
||||||
let arg_var = var_store.fresh();
|
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
|
||||||
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_expr, arg_out, arg_con) = canonicalize_expr(
|
|
||||||
rigids,
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
loc_arg.region,
|
|
||||||
&loc_arg.value,
|
|
||||||
expected_arg,
|
|
||||||
);
|
|
||||||
|
|
||||||
vars.push(arg_var);
|
args.push((var_store.fresh(), arg_expr));
|
||||||
arg_types.push(arg_type);
|
|
||||||
arg_cons.push(arg_con);
|
|
||||||
|
|
||||||
args.push(arg_expr);
|
|
||||||
outputs.push(arg_out);
|
outputs.push(arg_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,26 +208,35 @@ pub fn canonicalize_expr(
|
||||||
output.tail_call = None;
|
output.tail_call = None;
|
||||||
|
|
||||||
let expr = match fn_expr.value {
|
let expr = match fn_expr.value {
|
||||||
Var(_, ref sym) | FunctionPointer(_, ref sym) => {
|
Var {
|
||||||
// In the FunctionPointer case, we're calling an inline closure;
|
ref resolved_symbol,
|
||||||
// something like ((\a b -> a + b) 1 2).
|
..
|
||||||
output.references.calls.insert(sym.clone());
|
} => {
|
||||||
|
output.references.calls.insert(resolved_symbol.clone());
|
||||||
|
|
||||||
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
|
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
|
||||||
output.tail_call = match &env.tailcallable_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,
|
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(_) => {
|
RuntimeError(_) => {
|
||||||
// We can't call a runtime error; bail out by propagating it!
|
// We can't call a runtime error; bail out by propagating it!
|
||||||
return (fn_expr, output, True);
|
return (fn_expr, output);
|
||||||
}
|
}
|
||||||
not_var => {
|
_ => {
|
||||||
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
|
// 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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -336,23 +244,7 @@ pub fn canonicalize_expr(
|
||||||
output.references = output.references.union(arg_out.references);
|
output.references = output.references.union(arg_out.references);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected_fn_type = ForReason(
|
(expr, output)
|
||||||
fn_reason,
|
|
||||||
Function(arg_types, Box::new(ret_type.clone())),
|
|
||||||
region,
|
|
||||||
);
|
|
||||||
|
|
||||||
let constraint = exists(
|
|
||||||
vars,
|
|
||||||
And(vec![
|
|
||||||
fn_con,
|
|
||||||
Eq(fn_type, expected_fn_type, fn_region),
|
|
||||||
And(arg_cons),
|
|
||||||
Eq(ret_type, expected, region),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
(expr, output, constraint)
|
|
||||||
}
|
}
|
||||||
ast::Expr::Var(module_parts, name) => {
|
ast::Expr::Var(module_parts, name) => {
|
||||||
let symbol = if module_parts.is_empty() {
|
let symbol = if module_parts.is_empty() {
|
||||||
|
@ -363,10 +255,8 @@ pub fn canonicalize_expr(
|
||||||
|
|
||||||
let ident = Ident::new(module_parts, name);
|
let ident = Ident::new(module_parts, name);
|
||||||
|
|
||||||
canonicalize_lookup(env, scope, ident, symbol, region, expected, var_store)
|
canonicalize_lookup(env, scope, ident, symbol, region)
|
||||||
}
|
} //ast::Expr::InterpolatedStr(pairs, suffix) => {
|
||||||
|
|
||||||
//ast::Expr::InterpolatedStr(pairs, suffix) => {
|
|
||||||
// let mut output = Output::new();
|
// let mut output = Output::new();
|
||||||
// let can_pairs: Vec<(String, Located<Expr>)> = pairs
|
// let can_pairs: Vec<(String, Located<Expr>)> = pairs
|
||||||
// .into_iter()
|
// .into_iter()
|
||||||
|
@ -408,16 +298,12 @@ pub fn canonicalize_expr(
|
||||||
//}
|
//}
|
||||||
ast::Expr::Defs(loc_defs, loc_ret) => {
|
ast::Expr::Defs(loc_defs, loc_ret) => {
|
||||||
can_defs_with_return(
|
can_defs_with_return(
|
||||||
rigids,
|
|
||||||
env,
|
env,
|
||||||
var_store,
|
var_store,
|
||||||
// The body expression gets a new scope for canonicalization,
|
// The body expression gets a new scope for canonicalization,
|
||||||
// so clone it.
|
// so clone it.
|
||||||
scope.clone(),
|
scope.clone(),
|
||||||
loc_defs,
|
loc_defs,
|
||||||
expected,
|
|
||||||
Info::with_capacity(loc_defs.len()),
|
|
||||||
Info::with_capacity(loc_defs.len()),
|
|
||||||
loc_ret,
|
loc_ret,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -445,14 +331,7 @@ pub fn canonicalize_expr(
|
||||||
// it means there was shadowing, which will be handled later.
|
// it means there was shadowing, which will be handled later.
|
||||||
scope.idents = union_pairs(scope.idents, arg_idents.iter());
|
scope.idents = union_pairs(scope.idents, arg_idents.iter());
|
||||||
|
|
||||||
let mut state = PatternState {
|
let mut can_args = Vec::with_capacity(loc_arg_patterns.len());
|
||||||
headers: SendMap::default(),
|
|
||||||
vars: Vec::with_capacity(loc_arg_patterns.len()),
|
|
||||||
constraints: Vec::with_capacity(1),
|
|
||||||
};
|
|
||||||
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() {
|
for loc_pattern in loc_arg_patterns.into_iter() {
|
||||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||||
|
@ -460,62 +339,25 @@ pub fn canonicalize_expr(
|
||||||
let mut shadowable_idents = scope.idents.clone();
|
let mut shadowable_idents = scope.idents.clone();
|
||||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||||
|
|
||||||
let pattern_var = var_store.fresh();
|
|
||||||
let pattern_type = Type::Variable(pattern_var);
|
|
||||||
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
|
|
||||||
|
|
||||||
pattern_types.push(pattern_type);
|
|
||||||
|
|
||||||
let can_arg = canonicalize_pattern(
|
let can_arg = canonicalize_pattern(
|
||||||
env,
|
env,
|
||||||
&mut state,
|
|
||||||
var_store,
|
var_store,
|
||||||
&mut scope,
|
&mut scope,
|
||||||
FunctionArg,
|
FunctionArg,
|
||||||
&loc_pattern.value,
|
&loc_pattern.value,
|
||||||
loc_pattern.region,
|
loc_pattern.region,
|
||||||
&mut shadowable_idents,
|
&mut shadowable_idents,
|
||||||
pattern_expected,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
vars.push(pattern_var);
|
can_args.push((var_store.fresh(), can_arg));
|
||||||
|
|
||||||
can_args.push(can_arg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret_var = var_store.fresh();
|
let (loc_body_expr, mut output) = canonicalize_expr(
|
||||||
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, ret_constraint) = canonicalize_expr(
|
|
||||||
rigids,
|
|
||||||
env,
|
env,
|
||||||
var_store,
|
var_store,
|
||||||
&mut scope,
|
&mut scope,
|
||||||
loc_body_expr.region,
|
loc_body_expr.region,
|
||||||
&loc_body_expr.value,
|
&loc_body_expr.value,
|
||||||
body_type,
|
|
||||||
);
|
|
||||||
|
|
||||||
let defs_constraint = And(state.constraints);
|
|
||||||
|
|
||||||
let constraint = exists(
|
|
||||||
state.vars.clone(),
|
|
||||||
And(vec![
|
|
||||||
Let(Box::new(LetConstraint {
|
|
||||||
rigid_vars: Vec::new(),
|
|
||||||
flex_vars: state.vars,
|
|
||||||
def_types: state.headers,
|
|
||||||
defs_constraint: defs_constraint,
|
|
||||||
ret_constraint,
|
|
||||||
})),
|
|
||||||
// "the closure's type is equal to expected type"
|
|
||||||
Eq(fn_typ, expected, region),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now that we've collected all the references, check to see if any of the args we defined
|
// Now that we've collected all the references, check to see if any of the args we defined
|
||||||
|
@ -542,130 +384,43 @@ pub fn canonicalize_expr(
|
||||||
symbol,
|
symbol,
|
||||||
Recursive::NotRecursive,
|
Recursive::NotRecursive,
|
||||||
can_args,
|
can_args,
|
||||||
Box::new(loc_body_expr),
|
Box::new((loc_body_expr, var_store.fresh())),
|
||||||
),
|
),
|
||||||
output,
|
output,
|
||||||
constraint,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ast::Expr::When(loc_cond, branches) => {
|
||||||
ast::Expr::Case(loc_cond, branches) => {
|
|
||||||
// Infer the condition expression's type.
|
// Infer the condition expression's type.
|
||||||
let cond_var = var_store.fresh();
|
let cond_var = var_store.fresh();
|
||||||
let cond_type = Variable(cond_var);
|
let (can_cond, mut output) =
|
||||||
let (can_cond, mut output, expr_con) = canonicalize_expr(
|
canonicalize_expr(env, var_store, scope, region, &loc_cond.value);
|
||||||
rigids,
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
region,
|
|
||||||
&loc_cond.value,
|
|
||||||
NoExpectation(cond_type.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// the condition can never be a tail-call
|
// the condition can never be a tail-call
|
||||||
output.tail_call = None;
|
output.tail_call = None;
|
||||||
|
|
||||||
let mut can_branches = Vec::with_capacity(branches.len());
|
let mut can_branches = Vec::with_capacity(branches.len());
|
||||||
let mut constraints = Vec::with_capacity(branches.len() + 1);
|
|
||||||
|
|
||||||
match expected {
|
for (loc_pattern, loc_expr) in branches {
|
||||||
FromAnnotation(name, arity, _, typ) => {
|
|
||||||
for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() {
|
|
||||||
let mut shadowable_idents = scope.idents.clone();
|
let mut shadowable_idents = scope.idents.clone();
|
||||||
|
|
||||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||||
let (can_pattern, loc_can_expr, branch_con, branch_references) =
|
|
||||||
canonicalize_case_branch(
|
let (can_pattern, loc_can_expr, branch_references) = canonicalize_when_branch(
|
||||||
env,
|
env,
|
||||||
var_store,
|
var_store,
|
||||||
rigids,
|
|
||||||
scope,
|
scope,
|
||||||
region,
|
region,
|
||||||
loc_pattern,
|
loc_pattern,
|
||||||
loc_expr,
|
loc_expr,
|
||||||
PExpected::ForReason(
|
|
||||||
PReason::CaseMatch { index },
|
|
||||||
cond_type.clone(),
|
|
||||||
region,
|
|
||||||
),
|
|
||||||
FromAnnotation(
|
|
||||||
name.clone(),
|
|
||||||
arity,
|
|
||||||
TypedCaseBranch(index),
|
|
||||||
typ.clone(),
|
|
||||||
),
|
|
||||||
&mut output,
|
&mut output,
|
||||||
);
|
);
|
||||||
|
|
||||||
output.references = output.references.union(branch_references);
|
output.references = output.references.union(branch_references);
|
||||||
|
|
||||||
can_branches.push((can_pattern, loc_can_expr));
|
can_branches.push((can_pattern, loc_can_expr));
|
||||||
|
|
||||||
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]),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
// A "when" with no branches is a runtime error, but it will mess things up
|
||||||
let branch_var = var_store.fresh();
|
|
||||||
let branch_type = Variable(branch_var);
|
|
||||||
let mut branch_cons = Vec::with_capacity(branches.len());
|
|
||||||
|
|
||||||
for (index, (loc_pattern, loc_expr)) in branches.into_iter().enumerate() {
|
|
||||||
let mut shadowable_idents = scope.idents.clone();
|
|
||||||
|
|
||||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
|
||||||
|
|
||||||
let (can_pattern, loc_can_expr, branch_con, branch_references) =
|
|
||||||
canonicalize_case_branch(
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
rigids,
|
|
||||||
scope,
|
|
||||||
region,
|
|
||||||
loc_pattern,
|
|
||||||
loc_expr,
|
|
||||||
PExpected::ForReason(
|
|
||||||
PReason::CaseMatch { index },
|
|
||||||
cond_type.clone(),
|
|
||||||
region,
|
|
||||||
),
|
|
||||||
ForReason(
|
|
||||||
Reason::CaseBranch { index },
|
|
||||||
branch_type.clone(),
|
|
||||||
region,
|
|
||||||
),
|
|
||||||
&mut output,
|
|
||||||
);
|
|
||||||
|
|
||||||
output.references = output.references.union(branch_references);
|
|
||||||
|
|
||||||
can_branches.push((can_pattern, loc_can_expr));
|
|
||||||
|
|
||||||
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 case-expression.
|
|
||||||
Eq(branch_type, expected, region),
|
|
||||||
]),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A case with no branches is a runtime error, but it will mess things up
|
|
||||||
// if code gen mistakenly thinks this is a tail call just because its condition
|
// if code gen mistakenly thinks this is a tail call just because its condition
|
||||||
// happend to be one. (The condition gave us our initial output value.)
|
// happend to be one. (The condition gave us our initial output value.)
|
||||||
if branches.is_empty() {
|
if branches.is_empty() {
|
||||||
|
@ -673,75 +428,42 @@ pub fn canonicalize_expr(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incorporate all three expressions into a combined Output value.
|
// Incorporate all three expressions into a combined Output value.
|
||||||
let expr = Case(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,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO check for exhaustiveness. If this `case` is non-exaustive, then:
|
(expr, output)
|
||||||
//
|
|
||||||
// 1. Record a Problem.
|
|
||||||
// 2. Add an extra _ branch at the end which throws a runtime error.
|
|
||||||
|
|
||||||
(expr, output, And(constraints))
|
|
||||||
}
|
}
|
||||||
ast::Expr::Access(record_expr, field) => {
|
ast::Expr::Access(record_expr, field) => {
|
||||||
let ext_var = var_store.fresh();
|
let (loc_expr, output) = canonicalize_expr(env, var_store, scope, region, record_expr);
|
||||||
let field_var = var_store.fresh();
|
|
||||||
let ext_type = Type::Variable(ext_var);
|
|
||||||
let field_type = Type::Variable(field_var);
|
|
||||||
|
|
||||||
let mut rec_field_types = SendMap::default();
|
|
||||||
|
|
||||||
rec_field_types.insert(Lowercase::from(*field), field_type.clone());
|
|
||||||
|
|
||||||
let record_type = Type::Record(rec_field_types, Box::new(ext_type));
|
|
||||||
let record_expected = Expected::NoExpectation(record_type);
|
|
||||||
|
|
||||||
let (loc_expr, output, mut constraint) = canonicalize_expr(
|
|
||||||
&ImMap::default(),
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
region,
|
|
||||||
record_expr,
|
|
||||||
record_expected,
|
|
||||||
);
|
|
||||||
|
|
||||||
constraint = exists(
|
|
||||||
vec![field_var, ext_var],
|
|
||||||
And(vec![constraint, Eq(field_type, expected, region)]),
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Access(Box::new(loc_expr), Lowercase::from(*field).into()),
|
Access {
|
||||||
|
field_var: var_store.fresh(),
|
||||||
|
ext_var: var_store.fresh(),
|
||||||
|
loc_expr: Box::new(loc_expr),
|
||||||
|
field: Lowercase::from(*field),
|
||||||
|
},
|
||||||
output,
|
output,
|
||||||
constraint,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::AccessorFunction(field) => {
|
ast::Expr::AccessorFunction(field) => {
|
||||||
let ext_var = var_store.fresh();
|
let ext_var = var_store.fresh();
|
||||||
let ext_type = Variable(ext_var);
|
|
||||||
|
|
||||||
let field_var = var_store.fresh();
|
let field_var = var_store.fresh();
|
||||||
let field_type = Variable(field_var);
|
|
||||||
|
|
||||||
let mut field_types = SendMap::default();
|
|
||||||
let field_name: Lowercase = (*field).into();
|
let field_name: Lowercase = (*field).into();
|
||||||
field_types.insert(field_name.clone(), field_type.clone());
|
|
||||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Accessor(field_name.into()),
|
Accessor {
|
||||||
|
field: field_name,
|
||||||
|
ext_var,
|
||||||
|
field_var,
|
||||||
|
},
|
||||||
Output::default(),
|
Output::default(),
|
||||||
exists(
|
|
||||||
vec![field_var, ext_var],
|
|
||||||
Eq(
|
|
||||||
Type::Function(vec![record_type], Box::new(field_type)),
|
|
||||||
expected,
|
|
||||||
region,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::Expr::If(_)
|
ast::Expr::If(_)
|
||||||
| ast::Expr::GlobalTag(_)
|
| ast::Expr::GlobalTag(_)
|
||||||
| ast::Expr::PrivateTag(_)
|
| ast::Expr::PrivateTag(_)
|
||||||
|
@ -754,10 +476,9 @@ pub fn canonicalize_expr(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ast::Expr::Nested(sub_expr) => {
|
ast::Expr::Nested(sub_expr) => {
|
||||||
let (answer, output, constraint) =
|
let (answer, output) = canonicalize_expr(env, var_store, scope, region, sub_expr);
|
||||||
canonicalize_expr(rigids, env, var_store, scope, region, sub_expr, expected);
|
|
||||||
|
|
||||||
(answer.value, output, constraint)
|
(answer.value, output)
|
||||||
}
|
}
|
||||||
ast::Expr::NonBase10Int {
|
ast::Expr::NonBase10Int {
|
||||||
string,
|
string,
|
||||||
|
@ -770,10 +491,9 @@ pub fn canonicalize_expr(
|
||||||
result = result.map(i64::neg);
|
result = result.map(i64::neg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (constraint, answer) =
|
let answer = int_expr_from_result(var_store, result, env);
|
||||||
int_expr_from_result(var_store, result, env, expected, region);
|
|
||||||
|
|
||||||
(answer, Output::default(), constraint)
|
(answer, Output::default())
|
||||||
}
|
}
|
||||||
// Below this point, we shouln't see any of these nodes anymore because
|
// Below this point, we shouln't see any of these nodes anymore because
|
||||||
// operator desugaring should have removed them!
|
// operator desugaring should have removed them!
|
||||||
|
@ -822,7 +542,6 @@ pub fn canonicalize_expr(
|
||||||
value: expr,
|
value: expr,
|
||||||
},
|
},
|
||||||
output,
|
output,
|
||||||
constraint,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,19 +550,17 @@ fn canonicalize_lookup(
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
symbol: Symbol,
|
symbol_for_lookup: Symbol,
|
||||||
region: Region,
|
region: Region,
|
||||||
expected: Expected<Type>,
|
) -> (Expr, Output) {
|
||||||
var_store: &VarStore,
|
|
||||||
) -> (Expr, Output, Constraint) {
|
|
||||||
use self::Expr::*;
|
use self::Expr::*;
|
||||||
|
|
||||||
let mut output = Output::default();
|
let mut output = Output::default();
|
||||||
let (constraint, can_expr) = match resolve_ident(&env, &scope, ident, &mut output.references) {
|
let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) {
|
||||||
Ok(sub_symbol) => (
|
Ok(resolved_symbol) => Var {
|
||||||
Lookup(symbol, expected, region),
|
symbol_for_lookup,
|
||||||
Var(var_store.fresh(), sub_symbol),
|
resolved_symbol,
|
||||||
),
|
},
|
||||||
Err(ident) => {
|
Err(ident) => {
|
||||||
let loc_ident = Located {
|
let loc_ident = Located {
|
||||||
region,
|
region,
|
||||||
|
@ -852,28 +569,23 @@ fn canonicalize_lookup(
|
||||||
|
|
||||||
env.problem(Problem::UnrecognizedConstant(loc_ident.clone()));
|
env.problem(Problem::UnrecognizedConstant(loc_ident.clone()));
|
||||||
|
|
||||||
(True, RuntimeError(UnrecognizedConstant(loc_ident)))
|
RuntimeError(UnrecognizedConstant(loc_ident))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(can_expr, output, constraint)
|
(can_expr, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO trim down these arguments
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn canonicalize_case_branch<'a>(
|
fn canonicalize_when_branch<'a>(
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
rigids: &Rigids,
|
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
region: Region,
|
region: Region,
|
||||||
loc_pattern: &Located<ast::Pattern<'a>>,
|
loc_pattern: &Located<ast::Pattern<'a>>,
|
||||||
loc_expr: &Located<ast::Expr<'a>>,
|
loc_expr: &Located<ast::Expr<'a>>,
|
||||||
pattern_expected: PExpected<Type>,
|
|
||||||
expr_expected: Expected<Type>,
|
|
||||||
output: &mut Output,
|
output: &mut Output,
|
||||||
) -> (Located<Pattern>, Located<Expr>, Constraint, References) {
|
) -> (Located<Pattern>, Located<Expr>, References) {
|
||||||
// Each case branch gets a new scope for canonicalization.
|
// Each case branch gets a new scope for canonicalization.
|
||||||
// Shadow `scope` to make sure we don't accidentally use the original one for the
|
// Shadow `scope` to make sure we don't accidentally use the original one for the
|
||||||
// rest of this block.
|
// rest of this block.
|
||||||
|
@ -892,15 +604,8 @@ fn canonicalize_case_branch<'a>(
|
||||||
|
|
||||||
scope.idents = union_pairs(scope.idents, defined_idents.iter());
|
scope.idents = union_pairs(scope.idents, defined_idents.iter());
|
||||||
|
|
||||||
let (can_expr, branch_output, ret_constraint) = canonicalize_expr(
|
let (can_expr, branch_output) =
|
||||||
rigids,
|
canonicalize_expr(env, var_store, &mut scope, region, &loc_expr.value);
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
&mut scope,
|
|
||||||
region,
|
|
||||||
&loc_expr.value,
|
|
||||||
expr_expected,
|
|
||||||
);
|
|
||||||
|
|
||||||
// If we already recorded a tail call then keep it, else use this branch's tail call
|
// If we already recorded a tail call then keep it, else use this branch's tail call
|
||||||
match output.tail_call {
|
match output.tail_call {
|
||||||
|
@ -921,38 +626,17 @@ fn canonicalize_case_branch<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut state = PatternState {
|
|
||||||
headers: SendMap::default(),
|
|
||||||
vars: Vec::with_capacity(1),
|
|
||||||
constraints: Vec::with_capacity(1),
|
|
||||||
};
|
|
||||||
|
|
||||||
let loc_can_pattern = canonicalize_pattern(
|
let loc_can_pattern = canonicalize_pattern(
|
||||||
env,
|
env,
|
||||||
&mut state,
|
|
||||||
var_store,
|
var_store,
|
||||||
&mut scope,
|
&mut scope,
|
||||||
CaseBranch,
|
WhenBranch,
|
||||||
&loc_pattern.value,
|
&loc_pattern.value,
|
||||||
loc_pattern.region,
|
loc_pattern.region,
|
||||||
&mut shadowable_idents,
|
&mut shadowable_idents,
|
||||||
pattern_expected,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let constraint = Constraint::Let(Box::new(LetConstraint {
|
(loc_can_pattern, can_expr, branch_output.references)
|
||||||
rigid_vars: Vec::new(),
|
|
||||||
flex_vars: state.vars,
|
|
||||||
def_types: state.headers,
|
|
||||||
defs_constraint: Constraint::And(state.constraints),
|
|
||||||
ret_constraint,
|
|
||||||
}));
|
|
||||||
|
|
||||||
(
|
|
||||||
loc_can_pattern,
|
|
||||||
can_expr,
|
|
||||||
constraint,
|
|
||||||
branch_output.references,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn union_pairs<'a, K, V, I>(mut map: ImMap<K, V>, pairs: I) -> ImMap<K, V>
|
pub fn union_pairs<'a, K, V, I>(mut map: ImMap<K, V>, pairs: I) -> ImMap<K, V>
|
||||||
|
@ -1150,48 +834,38 @@ fn resolve_ident<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize_field<'a>(
|
fn canonicalize_field<'a>(
|
||||||
rigids: &Rigids,
|
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
field: &'a ast::AssignedField<'a, ast::Expr<'a>>,
|
field: &'a ast::AssignedField<'a, ast::Expr<'a>>,
|
||||||
region: Region,
|
region: Region,
|
||||||
) -> (Lowercase, Located<Expr>, Output, Variable, Type, Constraint) {
|
) -> (Lowercase, Located<Expr>, Output, Variable) {
|
||||||
use crate::parse::ast::AssignedField::*;
|
use crate::parse::ast::AssignedField::*;
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
// Both a label and a value, e.g. `{ name: "blah" }`
|
// Both a label and a value, e.g. `{ name: "blah" }`
|
||||||
LabeledValue(label, _, loc_expr) => {
|
LabeledValue(label, _, loc_expr) => {
|
||||||
let field_var = var_store.fresh();
|
let field_var = var_store.fresh();
|
||||||
let field_type = Variable(field_var);
|
let (loc_can_expr, output) =
|
||||||
let field_expected = NoExpectation(field_type.clone());
|
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
||||||
let (loc_can_expr, output, constraint) = canonicalize_expr(
|
|
||||||
rigids,
|
|
||||||
env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
loc_expr.region,
|
|
||||||
&loc_expr.value,
|
|
||||||
field_expected,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Lowercase::from(label.value),
|
Lowercase::from(label.value),
|
||||||
loc_can_expr,
|
loc_can_expr,
|
||||||
output,
|
output,
|
||||||
field_var,
|
field_var,
|
||||||
field_type,
|
|
||||||
constraint,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OptionalField(_, _, _) => panic!("invalid in expressions"),
|
||||||
|
|
||||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||||
LabelOnly(_) => {
|
LabelOnly(_) => {
|
||||||
panic!("Somehow a LabelOnly record field was not desugared!");
|
panic!("Somehow a LabelOnly record field was not desugared!");
|
||||||
}
|
}
|
||||||
|
|
||||||
SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => {
|
SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => {
|
||||||
canonicalize_field(rigids, env, var_store, scope, sub_field, region)
|
canonicalize_field(env, var_store, scope, sub_field, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
Malformed(_string) => {
|
Malformed(_string) => {
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
use crate::can::def::{canonicalize_defs, sort_can_defs, Def, Info};
|
use crate::can::def::{canonicalize_defs, sort_can_defs, Def};
|
||||||
use crate::can::env::Env;
|
use crate::can::env::Env;
|
||||||
use crate::can::expr::Output;
|
use crate::can::expr::Output;
|
||||||
use crate::can::operator::desugar_def;
|
use crate::can::operator::desugar_def;
|
||||||
use crate::can::scope::Scope;
|
use crate::can::scope::Scope;
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{ImMap, SendMap};
|
use crate::collections::SendMap;
|
||||||
use crate::parse::ast::{self, ExposesEntry};
|
use crate::parse::ast::{self, ExposesEntry};
|
||||||
use crate::region::Located;
|
use crate::region::{Located, Region};
|
||||||
use crate::subs::{VarStore, Variable};
|
use crate::subs::{VarStore, Variable};
|
||||||
use crate::types::Constraint::{self, *};
|
use crate::types::Constraint;
|
||||||
use crate::types::Expected::*;
|
|
||||||
use crate::types::Type;
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -21,6 +19,12 @@ pub struct Module {
|
||||||
pub constraint: Constraint,
|
pub constraint: Constraint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ModuleOutput {
|
||||||
|
pub defs: Vec<Def>,
|
||||||
|
pub exposed_imports: SendMap<Symbol, Variable>,
|
||||||
|
pub lookups: Vec<(Symbol, Variable, Region)>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn canonicalize_module_defs<'a, I>(
|
pub fn canonicalize_module_defs<'a, I>(
|
||||||
arena: &Bump,
|
arena: &Bump,
|
||||||
loc_defs: bumpalo::collections::Vec<'a, Located<ast::Def<'a>>>,
|
loc_defs: bumpalo::collections::Vec<'a, Located<ast::Def<'a>>>,
|
||||||
|
@ -28,7 +32,7 @@ pub fn canonicalize_module_defs<'a, I>(
|
||||||
_exposes: I,
|
_exposes: I,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
) -> (Vec<Def>, SendMap<Symbol, Variable>, Constraint)
|
) -> ModuleOutput
|
||||||
where
|
where
|
||||||
I: Iterator<Item = Located<ExposesEntry<'a>>>,
|
I: Iterator<Item = Located<ExposesEntry<'a>>>,
|
||||||
{
|
{
|
||||||
|
@ -52,8 +56,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut env = Env::new(home);
|
let mut env = Env::new(home);
|
||||||
let rigids = ImMap::default();
|
let mut lookups = Vec::with_capacity(scope.idents.len());
|
||||||
let mut flex_info = Info::default();
|
|
||||||
|
|
||||||
// Exposed values are treated like defs that appear before any others, e.g.
|
// Exposed values are treated like defs that appear before any others, e.g.
|
||||||
//
|
//
|
||||||
|
@ -76,30 +79,16 @@ where
|
||||||
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
||||||
exposed_imports.insert(scope.symbol(&*ident.clone().name()), expr_var);
|
exposed_imports.insert(scope.symbol(&*ident.clone().name()), expr_var);
|
||||||
|
|
||||||
// Add the usual Lookup constraint as if this were a normal def.
|
// This will be used during constraint generation,
|
||||||
let expr_type = Type::Variable(expr_var);
|
// to add the usual Lookup constraint as if this were a normal def.
|
||||||
let expected = NoExpectation(expr_type.clone());
|
lookups.push((symbol.clone(), expr_var, *region));
|
||||||
|
|
||||||
flex_info
|
|
||||||
.constraints
|
|
||||||
.push(Lookup(symbol.clone(), expected, *region));
|
|
||||||
} else {
|
} else {
|
||||||
// TODO add type aliases to type alias dictionary, based on exposed types
|
// TODO add type aliases to type alias dictionary, based on exposed types
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut found_rigids = SendMap::default();
|
let mut output = Output::default();
|
||||||
|
let defs = canonicalize_defs(&mut env, &mut output.rigids, var_store, scope, &desugared);
|
||||||
let defs = canonicalize_defs(
|
|
||||||
&rigids,
|
|
||||||
&mut found_rigids,
|
|
||||||
&mut env,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
&desugared,
|
|
||||||
&mut flex_info,
|
|
||||||
);
|
|
||||||
|
|
||||||
let defs = match sort_can_defs(&mut env, defs, Output::default()) {
|
let defs = match sort_can_defs(&mut env, defs, Output::default()) {
|
||||||
(Ok(defs), _) => {
|
(Ok(defs), _) => {
|
||||||
// TODO examine the patterns, extract toplevel identifiers from them,
|
// TODO examine the patterns, extract toplevel identifiers from them,
|
||||||
|
@ -116,5 +105,9 @@ where
|
||||||
// TODO incorporate rigids into here (possibly by making this be a Let instead
|
// TODO incorporate rigids into here (possibly by making this be a Let instead
|
||||||
// of an And)
|
// of an And)
|
||||||
|
|
||||||
(defs, exposed_imports, And(flex_info.constraints))
|
ModuleOutput {
|
||||||
|
defs,
|
||||||
|
exposed_imports,
|
||||||
|
lookups,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,8 @@ use crate::can::env::Env;
|
||||||
use crate::can::expr::Expr;
|
use crate::can::expr::Expr;
|
||||||
use crate::can::problem::Problem;
|
use crate::can::problem::Problem;
|
||||||
use crate::can::problem::RuntimeError::*;
|
use crate::can::problem::RuntimeError::*;
|
||||||
use crate::constrain;
|
|
||||||
use crate::parse::ast::Base;
|
use crate::parse::ast::Base;
|
||||||
use crate::region::Region;
|
|
||||||
use crate::subs::VarStore;
|
use crate::subs::VarStore;
|
||||||
use crate::types::Constraint::{self, *};
|
|
||||||
use crate::types::Expected;
|
|
||||||
use crate::types::Type;
|
|
||||||
use std::i64;
|
use std::i64;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -16,20 +11,15 @@ pub fn int_expr_from_result(
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
result: Result<i64, &str>,
|
result: Result<i64, &str>,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
expected: Expected<Type>,
|
) -> Expr {
|
||||||
region: Region,
|
|
||||||
) -> (Constraint, Expr) {
|
|
||||||
match result {
|
match result {
|
||||||
Ok(int) => (
|
Ok(int) => Expr::Int(var_store.fresh(), int),
|
||||||
constrain::int_literal(var_store, expected, region),
|
|
||||||
Expr::Int(int),
|
|
||||||
),
|
|
||||||
Err(raw) => {
|
Err(raw) => {
|
||||||
let runtime_error = IntOutsideRange(raw.into());
|
let runtime_error = IntOutsideRange(raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
(True, Expr::RuntimeError(runtime_error))
|
Expr::RuntimeError(runtime_error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,20 +29,15 @@ pub fn float_expr_from_result(
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
result: Result<f64, &str>,
|
result: Result<f64, &str>,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
expected: Expected<Type>,
|
) -> Expr {
|
||||||
region: Region,
|
|
||||||
) -> (Constraint, Expr) {
|
|
||||||
match result {
|
match result {
|
||||||
Ok(float) => (
|
Ok(float) => Expr::Float(var_store.fresh(), float),
|
||||||
constrain::float_literal(var_store, expected, region),
|
|
||||||
Expr::Float(float),
|
|
||||||
),
|
|
||||||
Err(raw) => {
|
Err(raw) => {
|
||||||
let runtime_error = FloatOutsideRange(raw.into());
|
let runtime_error = FloatOutsideRange(raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
(True, Expr::RuntimeError(runtime_error))
|
Expr::RuntimeError(runtime_error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||||
region: loc_expr.region,
|
region: loc_expr.region,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Case(loc_cond_expr, branches) | Nested(Case(loc_cond_expr, branches)) => {
|
When(loc_cond_expr, branches) | Nested(When(loc_cond_expr, branches)) => {
|
||||||
let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr));
|
let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr));
|
||||||
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
|
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||||
}
|
}
|
||||||
|
|
||||||
arena.alloc(Located {
|
arena.alloc(Located {
|
||||||
value: Case(loc_desugared_cond, desugared_branches),
|
value: When(loc_desugared_cond, desugared_branches),
|
||||||
region: loc_expr.region,
|
region: loc_expr.region,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||||
//
|
//
|
||||||
// becomes
|
// becomes
|
||||||
//
|
//
|
||||||
// case b when
|
// when b is
|
||||||
// False -> y
|
// False -> y
|
||||||
// _ -> x
|
// _ -> x
|
||||||
//
|
//
|
||||||
|
@ -272,7 +272,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||||
desugar_expr(
|
desugar_expr(
|
||||||
arena,
|
arena,
|
||||||
arena.alloc(Located {
|
arena.alloc(Located {
|
||||||
value: Case(condition, branches),
|
value: When(condition, branches),
|
||||||
region: loc_expr.region,
|
region: loc_expr.region,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -295,6 +295,7 @@ fn desugar_field<'a>(
|
||||||
spaces,
|
spaces,
|
||||||
desugar_expr(arena, loc_expr),
|
desugar_expr(arena, loc_expr),
|
||||||
),
|
),
|
||||||
|
OptionalField(_, _, _) => panic!("invalid in expressions"),
|
||||||
LabelOnly(loc_str) => {
|
LabelOnly(loc_str) => {
|
||||||
// Desugar { x } into { x: x }
|
// Desugar { x } into { x: x }
|
||||||
let loc_expr = Located {
|
let loc_expr = Located {
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
use crate::can::env::Env;
|
use crate::can::env::Env;
|
||||||
|
use crate::can::ident::Lowercase;
|
||||||
use crate::can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
use crate::can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
||||||
use crate::can::problem::Problem;
|
use crate::can::problem::Problem;
|
||||||
use crate::can::scope::Scope;
|
use crate::can::scope::Scope;
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{ImMap, SendMap};
|
use crate::collections::ImMap;
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::parse::ast;
|
use crate::parse::ast;
|
||||||
use crate::region::{Located, Region};
|
use crate::region::{Located, Region};
|
||||||
use crate::subs::VarStore;
|
use crate::subs::VarStore;
|
||||||
use crate::subs::Variable;
|
use crate::subs::Variable;
|
||||||
use crate::types::{Constraint, PExpected, PatternCategory, Type};
|
|
||||||
use im_rc::Vector;
|
use im_rc::Vector;
|
||||||
|
|
||||||
/// A pattern, including possible problems (e.g. shadowing) so that
|
/// A pattern, including possible problems (e.g. shadowing) so that
|
||||||
/// codegen can generate a runtime error if this pattern is reached.
|
/// codegen can generate a runtime error if this pattern is reached.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Pattern {
|
pub enum Pattern {
|
||||||
Identifier(Variable, Symbol),
|
Identifier(Symbol),
|
||||||
Tag(Variable, Symbol),
|
Tag(Symbol),
|
||||||
/// TODO replace regular Tag with this
|
/// TODO replace regular Tag with this
|
||||||
AppliedTag(Variable, Symbol, Vec<Located<Pattern>>),
|
AppliedTag(Symbol, Vec<Located<Pattern>>),
|
||||||
IntLiteral(i64),
|
IntLiteral(i64),
|
||||||
FloatLiteral(f64),
|
FloatLiteral(f64),
|
||||||
ExactString(Box<str>),
|
StrLiteral(Box<str>),
|
||||||
EmptyRecordLiteral(Variable),
|
RecordDestructure(Variable, Vec<RecordDestruct>),
|
||||||
Underscore(Variable),
|
Underscore,
|
||||||
|
|
||||||
// Runtime Exceptions
|
// Runtime Exceptions
|
||||||
Shadowed(Located<Ident>),
|
Shadowed(Located<Ident>),
|
||||||
|
@ -32,6 +32,14 @@ pub enum Pattern {
|
||||||
UnsupportedPattern(Region),
|
UnsupportedPattern(Region),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct RecordDestruct {
|
||||||
|
pub var: Variable,
|
||||||
|
pub label: Lowercase,
|
||||||
|
pub symbol: Symbol,
|
||||||
|
pub guard: Option<(Variable, Located<Pattern>)>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
|
pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
|
||||||
let mut symbols = Vec::new();
|
let mut symbols = Vec::new();
|
||||||
symbols_from_pattern_help(pattern, &mut symbols);
|
symbols_from_pattern_help(pattern, &mut symbols);
|
||||||
|
@ -40,41 +48,215 @@ pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||||
if let Pattern::Identifier(_, symbol) = pattern {
|
if let Pattern::Identifier(symbol) = pattern {
|
||||||
symbols.push(symbol.clone());
|
symbols.push(symbol.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Different patterns are supported in different circumstances.
|
/// Different patterns are supported in different circumstances.
|
||||||
/// For example, case branches can pattern match on number literals, but
|
/// For example, when branches can pattern match on number literals, but
|
||||||
/// assignments and function args can't. Underscore is supported in function
|
/// assignments and function args can't. Underscore is supported in function
|
||||||
/// arg patterns and in case branch patterns, but not in assignments.
|
/// arg patterns and in when branch patterns, but not in assignments.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum PatternType {
|
pub enum PatternType {
|
||||||
TopLevelDef,
|
TopLevelDef,
|
||||||
Assignment,
|
Assignment,
|
||||||
FunctionArg,
|
FunctionArg,
|
||||||
CaseBranch,
|
WhenBranch,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO trim down these arguments
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn canonicalize_pattern<'a>(
|
pub fn canonicalize_pattern<'a>(
|
||||||
env: &'a mut Env,
|
env: &'a mut Env,
|
||||||
state: &'a mut PatternState,
|
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
pattern_type: PatternType,
|
pattern_type: PatternType,
|
||||||
pattern: &'a ast::Pattern<'a>,
|
pattern: &'a ast::Pattern<'a>,
|
||||||
region: Region,
|
region: Region,
|
||||||
shadowable_idents: &'a mut ImMap<Ident, (Symbol, Region)>,
|
shadowable_idents: &'a mut ImMap<Ident, (Symbol, Region)>,
|
||||||
expected: PExpected<Type>,
|
|
||||||
) -> Located<Pattern> {
|
) -> Located<Pattern> {
|
||||||
use self::PatternType::*;
|
use self::PatternType::*;
|
||||||
use crate::parse::ast::Pattern::*;
|
use crate::parse::ast::Pattern::*;
|
||||||
|
|
||||||
let can_pattern = match &pattern {
|
let can_pattern = match &pattern {
|
||||||
&Identifier(ref name) => {
|
&Identifier(ref name) => {
|
||||||
|
match canonicalize_pattern_identifier(name, env, scope, region, shadowable_idents) {
|
||||||
|
Ok(symbol) => Pattern::Identifier(symbol),
|
||||||
|
Err(loc_shadowed_ident) => Pattern::Shadowed(loc_shadowed_ident),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&GlobalTag(name) => {
|
||||||
|
// Canonicalize the tag's name.
|
||||||
|
Pattern::Tag(Symbol::from_global_tag(name))
|
||||||
|
}
|
||||||
|
&PrivateTag(name) => {
|
||||||
|
// Canonicalize the tag's name.
|
||||||
|
Pattern::Tag(Symbol::from_private_tag(&env.home, name))
|
||||||
|
}
|
||||||
|
&FloatLiteral(ref string) => match pattern_type {
|
||||||
|
WhenBranch => {
|
||||||
|
let float = finish_parsing_float(string)
|
||||||
|
.unwrap_or_else(|_| panic!("TODO handle malformed float pattern"));
|
||||||
|
|
||||||
|
Pattern::FloatLiteral(float)
|
||||||
|
}
|
||||||
|
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||||
|
unsupported_pattern(env, ptype, region)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
&Underscore => match pattern_type {
|
||||||
|
WhenBranch | FunctionArg => Pattern::Underscore,
|
||||||
|
ptype @ Assignment | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region),
|
||||||
|
},
|
||||||
|
|
||||||
|
&IntLiteral(string) => match pattern_type {
|
||||||
|
WhenBranch => {
|
||||||
|
let int = finish_parsing_int(string)
|
||||||
|
.unwrap_or_else(|_| panic!("TODO handle malformed int pattern"));
|
||||||
|
|
||||||
|
Pattern::IntLiteral(int)
|
||||||
|
}
|
||||||
|
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||||
|
unsupported_pattern(env, ptype, region)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
&NonBase10Literal {
|
||||||
|
string,
|
||||||
|
base,
|
||||||
|
is_negative,
|
||||||
|
} => match pattern_type {
|
||||||
|
WhenBranch => {
|
||||||
|
let int = finish_parsing_base(string, *base)
|
||||||
|
.unwrap_or_else(|_| panic!("TODO handle malformed {:?} pattern", base));
|
||||||
|
|
||||||
|
if *is_negative {
|
||||||
|
Pattern::IntLiteral(-int)
|
||||||
|
} else {
|
||||||
|
Pattern::IntLiteral(int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||||
|
unsupported_pattern(env, ptype, region)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
&StrLiteral(_string) => match pattern_type {
|
||||||
|
WhenBranch => {
|
||||||
|
panic!("TODO check whether string pattern is malformed.");
|
||||||
|
// Pattern::StrLiteral((*string).into())
|
||||||
|
}
|
||||||
|
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||||
|
unsupported_pattern(env, ptype, region)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
&SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
|
||||||
|
return canonicalize_pattern(
|
||||||
|
env,
|
||||||
|
var_store,
|
||||||
|
scope,
|
||||||
|
pattern_type,
|
||||||
|
sub_pattern,
|
||||||
|
region,
|
||||||
|
shadowable_idents,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
&RecordDestructure(patterns) => {
|
||||||
|
let mut fields = Vec::with_capacity(patterns.len());
|
||||||
|
|
||||||
|
for loc_pattern in patterns {
|
||||||
|
match loc_pattern.value {
|
||||||
|
Identifier(label) => {
|
||||||
|
let symbol = match canonicalize_pattern_identifier(
|
||||||
|
&label,
|
||||||
|
env,
|
||||||
|
scope,
|
||||||
|
region,
|
||||||
|
shadowable_idents,
|
||||||
|
) {
|
||||||
|
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(RecordDestruct {
|
||||||
|
var: var_store.fresh(),
|
||||||
|
label: Lowercase::from(label),
|
||||||
|
symbol,
|
||||||
|
guard: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
RecordField(label, loc_guard) => {
|
||||||
|
let symbol = match canonicalize_pattern_identifier(
|
||||||
|
&label,
|
||||||
|
env,
|
||||||
|
scope,
|
||||||
|
region,
|
||||||
|
shadowable_idents,
|
||||||
|
) {
|
||||||
|
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(
|
||||||
|
env,
|
||||||
|
var_store,
|
||||||
|
scope,
|
||||||
|
pattern_type,
|
||||||
|
&loc_guard.value,
|
||||||
|
loc_guard.region,
|
||||||
|
shadowable_idents,
|
||||||
|
);
|
||||||
|
|
||||||
|
fields.push(RecordDestruct {
|
||||||
|
var: var_store.fresh(),
|
||||||
|
label: Lowercase::from(label),
|
||||||
|
symbol,
|
||||||
|
guard: Some((var_store.fresh(), can_guard)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => panic!("invalid pattern in record"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern::RecordDestructure(var_store.fresh(), fields)
|
||||||
|
}
|
||||||
|
&RecordField(_name, _loc_pattern) => {
|
||||||
|
unreachable!("should be handled in RecordDestructure");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern),
|
||||||
|
};
|
||||||
|
|
||||||
|
Located {
|
||||||
|
region,
|
||||||
|
value: can_pattern,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn canonicalize_pattern_identifier<'a>(
|
||||||
|
name: &'a &str,
|
||||||
|
env: &'a mut Env,
|
||||||
|
scope: &mut Scope,
|
||||||
|
region: Region,
|
||||||
|
shadowable_idents: &'a mut ImMap<Ident, (Symbol, Region)>,
|
||||||
|
) -> Result<Symbol, Located<Ident>> {
|
||||||
let lowercase_ident = Ident::Unqualified((*name).into());
|
let lowercase_ident = Ident::Unqualified((*name).into());
|
||||||
|
|
||||||
// We use shadowable_idents for this, and not scope, because for assignments
|
// We use shadowable_idents for this, and not scope, because for assignments
|
||||||
|
@ -97,12 +279,11 @@ pub fn canonicalize_pattern<'a>(
|
||||||
|
|
||||||
// Change this Pattern to a Shadowed variant, so that
|
// Change this Pattern to a Shadowed variant, so that
|
||||||
// codegen knows to generate a runtime exception here.
|
// codegen knows to generate a runtime exception here.
|
||||||
Pattern::Shadowed(loc_shadowed_ident)
|
Err(loc_shadowed_ident)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Make sure we aren't shadowing something in the home module's scope.
|
// Make sure we aren't shadowing something in the home module's scope.
|
||||||
let qualified_ident =
|
let qualified_ident = Ident::Qualified(env.home.clone(), lowercase_ident.name());
|
||||||
Ident::Qualified(env.home.clone(), lowercase_ident.name());
|
|
||||||
|
|
||||||
match scope.idents.get(&qualified_ident) {
|
match scope.idents.get(&qualified_ident) {
|
||||||
Some((_, region)) => {
|
Some((_, region)) => {
|
||||||
|
@ -117,7 +298,7 @@ pub fn canonicalize_pattern<'a>(
|
||||||
|
|
||||||
// Change this Pattern to a Shadowed variant, so that
|
// Change this Pattern to a Shadowed variant, so that
|
||||||
// codegen knows to generate a runtime exception here.
|
// codegen knows to generate a runtime exception here.
|
||||||
Pattern::Shadowed(loc_shadowed_ident)
|
Err(loc_shadowed_ident)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let new_ident = qualified_ident.clone();
|
let new_ident = qualified_ident.clone();
|
||||||
|
@ -132,113 +313,15 @@ pub fn canonicalize_pattern<'a>(
|
||||||
// The latter is relevant when recursively canonicalizing
|
// The latter is relevant when recursively canonicalizing
|
||||||
// tag application patterns, which can bring multiple
|
// tag application patterns, which can bring multiple
|
||||||
// new idents into scope. For example, it's important that
|
// new idents into scope. For example, it's important that
|
||||||
// we catch (Blah foo foo) as being an example of shadowing.
|
// we catch (Blah foo foo) -> … as being an example of shadowing.
|
||||||
scope
|
shadowable_idents.insert(new_ident.clone(), symbol_and_region.clone());
|
||||||
.idents
|
scope.idents.insert(new_ident, symbol_and_region);
|
||||||
.insert(new_ident.clone(), symbol_and_region.clone());
|
|
||||||
shadowable_idents.insert(new_ident, symbol_and_region);
|
|
||||||
|
|
||||||
Pattern::Identifier(var_store.fresh(), symbol)
|
Ok(symbol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
&GlobalTag(name) => {
|
|
||||||
// Canonicalize the tag's name.
|
|
||||||
let symbol = Symbol::from_global_tag(name);
|
|
||||||
|
|
||||||
Pattern::Tag(var_store.fresh(), symbol)
|
|
||||||
}
|
|
||||||
&PrivateTag(name) => {
|
|
||||||
// Canonicalize the tag's name.
|
|
||||||
let symbol = Symbol::from_private_tag(&env.home, name);
|
|
||||||
|
|
||||||
Pattern::Tag(var_store.fresh(), symbol)
|
|
||||||
}
|
|
||||||
&FloatLiteral(ref string) => match pattern_type {
|
|
||||||
CaseBranch => {
|
|
||||||
let float = finish_parsing_float(string)
|
|
||||||
.unwrap_or_else(|_| panic!("TODO handle malformed float pattern"));
|
|
||||||
|
|
||||||
Pattern::FloatLiteral(float)
|
|
||||||
}
|
|
||||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
|
||||||
unsupported_pattern(env, ptype, region)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
&Underscore => match pattern_type {
|
|
||||||
CaseBranch | FunctionArg => Pattern::Underscore(var_store.fresh()),
|
|
||||||
ptype @ Assignment | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region),
|
|
||||||
},
|
|
||||||
|
|
||||||
&IntLiteral(string) => match pattern_type {
|
|
||||||
CaseBranch => {
|
|
||||||
let int = finish_parsing_int(string)
|
|
||||||
.unwrap_or_else(|_| panic!("TODO handle malformed int pattern"));
|
|
||||||
|
|
||||||
Pattern::IntLiteral(int)
|
|
||||||
}
|
|
||||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
|
||||||
unsupported_pattern(env, ptype, region)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
&NonBase10Literal {
|
|
||||||
string,
|
|
||||||
base,
|
|
||||||
is_negative,
|
|
||||||
} => match pattern_type {
|
|
||||||
CaseBranch => {
|
|
||||||
let int = finish_parsing_base(string, *base)
|
|
||||||
.unwrap_or_else(|_| panic!("TODO handle malformed {:?} pattern", base));
|
|
||||||
|
|
||||||
if *is_negative {
|
|
||||||
Pattern::IntLiteral(-int)
|
|
||||||
} else {
|
|
||||||
Pattern::IntLiteral(int)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
|
||||||
unsupported_pattern(env, ptype, region)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
&StrLiteral(_string) => match pattern_type {
|
|
||||||
CaseBranch => {
|
|
||||||
panic!("TODO check whether string pattern is malformed.");
|
|
||||||
// Pattern::ExactString((*string).into())
|
|
||||||
}
|
|
||||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
|
||||||
unsupported_pattern(env, ptype, region)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// &EmptyRecordLiteral => Pattern::EmptyRecordLiteral,
|
|
||||||
&SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
|
|
||||||
return canonicalize_pattern(
|
|
||||||
env,
|
|
||||||
state,
|
|
||||||
var_store,
|
|
||||||
scope,
|
|
||||||
pattern_type,
|
|
||||||
sub_pattern,
|
|
||||||
region,
|
|
||||||
shadowable_idents,
|
|
||||||
expected,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern),
|
|
||||||
};
|
|
||||||
|
|
||||||
add_constraints(&pattern, &scope, region, expected, state);
|
|
||||||
|
|
||||||
Located {
|
|
||||||
region,
|
|
||||||
value: can_pattern,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
|
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
|
||||||
|
@ -249,87 +332,6 @@ fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region)
|
||||||
Pattern::UnsupportedPattern(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,
|
|
||||||
) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalTag(_)
|
|
||||||
| PrivateTag(_)
|
|
||||||
| Apply(_, _)
|
|
||||||
| RecordDestructure(_)
|
|
||||||
| RecordField(_, _)
|
|
||||||
| EmptyRecordLiteral => {
|
|
||||||
panic!("TODO add_constraints for {:?}", pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol, Region)>) {
|
pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol, Region)>) {
|
||||||
use crate::parse::ast::Pattern::*;
|
use crate::parse::ast::Pattern::*;
|
||||||
|
|
||||||
|
@ -348,11 +350,13 @@ pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol,
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
RecordDestructure(_) => {
|
RecordDestructure(patterns) => {
|
||||||
panic!("TODO implement RecordDestructure pattern in remove_idents.");
|
for loc_pattern in patterns {
|
||||||
|
remove_idents(&loc_pattern.value, idents);
|
||||||
}
|
}
|
||||||
RecordField(_, _) => {
|
}
|
||||||
panic!("TODO implement RecordField pattern in remove_idents.");
|
RecordField(_, loc_pattern) => {
|
||||||
|
remove_idents(&loc_pattern.value, idents);
|
||||||
}
|
}
|
||||||
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
||||||
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
||||||
|
@ -365,7 +369,6 @@ pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol,
|
||||||
| FloatLiteral(_)
|
| FloatLiteral(_)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| BlockStrLiteral(_)
|
| BlockStrLiteral(_)
|
||||||
| EmptyRecordLiteral
|
|
||||||
| Malformed(_)
|
| Malformed(_)
|
||||||
| Underscore => {}
|
| Underscore => {}
|
||||||
}
|
}
|
||||||
|
@ -417,11 +420,16 @@ fn add_idents_from_pattern<'a>(
|
||||||
// },
|
// },
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordDestructure(_) => {
|
RecordDestructure(patterns) => {
|
||||||
panic!("TODO implement RecordDestructure pattern in add_idents_from_pattern.");
|
for loc_pattern in patterns {
|
||||||
|
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer);
|
||||||
}
|
}
|
||||||
RecordField(_, _) => {
|
}
|
||||||
panic!("TODO implement RecordField pattern in add_idents_from_pattern.");
|
RecordField(name, loc_pattern) => {
|
||||||
|
let symbol = scope.symbol(&name);
|
||||||
|
|
||||||
|
answer.push_back((Ident::Unqualified((*name).into()), (symbol, *region)));
|
||||||
|
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer);
|
||||||
}
|
}
|
||||||
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
||||||
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
||||||
|
@ -434,7 +442,6 @@ fn add_idents_from_pattern<'a>(
|
||||||
| FloatLiteral(_)
|
| FloatLiteral(_)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| BlockStrLiteral(_)
|
| BlockStrLiteral(_)
|
||||||
| EmptyRecordLiteral
|
|
||||||
| Malformed(_)
|
| Malformed(_)
|
||||||
| Underscore => (),
|
| Underscore => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,34 @@
|
||||||
use crate::collections::SendMap;
|
|
||||||
use crate::region::Region;
|
use crate::region::Region;
|
||||||
use crate::subs::{VarStore, Variable};
|
use crate::subs::Variable;
|
||||||
use crate::types::Constraint::{self, *};
|
use crate::types::Constraint::{self, *};
|
||||||
use crate::types::Expected::{self, *};
|
use crate::types::Expected::{self, *};
|
||||||
use crate::types::Type::{self, *};
|
use crate::types::Type::{self, *};
|
||||||
use crate::types::{self, LetConstraint, Reason};
|
use crate::types::{self, Reason};
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
pub fn int_literal(var: Variable, expected: Expected<Type>, region: Region) -> 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 typ = number_literal_type("Int", "Integer");
|
||||||
let reason = Reason::IntLiteral;
|
let reason = Reason::IntLiteral;
|
||||||
|
|
||||||
num_literal(var_store, typ, reason, expected, region)
|
num_literal(var, typ, reason, expected, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn float_literal(var_store: &VarStore, expected: Expected<Type>, region: Region) -> Constraint {
|
pub fn float_literal(var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
|
||||||
let typ = number_literal_type("Float", "FloatingPoint");
|
let typ = number_literal_type("Float", "FloatingPoint");
|
||||||
let reason = Reason::FloatLiteral;
|
let reason = Reason::FloatLiteral;
|
||||||
|
|
||||||
num_literal(var_store, typ, reason, expected, region)
|
num_literal(var, typ, reason, expected, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn num_literal(
|
fn num_literal(
|
||||||
var_store: &VarStore,
|
num_var: Variable,
|
||||||
literal_type: Type,
|
literal_type: Type,
|
||||||
reason: Reason,
|
reason: Reason,
|
||||||
expected: Expected<Type>,
|
expected: Expected<Type>,
|
||||||
region: Region,
|
region: Region,
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
let num_var = var_store.fresh();
|
|
||||||
let num_type = Variable(num_var);
|
let num_type = Variable(num_var);
|
||||||
let expected_literal = ForReason(reason, literal_type, region);
|
let expected_literal = ForReason(reason, literal_type, region);
|
||||||
|
|
606
src/constrain/expr.rs
Normal file
606
src/constrain/expr.rs
Normal file
|
@ -0,0 +1,606 @@
|
||||||
|
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::RecordFieldLabel;
|
||||||
|
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(RecordFieldLabel::Required(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.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.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();
|
||||||
|
|
||||||
|
let label = RecordFieldLabel::Required(field.clone());
|
||||||
|
rec_field_types.insert(label, 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();
|
||||||
|
let label = RecordFieldLabel::Required(field.clone());
|
||||||
|
field_types.insert(label, 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(
|
||||||
|
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.expr_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]),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
}
|
4
src/constrain/mod.rs
Normal file
4
src/constrain/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod builtins;
|
||||||
|
pub mod expr;
|
||||||
|
pub mod module;
|
||||||
|
pub mod pattern;
|
31
src/constrain/module.rs
Normal file
31
src/constrain/module.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::can::def::Def;
|
||||||
|
use crate::can::symbol::Symbol;
|
||||||
|
use crate::collections::{ImMap, SendMap};
|
||||||
|
use crate::constrain::expr::{constrain_defs, Info};
|
||||||
|
use crate::region::Region;
|
||||||
|
use crate::subs::Variable;
|
||||||
|
use crate::types::Constraint::{self, *};
|
||||||
|
use crate::types::Expected::*;
|
||||||
|
use crate::types::Type;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn constrain_module(defs: &[Def], lookups: Vec<(Symbol, Variable, Region)>) -> Constraint {
|
||||||
|
let mut flex_info = Info::default();
|
||||||
|
|
||||||
|
for (symbol, expr_var, region) in lookups {
|
||||||
|
// Add the usual Lookup constraint as if this were a normal def.
|
||||||
|
let expr_type = Type::Variable(expr_var);
|
||||||
|
let expected = NoExpectation(expr_type.clone());
|
||||||
|
|
||||||
|
flex_info.constraints.push(Lookup(symbol, expected, region));
|
||||||
|
}
|
||||||
|
|
||||||
|
constrain_defs(
|
||||||
|
&ImMap::default(),
|
||||||
|
&mut SendMap::default(),
|
||||||
|
&defs,
|
||||||
|
&mut flex_info,
|
||||||
|
);
|
||||||
|
|
||||||
|
Constraint::And(flex_info.constraints)
|
||||||
|
}
|
112
src/constrain/pattern.rs
Normal file
112
src/constrain/pattern.rs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
use crate::can::pattern::Pattern::{self, *};
|
||||||
|
use crate::can::pattern::RecordDestruct;
|
||||||
|
use crate::can::symbol::Symbol;
|
||||||
|
use crate::collections::SendMap;
|
||||||
|
use crate::region::{Located, Region};
|
||||||
|
use crate::subs::Variable;
|
||||||
|
use crate::types::{Constraint, PExpected, PatternCategory, RecordFieldLabel, 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<RecordFieldLabel, Type> = SendMap::default();
|
||||||
|
|
||||||
|
for RecordDestruct {
|
||||||
|
var,
|
||||||
|
label,
|
||||||
|
symbol,
|
||||||
|
guard,
|
||||||
|
} in patterns
|
||||||
|
{
|
||||||
|
let pat_type = Type::Variable(*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(RecordFieldLabel::Required(label.clone()), pat_type.clone());
|
||||||
|
|
||||||
|
// TODO investigate: shouldn't guard_var be constrained somewhere?
|
||||||
|
if let Some((_guard_var, loc_guard)) = guard {
|
||||||
|
constrain_pattern(&loc_guard.value, loc_guard.region, expected, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.vars.push(*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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,11 +56,7 @@ where
|
||||||
D::Value: fmt::Debug,
|
D::Value: fmt::Debug,
|
||||||
{
|
{
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt.debug_struct("SnapshotVec")
|
self.values.fmt(fmt)
|
||||||
.field("values", &self.values)
|
|
||||||
.field("undo_log", &self.undo_log)
|
|
||||||
.field("num_open_snapshots", &self.num_open_snapshots)
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::ena::snapshot_vec as sv;
|
use crate::ena::snapshot_vec as sv;
|
||||||
#[cfg(feature = "persistent")]
|
#[cfg(feature = "persistent")]
|
||||||
use im_rc::Vector;
|
use im_rc::Vector;
|
||||||
use std::fmt::Debug;
|
use std::fmt::{self, Debug};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::ops::{self, Range};
|
use std::ops::{self, Range};
|
||||||
|
|
||||||
|
@ -50,11 +50,21 @@ pub trait UnificationStore:
|
||||||
|
|
||||||
/// Backing store for an in-place unification table.
|
/// Backing store for an in-place unification table.
|
||||||
/// Not typically used directly.
|
/// Not typically used directly.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct InPlace<K: UnifyKey> {
|
pub struct InPlace<K: UnifyKey> {
|
||||||
values: sv::SnapshotVec<Delegate<K>>,
|
values: sv::SnapshotVec<Delegate<K>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<K> fmt::Debug for InPlace<K>
|
||||||
|
where
|
||||||
|
K: UnifyKey,
|
||||||
|
K: Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.values.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HACK(eddyb) manual impl avoids `Default` bound on `K`.
|
// HACK(eddyb) manual impl avoids `Default` bound on `K`.
|
||||||
impl<K: UnifyKey> Default for InPlace<K> {
|
impl<K: UnifyKey> Default for InPlace<K> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
//! The best way to see how it is used is to read the `tests.rs` file;
|
//! The best way to see how it is used is to read the `tests.rs` file;
|
||||||
//! search for e.g. `UnitKey`.
|
//! search for e.g. `UnitKey`.
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::{self, Debug};
|
||||||
use std::marker;
|
use std::marker;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ pub struct NoError {
|
||||||
/// to keep the DAG relatively balanced, which helps keep the running
|
/// to keep the DAG relatively balanced, which helps keep the running
|
||||||
/// time of the algorithm under control. For more information, see
|
/// time of the algorithm under control. For more information, see
|
||||||
/// <http://en.wikipedia.org/wiki/Disjoint-set_data_structure>.
|
/// <http://en.wikipedia.org/wiki/Disjoint-set_data_structure>.
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone)]
|
||||||
pub struct VarValue<K: UnifyKey> {
|
pub struct VarValue<K: UnifyKey> {
|
||||||
// FIXME pub
|
// FIXME pub
|
||||||
parent: K, // if equal to self, this is a root
|
parent: K, // if equal to self, this is a root
|
||||||
|
@ -108,6 +108,15 @@ pub struct VarValue<K: UnifyKey> {
|
||||||
rank: u32, // max depth (only relevant to root)
|
rank: u32, // max depth (only relevant to root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<K> fmt::Debug for VarValue<K>
|
||||||
|
where
|
||||||
|
K: UnifyKey,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "p: {:?}, c: {:?}", self.parent, self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Table of unification keys and their values. You must define a key type K
|
/// Table of unification keys and their values. You must define a key type K
|
||||||
/// that implements the `UnifyKey` trait. Unification tables can be used in two-modes:
|
/// that implements the `UnifyKey` trait. Unification tables can be used in two-modes:
|
||||||
///
|
///
|
||||||
|
@ -121,12 +130,22 @@ pub struct VarValue<K: UnifyKey> {
|
||||||
/// cloning the table is an O(1) operation.
|
/// cloning the table is an O(1) operation.
|
||||||
/// - This implies that ordinary operations are quite a bit slower though.
|
/// - This implies that ordinary operations are quite a bit slower though.
|
||||||
/// - Requires the `persistent` feature be selected in your Cargo.toml file.
|
/// - Requires the `persistent` feature be selected in your Cargo.toml file.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct UnificationTable<S: UnificationStore> {
|
pub struct UnificationTable<S: UnificationStore> {
|
||||||
/// Indicates the current value of each key.
|
/// Indicates the current value of each key.
|
||||||
values: S,
|
values: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> fmt::Debug for UnificationTable<S>
|
||||||
|
where
|
||||||
|
S: UnificationStore,
|
||||||
|
S: Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.values.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A unification table that uses an "in-place" vector.
|
/// A unification table that uses an "in-place" vector.
|
||||||
#[allow(type_alias_bounds)]
|
#[allow(type_alias_bounds)]
|
||||||
pub type InPlaceUnificationTable<K: UnifyKey> = UnificationTable<InPlace<K>>;
|
pub type InPlaceUnificationTable<K: UnifyKey> = UnificationTable<InPlace<K>>;
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) {
|
||||||
Body(loc_pattern, loc_expr) => {
|
Body(loc_pattern, loc_expr) => {
|
||||||
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||||
buf.push_str(" = ");
|
buf.push_str(" = ");
|
||||||
fmt_expr(buf, &loc_expr.value, indent, false);
|
fmt_expr(buf, &loc_expr.value, indent, false, true);
|
||||||
}
|
}
|
||||||
TypedDef(_loc_pattern, _loc_annotation, _loc_expr) => {
|
TypedDef(_loc_pattern, _loc_annotation, _loc_expr) => {
|
||||||
panic!("TODO support Annotation in TypedDef");
|
panic!("TODO support Annotation in TypedDef");
|
||||||
|
|
127
src/fmt/expr.rs
127
src/fmt/expr.rs
|
@ -10,21 +10,30 @@ pub fn fmt_expr<'a>(
|
||||||
expr: &'a Expr<'a>,
|
expr: &'a Expr<'a>,
|
||||||
indent: u16,
|
indent: u16,
|
||||||
apply_needs_parens: bool,
|
apply_needs_parens: bool,
|
||||||
|
format_newlines: bool,
|
||||||
) {
|
) {
|
||||||
use self::Expr::*;
|
use self::Expr::*;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
SpaceBefore(sub_expr, spaces) => {
|
SpaceBefore(sub_expr, spaces) => {
|
||||||
|
if format_newlines {
|
||||||
fmt_spaces(buf, spaces.iter(), indent);
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
fmt_expr(buf, sub_expr, indent, apply_needs_parens);
|
} else {
|
||||||
|
fmt_comments_only(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
|
fmt_expr(buf, sub_expr, indent, apply_needs_parens, format_newlines);
|
||||||
}
|
}
|
||||||
SpaceAfter(sub_expr, spaces) => {
|
SpaceAfter(sub_expr, spaces) => {
|
||||||
fmt_expr(buf, sub_expr, indent, apply_needs_parens);
|
fmt_expr(buf, sub_expr, indent, apply_needs_parens, format_newlines);
|
||||||
|
if format_newlines {
|
||||||
fmt_spaces(buf, spaces.iter(), indent);
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
} else {
|
||||||
|
fmt_comments_only(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ParensAround(sub_expr) => {
|
ParensAround(sub_expr) => {
|
||||||
buf.push('(');
|
buf.push('(');
|
||||||
fmt_expr(buf, sub_expr, indent, false);
|
fmt_expr(buf, sub_expr, indent, false, true);
|
||||||
buf.push(')');
|
buf.push(')');
|
||||||
}
|
}
|
||||||
Str(string) => {
|
Str(string) => {
|
||||||
|
@ -45,12 +54,12 @@ pub fn fmt_expr<'a>(
|
||||||
buf.push('(');
|
buf.push('(');
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt_expr(buf, &loc_expr.value, indent, true);
|
fmt_expr(buf, &loc_expr.value, indent, true, true);
|
||||||
|
|
||||||
for loc_arg in loc_args {
|
for loc_arg in loc_args {
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
|
|
||||||
fmt_expr(buf, &loc_arg.value, indent, true);
|
fmt_expr(buf, &loc_arg.value, indent, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if apply_needs_parens {
|
if apply_needs_parens {
|
||||||
|
@ -118,20 +127,18 @@ pub fn fmt_expr<'a>(
|
||||||
|
|
||||||
// Even if there were no defs, which theoretically should never happen,
|
// Even if there were no defs, which theoretically should never happen,
|
||||||
// still print the return value.
|
// still print the return value.
|
||||||
fmt_expr(buf, &ret.value, indent, false);
|
fmt_expr(buf, &ret.value, indent, false, true);
|
||||||
}
|
}
|
||||||
If((loc_condition, loc_then, loc_else)) => {
|
If((loc_condition, loc_then, loc_else)) => {
|
||||||
buf.push_str("if ");
|
fmt_if(buf, loc_condition, loc_then, loc_else, indent);
|
||||||
fmt_expr(buf, &loc_condition.value, indent, false);
|
|
||||||
buf.push_str(" then ");
|
|
||||||
fmt_expr(buf, &loc_then.value, indent, false);
|
|
||||||
buf.push_str(" else ");
|
|
||||||
fmt_expr(buf, &loc_else.value, indent, false);
|
|
||||||
}
|
}
|
||||||
Case(loc_condition, branches) => {
|
When(loc_condition, branches) => {
|
||||||
buf.push_str("case ");
|
buf.push_str(
|
||||||
fmt_expr(buf, &loc_condition.value, indent, false);
|
"\
|
||||||
buf.push_str(" when\n");
|
when ",
|
||||||
|
);
|
||||||
|
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
||||||
|
buf.push_str(" is\n");
|
||||||
|
|
||||||
let mut it = branches.iter().peekable();
|
let mut it = branches.iter().peekable();
|
||||||
while let Some((pattern, expr)) = it.next() {
|
while let Some((pattern, expr)) = it.next() {
|
||||||
|
@ -153,10 +160,10 @@ pub fn fmt_expr<'a>(
|
||||||
match expr.value {
|
match expr.value {
|
||||||
Expr::SpaceBefore(nested, spaces) => {
|
Expr::SpaceBefore(nested, spaces) => {
|
||||||
fmt_comments_only(buf, spaces.iter(), indent + (INDENT * 2));
|
fmt_comments_only(buf, spaces.iter(), indent + (INDENT * 2));
|
||||||
fmt_expr(buf, &nested, indent + (INDENT * 2), false);
|
fmt_expr(buf, &nested, indent + (INDENT * 2), false, true);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
fmt_expr(buf, &expr.value, indent + (INDENT * 2), false);
|
fmt_expr(buf, &expr.value, indent + (INDENT * 2), false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +173,25 @@ pub fn fmt_expr<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
List(loc_items) => {
|
||||||
|
buf.push('[');
|
||||||
|
|
||||||
|
let mut iter = loc_items.iter().peekable();
|
||||||
|
|
||||||
|
while let Some(item) = iter.next() {
|
||||||
|
buf.push(' ');
|
||||||
|
fmt_expr(buf, &item.value, indent, false, true);
|
||||||
|
|
||||||
|
if iter.peek().is_some() {
|
||||||
|
buf.push(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loc_items.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
buf.push(']');
|
||||||
|
}
|
||||||
other => panic!("TODO implement Display for AST variant {:?}", other),
|
other => panic!("TODO implement Display for AST variant {:?}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,7 +219,23 @@ pub fn fmt_field<'a>(
|
||||||
|
|
||||||
buf.push(':');
|
buf.push(':');
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
fmt_expr(buf, &value.value, indent, apply_needs_parens);
|
fmt_expr(buf, &value.value, indent, apply_needs_parens, true);
|
||||||
|
}
|
||||||
|
OptionalField(name, spaces, value) => {
|
||||||
|
if is_multiline {
|
||||||
|
newline(buf, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(name.value);
|
||||||
|
buf.push('?');
|
||||||
|
|
||||||
|
if !spaces.is_empty() {
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push(':');
|
||||||
|
buf.push(' ');
|
||||||
|
fmt_expr(buf, &value.value, indent, apply_needs_parens, true);
|
||||||
}
|
}
|
||||||
LabelOnly(name) => {
|
LabelOnly(name) => {
|
||||||
if is_multiline {
|
if is_multiline {
|
||||||
|
@ -261,7 +303,6 @@ pub fn is_multiline_pattern<'a>(pattern: &'a Pattern<'a>) -> bool {
|
||||||
| Pattern::FloatLiteral(_)
|
| Pattern::FloatLiteral(_)
|
||||||
| Pattern::StrLiteral(_)
|
| Pattern::StrLiteral(_)
|
||||||
| Pattern::BlockStrLiteral(_)
|
| Pattern::BlockStrLiteral(_)
|
||||||
| Pattern::EmptyRecordLiteral
|
|
||||||
| Pattern::Underscore
|
| Pattern::Underscore
|
||||||
| Pattern::Malformed(_)
|
| Pattern::Malformed(_)
|
||||||
| Pattern::QualifiedIdentifier(_) => false,
|
| Pattern::QualifiedIdentifier(_) => false,
|
||||||
|
@ -293,7 +334,7 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool {
|
||||||
| PrivateTag(_) => false,
|
| PrivateTag(_) => false,
|
||||||
|
|
||||||
// These expressions always have newlines
|
// These expressions always have newlines
|
||||||
Defs(_, _) | Case(_, _) => true,
|
Defs(_, _) | When(_, _) => true,
|
||||||
|
|
||||||
List(elems) => elems
|
List(elems) => elems
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -340,12 +381,54 @@ pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool {
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
LabeledValue(_, spaces, _) => !spaces.is_empty(),
|
LabeledValue(_, spaces, _) => !spaces.is_empty(),
|
||||||
|
OptionalField(_, spaces, _) => !spaces.is_empty(),
|
||||||
LabelOnly(_) => false,
|
LabelOnly(_) => false,
|
||||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
||||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fmt_if<'a>(
|
||||||
|
buf: &mut String<'a>,
|
||||||
|
loc_condition: &'a Located<Expr<'a>>,
|
||||||
|
loc_then: &'a Located<Expr<'a>>,
|
||||||
|
loc_else: &'a Located<Expr<'a>>,
|
||||||
|
indent: u16,
|
||||||
|
) {
|
||||||
|
let is_multiline_then = is_multiline_expr(&loc_then.value);
|
||||||
|
let is_multiline_else = is_multiline_expr(&loc_else.value);
|
||||||
|
let is_multiline = is_multiline_then || is_multiline_else;
|
||||||
|
|
||||||
|
buf.push_str("if ");
|
||||||
|
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
||||||
|
buf.push_str(" then");
|
||||||
|
|
||||||
|
let return_indent = if is_multiline {
|
||||||
|
indent + INDENT
|
||||||
|
} else {
|
||||||
|
indent
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_multiline {
|
||||||
|
newline(buf, return_indent);
|
||||||
|
} else {
|
||||||
|
buf.push_str(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_expr(buf, &loc_then.value, return_indent, false, false);
|
||||||
|
|
||||||
|
if is_multiline {
|
||||||
|
buf.push('\n');
|
||||||
|
newline(buf, indent);
|
||||||
|
buf.push_str("else");
|
||||||
|
newline(buf, return_indent);
|
||||||
|
} else {
|
||||||
|
buf.push_str(" else ");
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_expr(buf, &loc_else.value, return_indent, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fmt_closure<'a>(
|
pub fn fmt_closure<'a>(
|
||||||
buf: &mut String<'a>,
|
buf: &mut String<'a>,
|
||||||
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>,
|
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>,
|
||||||
|
@ -408,7 +491,7 @@ pub fn fmt_closure<'a>(
|
||||||
buf.push(' ');
|
buf.push(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt_expr(buf, &loc_ret.value, indent, false);
|
fmt_expr(buf, &loc_ret.value, indent, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_record<'a>(
|
pub fn fmt_record<'a>(
|
||||||
|
|
|
@ -82,7 +82,6 @@ pub fn fmt_pattern<'a>(
|
||||||
buf.push_str(line)
|
buf.push_str(line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EmptyRecordLiteral => buf.push_str("{}"),
|
|
||||||
Underscore => buf.push('_'),
|
Underscore => buf.push('_'),
|
||||||
|
|
||||||
// Space
|
// Space
|
||||||
|
|
|
@ -43,10 +43,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LineComment(comment) => {
|
LineComment(comment) => {
|
||||||
buf.push('#');
|
fmt_comment(buf, comment, indent);
|
||||||
buf.push_str(comment);
|
|
||||||
|
|
||||||
newline(buf, indent);
|
|
||||||
|
|
||||||
// Reset to 1 because we just printed a \n
|
// Reset to 1 because we just printed a \n
|
||||||
consecutive_newlines = 1;
|
consecutive_newlines = 1;
|
||||||
|
@ -66,11 +63,15 @@ where
|
||||||
match space {
|
match space {
|
||||||
Newline => {}
|
Newline => {}
|
||||||
LineComment(comment) => {
|
LineComment(comment) => {
|
||||||
buf.push('#');
|
fmt_comment(buf, comment, indent);
|
||||||
buf.push_str(comment);
|
|
||||||
|
|
||||||
newline(buf, indent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str, indent: u16) {
|
||||||
|
buf.push('#');
|
||||||
|
buf.push_str(comment);
|
||||||
|
|
||||||
|
newline(buf, indent);
|
||||||
|
}
|
||||||
|
|
|
@ -116,28 +116,33 @@ fn compile_expr<'ctx, 'env>(
|
||||||
use crate::can::expr::Expr::*;
|
use crate::can::expr::Expr::*;
|
||||||
|
|
||||||
match *expr {
|
match *expr {
|
||||||
Int(num) => IntConst(env.context.i64_type().const_int(num as u64, false)),
|
Int(_, num) => IntConst(env.context.i64_type().const_int(num as u64, false)),
|
||||||
Float(num) => FloatConst(env.context.f64_type().const_float(num)),
|
Float(_, num) => FloatConst(env.context.f64_type().const_float(num)),
|
||||||
Case(_, ref loc_cond_expr, ref branches) => {
|
When {
|
||||||
|
ref loc_cond,
|
||||||
|
ref branches,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
if branches.len() < 2 {
|
if branches.len() < 2 {
|
||||||
panic!("TODO support case-expressions of fewer than 2 branches.");
|
panic!("TODO support when-expressions of fewer than 2 branches.");
|
||||||
}
|
}
|
||||||
if branches.len() == 2 {
|
if branches.len() == 2 {
|
||||||
let mut iter = branches.iter();
|
let mut iter = branches.iter();
|
||||||
|
|
||||||
let (pattern, branch_expr) = iter.next().unwrap();
|
let (pattern, branch_expr) = iter.next().unwrap();
|
||||||
|
let (_, else_expr) = iter.next().unwrap();
|
||||||
|
|
||||||
compile_case_branch(
|
compile_when_branch(
|
||||||
env,
|
env,
|
||||||
parent,
|
parent,
|
||||||
&loc_cond_expr.value,
|
&loc_cond.value,
|
||||||
pattern.value.clone(),
|
pattern.value.clone(),
|
||||||
&branch_expr.value,
|
&branch_expr.value,
|
||||||
&iter.next().unwrap().1.value,
|
&else_expr.value,
|
||||||
vars,
|
vars,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
panic!("TODO support case-expressions of more than 2 branches.");
|
panic!("TODO support when-expressions of more than 2 branches.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -155,7 +160,7 @@ pub struct Env<'ctx, 'env> {
|
||||||
pub module: &'env Module<'ctx>,
|
pub module: &'env Module<'ctx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_case_branch<'ctx, 'env>(
|
fn compile_when_branch<'ctx, 'env>(
|
||||||
env: &Env<'ctx, 'env>,
|
env: &Env<'ctx, 'env>,
|
||||||
parent: &FunctionValue<'ctx>,
|
parent: &FunctionValue<'ctx>,
|
||||||
cond_expr: &Expr,
|
cond_expr: &Expr,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Adapted from the Pathfinding crate by Samuel Tardieu <sam@rfc1149.net>,
|
// Adapted from the Pathfinding crate v2.0.3 by Samuel Tardieu <sam@rfc1149.net>,
|
||||||
// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
|
// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
// The original source code can be found at: https://github.com/samueltardieu/pathfinding
|
// The original source code can be found at: https://github.com/samueltardieu/pathfinding
|
||||||
|
@ -217,12 +217,12 @@ where
|
||||||
let remaining: Vec<N> = preds_map.into_iter().map(|(node, _)| node).collect();
|
let remaining: Vec<N> = preds_map.into_iter().map(|(node, _)| node).collect();
|
||||||
return Err((Vec::new(), remaining));
|
return Err((Vec::new(), remaining));
|
||||||
}
|
}
|
||||||
for node in prev_group.iter() {
|
for node in &prev_group {
|
||||||
preds_map.remove(node);
|
preds_map.remove(node);
|
||||||
}
|
}
|
||||||
while !preds_map.is_empty() {
|
while !preds_map.is_empty() {
|
||||||
let mut next_group = Vec::<N>::new();
|
let mut next_group = Vec::<N>::new();
|
||||||
for node in prev_group.iter() {
|
for node in &prev_group {
|
||||||
for succ in &succs_map[node] {
|
for succ in &succs_map[node] {
|
||||||
{
|
{
|
||||||
let num_preds = preds_map.get_mut(succ).unwrap();
|
let num_preds = preds_map.get_mut(succ).unwrap();
|
||||||
|
@ -273,7 +273,7 @@ where
|
||||||
IN: IntoIterator<Item = N>,
|
IN: IntoIterator<Item = N>,
|
||||||
{
|
{
|
||||||
fn new(nodes: &[N], successors: FN) -> Self {
|
fn new(nodes: &[N], successors: FN) -> Self {
|
||||||
Params {
|
Self {
|
||||||
preorders: nodes.iter().map(|n| (n.clone(), None)).collect::<HashMap<
|
preorders: nodes.iter().map(|n| (n.clone(), None)).collect::<HashMap<
|
||||||
N,
|
N,
|
||||||
Option<usize>,
|
Option<usize>,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::can::def::Def;
|
use crate::can::def::Def;
|
||||||
use crate::can::module::{canonicalize_module_defs, Module};
|
use crate::can::module::{canonicalize_module_defs, Module, ModuleOutput};
|
||||||
use crate::can::scope::Scope;
|
use crate::can::scope::Scope;
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{ImMap, SendMap, SendSet};
|
use crate::collections::{ImMap, SendMap, SendSet};
|
||||||
|
use crate::constrain::module::constrain_module;
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::module::ModuleName;
|
use crate::module::ModuleName;
|
||||||
use crate::parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
|
use crate::parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
|
||||||
|
@ -217,8 +218,7 @@ fn load_filename(
|
||||||
|
|
||||||
let mut scope =
|
let mut scope =
|
||||||
Scope::new(format!("{}.", declared_name).into(), scope_from_imports);
|
Scope::new(format!("{}.", declared_name).into(), scope_from_imports);
|
||||||
|
let (defs, exposed_imports, constraint) = process_defs(
|
||||||
let (defs, exposed_imports, constraint) = parse_and_canonicalize_defs(
|
|
||||||
&arena,
|
&arena,
|
||||||
state,
|
state,
|
||||||
declared_name.clone(),
|
declared_name.clone(),
|
||||||
|
@ -259,7 +259,7 @@ fn load_filename(
|
||||||
let mut scope = Scope::new(".".into(), scope_from_imports);
|
let mut scope = Scope::new(".".into(), scope_from_imports);
|
||||||
|
|
||||||
// The app module has no declared name. Pass it as "".
|
// The app module has no declared name. Pass it as "".
|
||||||
let (defs, exposed_imports, constraint) = parse_and_canonicalize_defs(
|
let (defs, exposed_imports, constraint) = process_defs(
|
||||||
&arena,
|
&arena,
|
||||||
state,
|
state,
|
||||||
"".into(),
|
"".into(),
|
||||||
|
@ -288,7 +288,7 @@ fn load_filename(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_and_canonicalize_defs<'a, I>(
|
fn process_defs<'a, I>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
state: State<'a>,
|
state: State<'a>,
|
||||||
home: Box<str>,
|
home: Box<str>,
|
||||||
|
@ -303,7 +303,15 @@ where
|
||||||
.parse(arena, state)
|
.parse(arena, state)
|
||||||
.expect("TODO gracefully handle parse error on module defs");
|
.expect("TODO gracefully handle parse error on module defs");
|
||||||
|
|
||||||
canonicalize_module_defs(arena, parsed_defs, home, exposes, scope, var_store)
|
let ModuleOutput {
|
||||||
|
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(
|
fn load_import(
|
||||||
|
@ -362,17 +370,17 @@ pub fn solve_loaded(
|
||||||
use LoadedModule::*;
|
use LoadedModule::*;
|
||||||
|
|
||||||
let mut vars_by_symbol: ImMap<Symbol, Variable> = ImMap::default();
|
let mut vars_by_symbol: ImMap<Symbol, Variable> = ImMap::default();
|
||||||
let mut constraints = Vec::with_capacity(loaded_deps.len() + 1);
|
let mut dep_constraints = Vec::with_capacity(loaded_deps.len());
|
||||||
|
|
||||||
// All the exposed imports should be available in the solver's vars_by_symbol
|
// All the exposed imports should be available in the solver's vars_by_symbol
|
||||||
for (symbol, var) in module.exposed_imports.iter() {
|
for (symbol, expr_var) in im::HashMap::clone(&module.exposed_imports) {
|
||||||
vars_by_symbol.insert(symbol.clone(), var.clone());
|
vars_by_symbol.insert(symbol, expr_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All the top-level defs should also be available in vars_by_symbol
|
// All the top-level defs should also be available in vars_by_symbol
|
||||||
for def in module.defs.iter() {
|
for def in module.defs.iter() {
|
||||||
for (symbol, var) in def.variables_by_symbol.iter() {
|
for (symbol, var) in im::HashMap::clone(&def.pattern_vars) {
|
||||||
vars_by_symbol.insert(symbol.clone(), var.clone());
|
vars_by_symbol.insert(symbol, var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +388,7 @@ pub fn solve_loaded(
|
||||||
// to solve, looking up qualified idents gets the correct answer.
|
// to solve, looking up qualified idents gets the correct answer.
|
||||||
//
|
//
|
||||||
// TODO filter these by what's actually exposed; don't add it to the Env
|
// TODO filter these by what's actually exposed; don't add it to the Env
|
||||||
// unless the module actually exposes it!
|
// unless the other module actually exposes it!
|
||||||
for loaded_dep in loaded_deps {
|
for loaded_dep in loaded_deps {
|
||||||
match loaded_dep {
|
match loaded_dep {
|
||||||
Valid(valid_dep) => {
|
Valid(valid_dep) => {
|
||||||
|
@ -388,18 +396,18 @@ pub fn solve_loaded(
|
||||||
// in the solver's vars_by_symbol. (The map's keys are
|
// in the solver's vars_by_symbol. (The map's keys are
|
||||||
// fully qualified, so there won't be any collisions
|
// fully qualified, so there won't be any collisions
|
||||||
// with the primary module's exposed imports!)
|
// with the primary module's exposed imports!)
|
||||||
for (symbol, var) in valid_dep.exposed_imports {
|
for (symbol, expr_var) in valid_dep.exposed_imports {
|
||||||
vars_by_symbol.insert(symbol, var);
|
vars_by_symbol.insert(symbol, expr_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
constraints.push(valid_dep.constraint);
|
|
||||||
|
|
||||||
// All its top-level defs should also be available in vars_by_symbol
|
// All its top-level defs should also be available in vars_by_symbol
|
||||||
for def in valid_dep.defs {
|
for def in valid_dep.defs {
|
||||||
for (symbol, var) in def.variables_by_symbol {
|
for (symbol, var) in def.pattern_vars {
|
||||||
vars_by_symbol.insert(symbol, var);
|
vars_by_symbol.insert(symbol, var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dep_constraints.push(valid_dep.constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
broken @ FileProblem { .. } => {
|
broken @ FileProblem { .. } => {
|
||||||
|
@ -412,8 +420,8 @@ pub fn solve_loaded(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for constraint in constraints {
|
for dep_constraint in dep_constraints {
|
||||||
solve::run(&vars_by_symbol, problems, subs, &constraint);
|
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);
|
||||||
|
|
|
@ -153,7 +153,7 @@ pub enum Expr<'a> {
|
||||||
|
|
||||||
// Conditionals
|
// Conditionals
|
||||||
If(&'a (Loc<Expr<'a>>, Loc<Expr<'a>>, Loc<Expr<'a>>)),
|
If(&'a (Loc<Expr<'a>>, Loc<Expr<'a>>, Loc<Expr<'a>>)),
|
||||||
Case(
|
When(
|
||||||
&'a Loc<Expr<'a>>,
|
&'a Loc<Expr<'a>>,
|
||||||
Vec<'a, &'a (Loc<Pattern<'a>>, Loc<Expr<'a>>)>,
|
Vec<'a, &'a (Loc<Pattern<'a>>, Loc<Expr<'a>>)>,
|
||||||
),
|
),
|
||||||
|
@ -240,6 +240,9 @@ pub enum AssignedField<'a, Val> {
|
||||||
// Both a label and a value, e.g. `{ name: "blah" }`
|
// Both a label and a value, e.g. `{ name: "blah" }`
|
||||||
LabeledValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
LabeledValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||||
|
|
||||||
|
// An optional field, e.g. `{ name? : String }`. Only for types
|
||||||
|
OptionalField(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||||
|
|
||||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||||
LabelOnly(Loc<&'a str>),
|
LabelOnly(Loc<&'a str>),
|
||||||
|
|
||||||
|
@ -298,7 +301,6 @@ pub enum Pattern<'a> {
|
||||||
FloatLiteral(&'a str),
|
FloatLiteral(&'a str),
|
||||||
StrLiteral(&'a str),
|
StrLiteral(&'a str),
|
||||||
BlockStrLiteral(&'a [&'a str]),
|
BlockStrLiteral(&'a [&'a str]),
|
||||||
EmptyRecordLiteral,
|
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
||||||
// Space
|
// Space
|
||||||
|
@ -354,6 +356,63 @@ impl<'a> Pattern<'a> {
|
||||||
Ident::Malformed(string) => Pattern::Malformed(string),
|
Ident::Malformed(string) => Pattern::Malformed(string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that patterns are equivalent, meaning they have the same shape, but may have
|
||||||
|
/// different locations/whitespace
|
||||||
|
pub fn equivalent(&self, other: &Self) -> bool {
|
||||||
|
use Pattern::*;
|
||||||
|
match (self, other) {
|
||||||
|
(Identifier(x), Identifier(y)) => x == y,
|
||||||
|
(GlobalTag(x), GlobalTag(y)) => x == y,
|
||||||
|
(PrivateTag(x), PrivateTag(y)) => x == y,
|
||||||
|
(Apply(constructor_x, args_x), Apply(constructor_y, args_y)) => {
|
||||||
|
let equivalent_args = args_x
|
||||||
|
.iter()
|
||||||
|
.zip(args_y.iter())
|
||||||
|
.all(|(p, q)| p.value.equivalent(&q.value));
|
||||||
|
|
||||||
|
constructor_x.value.equivalent(&constructor_y.value) && equivalent_args
|
||||||
|
}
|
||||||
|
(RecordDestructure(fields_x), RecordDestructure(fields_y)) => fields_x
|
||||||
|
.iter()
|
||||||
|
.zip(fields_y.iter())
|
||||||
|
.all(|(p, q)| p.value.equivalent(&q.value)),
|
||||||
|
(RecordField(x, inner_x), RecordField(y, inner_y)) => {
|
||||||
|
x == y && inner_x.value.equivalent(&inner_y.value)
|
||||||
|
}
|
||||||
|
(Nested(x), Nested(y)) => x.equivalent(y),
|
||||||
|
|
||||||
|
// Literal
|
||||||
|
(IntLiteral(x), IntLiteral(y)) => x == y,
|
||||||
|
(
|
||||||
|
NonBase10Literal {
|
||||||
|
string: string_x,
|
||||||
|
base: base_x,
|
||||||
|
is_negative: is_negative_x,
|
||||||
|
},
|
||||||
|
NonBase10Literal {
|
||||||
|
string: string_y,
|
||||||
|
base: base_y,
|
||||||
|
is_negative: is_negative_y,
|
||||||
|
},
|
||||||
|
) => string_x == string_y && base_x == base_y && is_negative_x == is_negative_y,
|
||||||
|
(FloatLiteral(x), FloatLiteral(y)) => x == y,
|
||||||
|
(StrLiteral(x), StrLiteral(y)) => x == y,
|
||||||
|
(BlockStrLiteral(x), BlockStrLiteral(y)) => x == y,
|
||||||
|
(Underscore, Underscore) => true,
|
||||||
|
|
||||||
|
// Space
|
||||||
|
(SpaceBefore(x, _), SpaceBefore(y, _)) => x.equivalent(y),
|
||||||
|
(SpaceAfter(x, _), SpaceAfter(y, _)) => x.equivalent(y),
|
||||||
|
|
||||||
|
// Malformed
|
||||||
|
(Malformed(x), Malformed(y)) => x == y,
|
||||||
|
(QualifiedIdentifier(x), QualifiedIdentifier(y)) => x == y,
|
||||||
|
|
||||||
|
// Different constructors
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Spaceable<'a> {
|
pub trait Spaceable<'a> {
|
||||||
|
@ -464,8 +523,8 @@ pub enum Attempting {
|
||||||
Identifier,
|
Identifier,
|
||||||
ConcreteType,
|
ConcreteType,
|
||||||
TypeVariable,
|
TypeVariable,
|
||||||
CaseCondition,
|
WhenCondition,
|
||||||
CaseBranch,
|
WhenBranch,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Expr<'a> {
|
impl<'a> Expr<'a> {
|
||||||
|
|
|
@ -4,3 +4,4 @@ pub static ELSE: &str = "else";
|
||||||
pub static CASE: &str = "case";
|
pub static CASE: &str = "case";
|
||||||
pub static WHEN: &str = "when";
|
pub static WHEN: &str = "when";
|
||||||
pub static AS: &str = "as";
|
pub static AS: &str = "as";
|
||||||
|
pub static IS: &str = "is";
|
||||||
|
|
149
src/parse/mod.rs
149
src/parse/mod.rs
|
@ -45,7 +45,7 @@ fn loc_parse_expr_body_without_operators<'a>(
|
||||||
loc!(record_literal(min_indent)),
|
loc!(record_literal(min_indent)),
|
||||||
loc!(list_literal(min_indent)),
|
loc!(list_literal(min_indent)),
|
||||||
loc!(unary_op(min_indent)),
|
loc!(unary_op(min_indent)),
|
||||||
loc!(case_expr(min_indent)),
|
loc!(when_expr(min_indent)),
|
||||||
loc!(if_expr(min_indent)),
|
loc!(if_expr(min_indent)),
|
||||||
loc!(ident_etc(min_indent))
|
loc!(ident_etc(min_indent))
|
||||||
)
|
)
|
||||||
|
@ -290,7 +290,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
|
||||||
|
|
||||||
for loc_assigned_field in loc_assigned_fields {
|
for loc_assigned_field in loc_assigned_fields {
|
||||||
let region = loc_assigned_field.region;
|
let region = loc_assigned_field.region;
|
||||||
let value = assigned_field_to_pattern(arena, &loc_assigned_field.value)?;
|
let value = assigned_expr_field_to_pattern(arena, &loc_assigned_field.value)?;
|
||||||
|
|
||||||
loc_patterns.push(Located { region, value });
|
loc_patterns.push(Located { region, value });
|
||||||
}
|
}
|
||||||
|
@ -321,7 +321,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
|
||||||
| Expr::BinOp(_)
|
| Expr::BinOp(_)
|
||||||
| Expr::Defs(_, _)
|
| Expr::Defs(_, _)
|
||||||
| Expr::If(_)
|
| Expr::If(_)
|
||||||
| Expr::Case(_, _)
|
| Expr::When(_, _)
|
||||||
| Expr::MalformedClosure
|
| Expr::MalformedClosure
|
||||||
| Expr::PrecedenceConflict(_, _, _)
|
| Expr::PrecedenceConflict(_, _, _)
|
||||||
| Expr::UnaryOp(_, _) => Err(Fail {
|
| Expr::UnaryOp(_, _) => Err(Fail {
|
||||||
|
@ -331,7 +331,8 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assigned_field_to_pattern<'a>(
|
/// use for expressions like { x: a + b }
|
||||||
|
pub fn assigned_expr_field_to_pattern<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
assigned_field: &AssignedField<'a, Expr<'a>>,
|
assigned_field: &AssignedField<'a, Expr<'a>>,
|
||||||
) -> Result<Pattern<'a>, Fail> {
|
) -> Result<Pattern<'a>, Fail> {
|
||||||
|
@ -352,19 +353,69 @@ pub fn assigned_field_to_pattern<'a>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AssignedField::OptionalField(_, _, _) => panic!("invalid in literals"),
|
||||||
AssignedField::LabelOnly(name) => Pattern::Identifier(name.value),
|
AssignedField::LabelOnly(name) => Pattern::Identifier(name.value),
|
||||||
AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore(
|
AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore(
|
||||||
arena.alloc(assigned_field_to_pattern(arena, nested)?),
|
arena.alloc(assigned_expr_field_to_pattern(arena, nested)?),
|
||||||
spaces,
|
spaces,
|
||||||
),
|
),
|
||||||
AssignedField::SpaceAfter(nested, spaces) => Pattern::SpaceAfter(
|
AssignedField::SpaceAfter(nested, spaces) => Pattern::SpaceAfter(
|
||||||
arena.alloc(assigned_field_to_pattern(arena, nested)?),
|
arena.alloc(assigned_expr_field_to_pattern(arena, nested)?),
|
||||||
spaces,
|
spaces,
|
||||||
),
|
),
|
||||||
AssignedField::Malformed(string) => Pattern::Malformed(string),
|
AssignedField::Malformed(string) => Pattern::Malformed(string),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used for patterns like { x: Just _ }
|
||||||
|
pub fn assigned_pattern_field_to_pattern<'a>(
|
||||||
|
arena: &'a Bump,
|
||||||
|
assigned_field: &AssignedField<'a, Pattern<'a>>,
|
||||||
|
backup_region: Region,
|
||||||
|
) -> Result<Located<Pattern<'a>>, Fail> {
|
||||||
|
// the assigned fields always store spaces, but this slice is often empty
|
||||||
|
Ok(match assigned_field {
|
||||||
|
AssignedField::LabeledValue(name, spaces, value) => {
|
||||||
|
let pattern = value.value.clone();
|
||||||
|
let region = Region::span_across(&value.region, &value.region);
|
||||||
|
let result = arena.alloc(Located {
|
||||||
|
region: value.region,
|
||||||
|
value: pattern,
|
||||||
|
});
|
||||||
|
if spaces.is_empty() {
|
||||||
|
Located::at(region, Pattern::RecordField(name.value, result))
|
||||||
|
} else {
|
||||||
|
Located::at(
|
||||||
|
region,
|
||||||
|
Pattern::SpaceAfter(
|
||||||
|
arena.alloc(Pattern::RecordField(name.value, result)),
|
||||||
|
spaces,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignedField::OptionalField(_, _, _) => {
|
||||||
|
panic!("invalid as a pattern");
|
||||||
|
}
|
||||||
|
AssignedField::LabelOnly(name) => Located::at(name.region, Pattern::Identifier(name.value)),
|
||||||
|
AssignedField::SpaceBefore(nested, spaces) => {
|
||||||
|
let can_nested = assigned_pattern_field_to_pattern(arena, nested, backup_region)?;
|
||||||
|
Located::at(
|
||||||
|
can_nested.region,
|
||||||
|
Pattern::SpaceBefore(arena.alloc(can_nested.value), spaces),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AssignedField::SpaceAfter(nested, spaces) => {
|
||||||
|
let can_nested = assigned_pattern_field_to_pattern(arena, nested, backup_region)?;
|
||||||
|
Located::at(
|
||||||
|
can_nested.region,
|
||||||
|
Pattern::SpaceAfter(arena.alloc(can_nested.value), spaces),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AssignedField::Malformed(string) => Located::at(backup_region, Pattern::Malformed(string)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// A def beginning with a parenthetical pattern, for example:
|
/// A def beginning with a parenthetical pattern, for example:
|
||||||
///
|
///
|
||||||
/// (UserId userId) = ...
|
/// (UserId userId) = ...
|
||||||
|
@ -622,7 +673,7 @@ fn loc_parse_function_arg<'a>(
|
||||||
loc!(record_literal(min_indent)),
|
loc!(record_literal(min_indent)),
|
||||||
loc!(list_literal(min_indent)),
|
loc!(list_literal(min_indent)),
|
||||||
loc!(unary_op(min_indent)),
|
loc!(unary_op(min_indent)),
|
||||||
loc!(case_expr(min_indent)),
|
loc!(when_expr(min_indent)),
|
||||||
loc!(if_expr(min_indent)),
|
loc!(if_expr(min_indent)),
|
||||||
loc!(ident_without_apply())
|
loc!(ident_without_apply())
|
||||||
)
|
)
|
||||||
|
@ -636,6 +687,7 @@ fn reserved_keyword<'a>() -> impl Parser<'a, ()> {
|
||||||
string(keyword::ELSE),
|
string(keyword::ELSE),
|
||||||
string(keyword::CASE),
|
string(keyword::CASE),
|
||||||
string(keyword::WHEN),
|
string(keyword::WHEN),
|
||||||
|
string(keyword::IS),
|
||||||
string(keyword::AS)
|
string(keyword::AS)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -744,15 +796,23 @@ fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
||||||
map!(
|
then(
|
||||||
collection!(
|
record!(loc!(pattern(min_indent)), min_indent),
|
||||||
char('{'),
|
move |arena, state, assigned_fields| {
|
||||||
loc!(ident_pattern()),
|
let mut patterns = Vec::with_capacity_in(assigned_fields.len(), arena);
|
||||||
char(','),
|
for assigned_field in assigned_fields {
|
||||||
char('}'),
|
match assigned_pattern_field_to_pattern(
|
||||||
min_indent
|
arena,
|
||||||
),
|
&assigned_field.value,
|
||||||
Pattern::RecordDestructure
|
assigned_field.region,
|
||||||
|
) {
|
||||||
|
Ok(pattern) => patterns.push(pattern),
|
||||||
|
Err(e) => return Err((e, state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((Pattern::RecordDestructure(patterns), state))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -767,33 +827,33 @@ fn ident_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||||
map!(lowercase_ident(), Pattern::Identifier)
|
map!(lowercase_ident(), Pattern::Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn case_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
pub fn when_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
then(
|
then(
|
||||||
and!(
|
and!(
|
||||||
case_with_indent(),
|
case_with_indent(),
|
||||||
attempt!(
|
attempt!(
|
||||||
Attempting::CaseCondition,
|
Attempting::WhenCondition,
|
||||||
skip_second!(
|
skip_second!(
|
||||||
space1_around(
|
space1_around(
|
||||||
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
|
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
|
||||||
min_indent,
|
min_indent,
|
||||||
),
|
),
|
||||||
string(keyword::WHEN)
|
string(keyword::IS)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
move |arena, state, (case_indent, loc_condition)| {
|
move |arena, state, (case_indent, loc_condition)| {
|
||||||
if case_indent < min_indent {
|
if case_indent < min_indent {
|
||||||
panic!("TODO case wasns't indented enough");
|
panic!("TODO case wasn't indented enough");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything in the branches must be indented at least as much as the case itself.
|
// Everything in the branches must be indented at least as much as the case itself.
|
||||||
let min_indent = case_indent;
|
let min_indent = case_indent;
|
||||||
|
|
||||||
let (branches, state) =
|
let (branches, state) =
|
||||||
attempt!(Attempting::CaseBranch, case_branches(min_indent)).parse(arena, state)?;
|
attempt!(Attempting::WhenBranch, case_branches(min_indent)).parse(arena, state)?;
|
||||||
|
|
||||||
Ok((Expr::Case(arena.alloc(loc_condition), branches), state))
|
Ok((Expr::When(arena.alloc(loc_condition), branches), state))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1138,7 +1198,7 @@ pub fn colon_with_indent<'a>() -> impl Parser<'a, u16> {
|
||||||
|
|
||||||
pub fn case_with_indent<'a>() -> impl Parser<'a, u16> {
|
pub fn case_with_indent<'a>() -> impl Parser<'a, u16> {
|
||||||
move |arena, state: State<'a>| {
|
move |arena, state: State<'a>| {
|
||||||
string(keyword::CASE)
|
string(keyword::WHEN)
|
||||||
.parse(arena, state)
|
.parse(arena, state)
|
||||||
.map(|((), state)| (state.indent_col, state))
|
.map(|((), state)| (state.indent_col, state))
|
||||||
}
|
}
|
||||||
|
@ -1235,7 +1295,10 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
Attempting::Record,
|
Attempting::Record,
|
||||||
loc!(record!(loc!(expr(min_indent)), min_indent))
|
loc!(record!(loc!(expr(min_indent)), min_indent))
|
||||||
),
|
),
|
||||||
optional(and!(space0(min_indent), equals_with_indent()))
|
optional(and!(
|
||||||
|
space0(min_indent),
|
||||||
|
either!(equals_with_indent(), colon_with_indent())
|
||||||
|
))
|
||||||
),
|
),
|
||||||
move |arena, state, (loc_assigned_fields, opt_def)| match opt_def {
|
move |arena, state, (loc_assigned_fields, opt_def)| match opt_def {
|
||||||
None => {
|
None => {
|
||||||
|
@ -1258,7 +1321,7 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
|
|
||||||
Ok((value, state))
|
Ok((value, state))
|
||||||
}
|
}
|
||||||
Some((spaces_before_equals, equals_indent)) => {
|
Some((spaces_before_equals, Either::First(equals_indent))) => {
|
||||||
// This is a record destructure def.
|
// This is a record destructure def.
|
||||||
let region = loc_assigned_fields.region;
|
let region = loc_assigned_fields.region;
|
||||||
let assigned_fields = loc_assigned_fields.value;
|
let assigned_fields = loc_assigned_fields.value;
|
||||||
|
@ -1266,7 +1329,7 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
|
|
||||||
for loc_assigned_field in assigned_fields {
|
for loc_assigned_field in assigned_fields {
|
||||||
let region = loc_assigned_field.region;
|
let region = loc_assigned_field.region;
|
||||||
match assigned_field_to_pattern(arena, &loc_assigned_field.value) {
|
match assigned_expr_field_to_pattern(arena, &loc_assigned_field.value) {
|
||||||
Ok(value) => loc_patterns.push(Located { region, value }),
|
Ok(value) => loc_patterns.push(Located { region, value }),
|
||||||
// an Expr became a pattern that should not be.
|
// an Expr became a pattern that should not be.
|
||||||
Err(e) => return Err((e, state)),
|
Err(e) => return Err((e, state)),
|
||||||
|
@ -1291,6 +1354,40 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Ok((answer, state))
|
||||||
|
}
|
||||||
|
Some((spaces_before_colon, Either::Second(colon_indent))) => {
|
||||||
|
// This is a record type annotation
|
||||||
|
let region = loc_assigned_fields.region;
|
||||||
|
let assigned_fields = loc_assigned_fields.value;
|
||||||
|
let mut loc_patterns = Vec::with_capacity_in(assigned_fields.len(), arena);
|
||||||
|
|
||||||
|
for loc_assigned_field in assigned_fields {
|
||||||
|
let region = loc_assigned_field.region;
|
||||||
|
match assigned_expr_field_to_pattern(arena, &loc_assigned_field.value) {
|
||||||
|
Ok(value) => loc_patterns.push(Located { region, value }),
|
||||||
|
// an Expr became a pattern that should not be.
|
||||||
|
Err(e) => return Err((e, state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern = Pattern::RecordDestructure(loc_patterns);
|
||||||
|
let value = if spaces_before_colon.is_empty() {
|
||||||
|
pattern
|
||||||
|
} else {
|
||||||
|
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_colon)
|
||||||
|
};
|
||||||
|
let loc_pattern = Located { region, value };
|
||||||
|
let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?;
|
||||||
|
let (parsed_expr, state) =
|
||||||
|
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)?;
|
||||||
|
|
||||||
|
let answer = if spaces_after_equals.is_empty() {
|
||||||
|
parsed_expr
|
||||||
|
} else {
|
||||||
|
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||||
|
};
|
||||||
|
|
||||||
Ok((answer, state))
|
Ok((answer, state))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -652,11 +652,21 @@ macro_rules! skip_second {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! collection {
|
macro_rules! collection {
|
||||||
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr) => {
|
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr) => {
|
||||||
// TODO allow trailing commas before the closing delimiter, *but* without
|
|
||||||
// losing any comments or newlines! This will require parsing them and then,
|
|
||||||
// if they are present, merging them into the final Spaceable.
|
|
||||||
skip_first!(
|
skip_first!(
|
||||||
$opening_brace,
|
$opening_brace,
|
||||||
|
skip_first!(
|
||||||
|
// We specifically allow space characters inside here, so that
|
||||||
|
// `[ ]` can be successfully parsed as an empty list, and then
|
||||||
|
// changed by the formatter back into `[]`.
|
||||||
|
//
|
||||||
|
// We don't allow newlines or comments in the middle of empty
|
||||||
|
// collections because those are normally stored in an Expr,
|
||||||
|
// and there's no Expr in which to store them in an empty collection!
|
||||||
|
//
|
||||||
|
// We could change the AST to add extra storage specifically to
|
||||||
|
// support empty literals containing newlines or comments, but this
|
||||||
|
// does not seem worth even the tiniest regression in compiler performance.
|
||||||
|
zero_or_more!(char(' ')),
|
||||||
skip_second!(
|
skip_second!(
|
||||||
$crate::parse::parser::sep_by0(
|
$crate::parse::parser::sep_by0(
|
||||||
$delimiter,
|
$delimiter,
|
||||||
|
@ -665,6 +675,7 @@ macro_rules! collection {
|
||||||
$closing_brace
|
$closing_brace
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -887,6 +898,10 @@ macro_rules! record_field {
|
||||||
|
|
||||||
// You must have a field name, e.g. "email"
|
// You must have a field name, e.g. "email"
|
||||||
let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?;
|
let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?;
|
||||||
|
|
||||||
|
let (opt_field, state) =
|
||||||
|
$crate::parse::parser::optional(char('?')).parse(arena, state)?;
|
||||||
|
|
||||||
let (spaces, state) = space0($min_indent).parse(arena, state)?;
|
let (spaces, state) = space0($min_indent).parse(arena, state)?;
|
||||||
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
|
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
|
||||||
// (This is true in both literals and types.)
|
// (This is true in both literals and types.)
|
||||||
|
@ -896,17 +911,21 @@ macro_rules! record_field {
|
||||||
))
|
))
|
||||||
.parse(arena, state)?;
|
.parse(arena, state)?;
|
||||||
|
|
||||||
let answer = match opt_loc_val {
|
let answer = match (opt_loc_val, opt_field) {
|
||||||
Some(loc_val) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)),
|
(Some(loc_val), None) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)),
|
||||||
|
(Some(loc_val), Some(_)) => OptionalField(loc_label, spaces, arena.alloc(loc_val)),
|
||||||
// If no value was provided, record it as a Var.
|
// If no value was provided, record it as a Var.
|
||||||
// Canonicalize will know what to do with a Var later.
|
// Canonicalize will know what to do with a Var later.
|
||||||
None => {
|
(None, None) => {
|
||||||
if !spaces.is_empty() {
|
if !spaces.is_empty() {
|
||||||
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
||||||
} else {
|
} else {
|
||||||
LabelOnly(loc_label)
|
LabelOnly(loc_label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
panic!("TODO should `{ x? }` be valid? realistically, how of often does `{ a : a }` occur in a type?");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((answer, state))
|
Ok((answer, state))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||||
use crate::collections::{MutMap, MutSet};
|
use crate::collections::{MutMap, MutSet};
|
||||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||||
use crate::types::{self, name_type_var};
|
use crate::types::{self, name_type_var, RecordFieldLabel};
|
||||||
|
|
||||||
static WILDCARD: &str = "*";
|
static WILDCARD: &str = "*";
|
||||||
static EMPTY_RECORD: &str = "{}";
|
static EMPTY_RECORD: &str = "{}";
|
||||||
|
@ -202,7 +202,7 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||||
let mut sorted_fields = Vec::with_capacity(fields.len());
|
let mut sorted_fields = Vec::with_capacity(fields.len());
|
||||||
|
|
||||||
for (label, field_var) in fields {
|
for (label, field_var) in fields {
|
||||||
sorted_fields.push((label.into_str(), field_var));
|
sorted_fields.push((label, field_var));
|
||||||
}
|
}
|
||||||
|
|
||||||
sorted_fields.sort_by(|(a, _), (b, _)| a.cmp(b));
|
sorted_fields.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||||
|
@ -216,7 +216,13 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||||
any_written_yet = true;
|
any_written_yet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push_str(&label);
|
match label {
|
||||||
|
RecordFieldLabel::Required(l) => buf.push_str(&l.into_str()),
|
||||||
|
RecordFieldLabel::Optional(l) => {
|
||||||
|
buf.push_str(&l.into_str());
|
||||||
|
buf.push('?')
|
||||||
|
}
|
||||||
|
}
|
||||||
buf.push_str(" : ");
|
buf.push_str(" : ");
|
||||||
write_content(subs.get(field_var).content, subs, buf, parens);
|
write_content(subs.get(field_var).content, subs, buf, parens);
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,10 @@ impl<T> Located<T> {
|
||||||
};
|
};
|
||||||
Located { value, region }
|
Located { value, region }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn at(region: Region, value: T) -> Located<T> {
|
||||||
|
Located { value, region }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Located<T> {
|
impl<T> Located<T> {
|
||||||
|
|
243
src/solve.rs
243
src/solve.rs
|
@ -32,6 +32,10 @@ impl Pools {
|
||||||
Pools(pools)
|
Pools(pools)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, rank: Rank) -> &mut Vec<Variable> {
|
pub fn get_mut(&mut self, rank: Rank) -> &mut Vec<Variable> {
|
||||||
self.0
|
self.0
|
||||||
.get_mut(rank.into_usize())
|
.get_mut(rank.into_usize())
|
||||||
|
@ -69,9 +73,9 @@ pub fn run(
|
||||||
let mut pools = Pools::default();
|
let mut pools = Pools::default();
|
||||||
let state = State {
|
let state = State {
|
||||||
vars_by_symbol: vars_by_symbol.clone(),
|
vars_by_symbol: vars_by_symbol.clone(),
|
||||||
mark: Mark::none().next(),
|
mark: Mark::NONE.next(),
|
||||||
};
|
};
|
||||||
let rank = Rank::outermost();
|
let rank = Rank::toplevel();
|
||||||
|
|
||||||
solve(
|
solve(
|
||||||
vars_by_symbol,
|
vars_by_symbol,
|
||||||
|
@ -96,11 +100,11 @@ fn solve(
|
||||||
match constraint {
|
match constraint {
|
||||||
True => state,
|
True => state,
|
||||||
Eq(typ, expected_type, _region) => {
|
Eq(typ, expected_type, _region) => {
|
||||||
// TODO use region?
|
let actual = type_to_var(subs, rank, pools, typ);
|
||||||
let actual = type_to_var(subs, typ.clone());
|
let expected = type_to_var(subs, rank, pools, expected_type.get_type_ref());
|
||||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
|
||||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||||
|
|
||||||
|
// TODO use region when reporting a problem
|
||||||
problems.extend(mismatches);
|
problems.extend(mismatches);
|
||||||
|
|
||||||
introduce(subs, rank, pools, &vars);
|
introduce(subs, rank, pools, &vars);
|
||||||
|
@ -108,18 +112,36 @@ fn solve(
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
Lookup(symbol, expected_type, _region) => {
|
Lookup(symbol, expected_type, _region) => {
|
||||||
// TODO use region?
|
let var = *vars_by_symbol.get(&symbol).unwrap_or_else(|| {
|
||||||
let actual = subs.copy_var(*vars_by_symbol.get(&symbol).unwrap_or_else(|| {
|
|
||||||
// TODO Instead of panicking, solve this as True and record
|
// TODO Instead of panicking, solve this as True and record
|
||||||
// a Problem ("module Foo does not expose `bar`") for later.
|
// a Problem ("module Foo does not expose `bar`") for later.
|
||||||
panic!(
|
panic!(
|
||||||
"Could not find symbol {:?} in vars_by_symbol {:?}",
|
"Could not find symbol {:?} in vars_by_symbol {:?}",
|
||||||
symbol, vars_by_symbol
|
symbol, vars_by_symbol
|
||||||
)
|
)
|
||||||
}));
|
});
|
||||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
|
||||||
|
// Deep copy the vars associated with this symbol before unifying them.
|
||||||
|
// Otherwise, suppose we have this:
|
||||||
|
//
|
||||||
|
// identity = \a -> a
|
||||||
|
//
|
||||||
|
// x = identity 5
|
||||||
|
//
|
||||||
|
// When we call (identity 5), it's important that we not unify
|
||||||
|
// on identity's original vars. If we do, the type of `identity` will be
|
||||||
|
// mutated to be `Int -> Int` instead of `a -> `, which would be incorrect;
|
||||||
|
// the type of `identity` is more general than that!
|
||||||
|
//
|
||||||
|
// Instead, we want to unify on a *copy* of its vars. If the copy unifies
|
||||||
|
// successfully (in this case, to `Int -> Int`), we can use that to
|
||||||
|
// infer the type of this lookup (in this case, `Int`) without ever
|
||||||
|
// having mutated the original.
|
||||||
|
let actual = var; // TODO deep copy this var
|
||||||
|
let expected = type_to_var(subs, rank, pools, expected_type.get_type_ref());
|
||||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||||
|
|
||||||
|
// TODO use region when reporting a problem
|
||||||
problems.extend(mismatches);
|
problems.extend(mismatches);
|
||||||
|
|
||||||
introduce(subs, rank, pools, &vars);
|
introduce(subs, rank, pools, &vars);
|
||||||
|
@ -144,11 +166,11 @@ fn solve(
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
Pattern(_region, _category, typ, expected) => {
|
Pattern(_region, _category, typ, expected) => {
|
||||||
// TODO use region?
|
let actual = type_to_var(subs, rank, pools, typ);
|
||||||
let actual = type_to_var(subs, typ.clone());
|
let expected = type_to_var(subs, rank, pools, expected.get_type_ref());
|
||||||
let expected = type_to_var(subs, expected.clone().get_type());
|
|
||||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||||
|
|
||||||
|
// TODO use region when reporting a problem
|
||||||
problems.extend(mismatches);
|
problems.extend(mismatches);
|
||||||
|
|
||||||
introduce(subs, rank, pools, &vars);
|
introduce(subs, rank, pools, &vars);
|
||||||
|
@ -157,7 +179,7 @@ fn solve(
|
||||||
}
|
}
|
||||||
Let(let_con) => {
|
Let(let_con) => {
|
||||||
match &let_con.ret_constraint {
|
match &let_con.ret_constraint {
|
||||||
True => {
|
True if let_con.rigid_vars.is_empty() => {
|
||||||
introduce(subs, rank, pools, &let_con.flex_vars);
|
introduce(subs, rank, pools, &let_con.flex_vars);
|
||||||
|
|
||||||
// If the return expression is guaranteed to solve,
|
// If the return expression is guaranteed to solve,
|
||||||
|
@ -172,6 +194,56 @@ fn solve(
|
||||||
&let_con.defs_constraint,
|
&let_con.defs_constraint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => {
|
||||||
|
let state = solve(
|
||||||
|
vars_by_symbol,
|
||||||
|
state,
|
||||||
|
rank,
|
||||||
|
pools,
|
||||||
|
problems,
|
||||||
|
subs,
|
||||||
|
&let_con.defs_constraint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a variable for each def to new_vars_by_env.
|
||||||
|
let mut local_def_vars = ImMap::default();
|
||||||
|
|
||||||
|
for (symbol, loc_type) in let_con.def_types.iter() {
|
||||||
|
let var = type_to_var(subs, rank, pools, &loc_type.value);
|
||||||
|
|
||||||
|
local_def_vars.insert(
|
||||||
|
symbol.clone(),
|
||||||
|
Located {
|
||||||
|
value: var,
|
||||||
|
region: loc_type.region,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_vars_by_symbol = vars_by_symbol.clone();
|
||||||
|
|
||||||
|
for (symbol, loc_var) in local_def_vars.iter() {
|
||||||
|
if !new_vars_by_symbol.contains_key(&symbol) {
|
||||||
|
new_vars_by_symbol.insert(symbol.clone(), loc_var.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_state = solve(
|
||||||
|
&new_vars_by_symbol,
|
||||||
|
state,
|
||||||
|
rank,
|
||||||
|
pools,
|
||||||
|
problems,
|
||||||
|
subs,
|
||||||
|
ret_con,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (symbol, loc_var) in local_def_vars {
|
||||||
|
check_for_infinite_type(subs, problems, symbol, loc_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_state
|
||||||
|
}
|
||||||
ret_con => {
|
ret_con => {
|
||||||
let rigid_vars = &let_con.rigid_vars;
|
let rigid_vars = &let_con.rigid_vars;
|
||||||
let flex_vars = &let_con.flex_vars;
|
let flex_vars = &let_con.flex_vars;
|
||||||
|
@ -179,30 +251,28 @@ fn solve(
|
||||||
// work in the next pool to localize header
|
// work in the next pool to localize header
|
||||||
let next_rank = rank.next();
|
let next_rank = rank.next();
|
||||||
|
|
||||||
let mut next_pools = pools.clone();
|
|
||||||
|
|
||||||
// introduce variables
|
// introduce variables
|
||||||
for &var in rigid_vars.iter() {
|
for &var in rigid_vars.iter().chain(flex_vars.iter()) {
|
||||||
subs.set_rank(var, next_rank);
|
|
||||||
}
|
|
||||||
|
|
||||||
for &var in flex_vars.iter() {
|
|
||||||
subs.set_rank(var, next_rank);
|
subs.set_rank(var, next_rank);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let work_in_next_pools = |next_pools: &mut Pools| {
|
||||||
let pool: &mut Vec<Variable> = next_pools.get_mut(next_rank);
|
let pool: &mut Vec<Variable> = next_pools.get_mut(next_rank);
|
||||||
|
|
||||||
|
// Replace the contents of this pool with rigid_vars and flex_vars
|
||||||
|
pool.clear();
|
||||||
pool.reserve(rigid_vars.len() + flex_vars.len());
|
pool.reserve(rigid_vars.len() + flex_vars.len());
|
||||||
pool.extend(rigid_vars.iter());
|
pool.extend(rigid_vars.iter());
|
||||||
pool.extend(flex_vars.iter());
|
pool.extend(flex_vars.iter());
|
||||||
|
|
||||||
// Add a variable for each assignment to the vars_by_symbol.
|
// Add a variable for each def to local_def_vars.
|
||||||
let mut locals = ImMap::default();
|
let mut local_def_vars = ImMap::default();
|
||||||
|
|
||||||
for (symbol, loc_type) in let_con.def_types.iter() {
|
for (symbol, loc_type) in let_con.def_types.iter() {
|
||||||
let var = type_to_var(subs, loc_type.value.clone());
|
let def_type = loc_type.value.clone();
|
||||||
|
let var = type_to_var(subs, next_rank, next_pools, &def_type);
|
||||||
|
|
||||||
locals.insert(
|
local_def_vars.insert(
|
||||||
symbol.clone(),
|
symbol.clone(),
|
||||||
Located {
|
Located {
|
||||||
value: var,
|
value: var,
|
||||||
|
@ -218,7 +288,7 @@ fn solve(
|
||||||
vars_by_symbol,
|
vars_by_symbol,
|
||||||
state,
|
state,
|
||||||
next_rank,
|
next_rank,
|
||||||
&mut next_pools,
|
next_pools,
|
||||||
problems,
|
problems,
|
||||||
subs,
|
subs,
|
||||||
&let_con.defs_constraint,
|
&let_con.defs_constraint,
|
||||||
|
@ -228,20 +298,22 @@ fn solve(
|
||||||
let final_mark = visit_mark.next();
|
let final_mark = visit_mark.next();
|
||||||
|
|
||||||
// pop pool
|
// pop pool
|
||||||
generalize(subs, young_mark, visit_mark, next_rank, &mut next_pools);
|
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
|
||||||
|
|
||||||
next_pools.get_mut(next_rank).clear();
|
next_pools.get_mut(next_rank).clear();
|
||||||
|
|
||||||
// check that things went well
|
// check that things went well
|
||||||
debug_assert!(rigid_vars
|
debug_assert!(rigid_vars
|
||||||
.iter()
|
.iter()
|
||||||
.all(|&var| subs.get_without_compacting(var).rank == Rank::none()));
|
.all(|&var| subs.get_without_compacting(var).rank == Rank::NONE));
|
||||||
|
|
||||||
let mut new_vars_by_symbol = vars_by_symbol.clone();
|
let mut new_vars_by_symbol = vars_by_symbol.clone();
|
||||||
|
|
||||||
for (symbol, loc_var) in locals.iter() {
|
for (symbol, loc_var) in local_def_vars.iter() {
|
||||||
|
if !new_vars_by_symbol.contains_key(&symbol) {
|
||||||
new_vars_by_symbol.insert(symbol.clone(), loc_var.value);
|
new_vars_by_symbol.insert(symbol.clone(), loc_var.value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Note that this vars_by_symbol is the one returned by the
|
// Note that this vars_by_symbol is the one returned by the
|
||||||
// previous call to solve()
|
// previous call to solve()
|
||||||
|
@ -256,30 +328,43 @@ fn solve(
|
||||||
&new_vars_by_symbol,
|
&new_vars_by_symbol,
|
||||||
temp_state,
|
temp_state,
|
||||||
rank,
|
rank,
|
||||||
&mut next_pools,
|
next_pools,
|
||||||
problems,
|
problems,
|
||||||
subs,
|
subs,
|
||||||
&ret_con,
|
&ret_con,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (symbol, loc_var) in locals {
|
for (symbol, loc_var) in local_def_vars {
|
||||||
check_for_infinite_type(subs, problems, symbol, loc_var);
|
check_for_infinite_type(subs, problems, symbol, loc_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
new_state
|
new_state
|
||||||
|
};
|
||||||
|
|
||||||
|
if next_rank.into_usize() < pools.len() {
|
||||||
|
work_in_next_pools(pools)
|
||||||
|
} else {
|
||||||
|
work_in_next_pools(&mut pools.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_to_var(subs: &mut Subs, typ: Type) -> Variable {
|
fn type_to_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, typ: &Type) -> Variable {
|
||||||
type_to_variable(subs, &ImMap::default(), typ)
|
type_to_variable(subs, rank, pools, &ImMap::default(), typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_to_variable(subs: &mut Subs, aliases: &ImMap<Lowercase, Variable>, typ: Type) -> Variable {
|
fn type_to_variable(
|
||||||
|
subs: &mut Subs,
|
||||||
|
rank: Rank,
|
||||||
|
pools: &mut Pools,
|
||||||
|
aliases: &ImMap<Lowercase, Variable>,
|
||||||
|
typ: &Type,
|
||||||
|
) -> Variable {
|
||||||
match typ {
|
match typ {
|
||||||
Variable(var) => var,
|
Variable(var) => *var,
|
||||||
Apply {
|
Apply {
|
||||||
module_name,
|
module_name,
|
||||||
name,
|
name,
|
||||||
|
@ -288,67 +373,70 @@ fn type_to_variable(subs: &mut Subs, aliases: &ImMap<Lowercase, Variable>, typ:
|
||||||
let mut arg_vars = Vec::with_capacity(args.len());
|
let mut arg_vars = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg_vars.push(type_to_variable(subs, aliases, arg.clone()))
|
arg_vars.push(type_to_variable(subs, rank, pools, aliases, arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
let flat_type = FlatType::Apply {
|
let flat_type = FlatType::Apply {
|
||||||
module_name,
|
module_name: module_name.clone(),
|
||||||
name,
|
name: name.clone(),
|
||||||
args: arg_vars,
|
args: arg_vars,
|
||||||
};
|
};
|
||||||
let content = Content::Structure(flat_type);
|
let content = Content::Structure(flat_type);
|
||||||
|
|
||||||
subs.fresh(Descriptor::from(content))
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
EmptyRec => {
|
EmptyRec => {
|
||||||
let content = Content::Structure(FlatType::EmptyRecord);
|
let content = Content::Structure(FlatType::EmptyRecord);
|
||||||
|
|
||||||
subs.fresh(Descriptor::from(content))
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
Function(args, ret_type) => {
|
Function(args, ret_type) => {
|
||||||
let mut arg_vars = Vec::with_capacity(args.len());
|
let mut arg_vars = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg_vars.push(type_to_variable(subs, aliases, arg.clone()))
|
arg_vars.push(type_to_variable(subs, rank, pools, aliases, arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret_var = type_to_variable(subs, aliases, *ret_type);
|
let ret_var = type_to_variable(subs, rank, pools, aliases, ret_type);
|
||||||
let content = Content::Structure(FlatType::Func(arg_vars, ret_var));
|
let content = Content::Structure(FlatType::Func(arg_vars, ret_var));
|
||||||
|
|
||||||
subs.fresh(Descriptor::from(content))
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
Record(fields, ext) => {
|
Record(fields, ext) => {
|
||||||
let mut field_vars = ImMap::default();
|
let mut field_vars = ImMap::default();
|
||||||
|
|
||||||
for (field, field_type) in fields {
|
for (field, field_type) in fields {
|
||||||
field_vars.insert(field, type_to_variable(subs, aliases, field_type));
|
field_vars.insert(
|
||||||
|
field.clone(),
|
||||||
|
type_to_variable(subs, rank, pools, aliases, field_type),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext_var = type_to_variable(subs, aliases, *ext);
|
let ext_var = type_to_variable(subs, rank, pools, aliases, ext);
|
||||||
let content = Content::Structure(FlatType::Record(field_vars, ext_var));
|
let content = Content::Structure(FlatType::Record(field_vars, ext_var));
|
||||||
|
|
||||||
subs.fresh(Descriptor::from(content))
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
Alias(home, name, args, alias_type) => {
|
Alias(home, name, args, alias_type) => {
|
||||||
let mut arg_vars = Vec::with_capacity(args.len());
|
let mut arg_vars = Vec::with_capacity(args.len());
|
||||||
let mut new_aliases = ImMap::default();
|
let mut new_aliases = ImMap::default();
|
||||||
|
|
||||||
for (arg, arg_type) in args {
|
for (arg, arg_type) in args {
|
||||||
let arg_var = type_to_variable(subs, aliases, arg_type.clone());
|
let arg_var = type_to_variable(subs, rank, pools, aliases, arg_type);
|
||||||
|
|
||||||
arg_vars.push((arg.clone(), arg_var));
|
arg_vars.push((arg.clone(), arg_var));
|
||||||
new_aliases.insert(arg, arg_var);
|
new_aliases.insert(arg.clone(), arg_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
let alias_var = type_to_variable(subs, &new_aliases, *alias_type);
|
let alias_var = type_to_variable(subs, rank, pools, &new_aliases, alias_type);
|
||||||
let content = Content::Alias(home, name, arg_vars, alias_var);
|
let content = Content::Alias(home.clone(), name.clone(), arg_vars, alias_var);
|
||||||
|
|
||||||
subs.fresh(Descriptor::from(content))
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
Erroneous(problem) => {
|
Erroneous(problem) => {
|
||||||
let content = Content::Structure(FlatType::Erroneous(problem));
|
let content = Content::Structure(FlatType::Erroneous(problem.clone()));
|
||||||
|
|
||||||
subs.fresh(Descriptor::from(content))
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,9 +469,8 @@ fn generalize(
|
||||||
let young_vars = pools.get(young_rank);
|
let young_vars = pools.get(young_rank);
|
||||||
let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars);
|
let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars);
|
||||||
|
|
||||||
// get the ranks right for each entry.
|
// Get the ranks right for each entry.
|
||||||
// start at low ranks so that we only have to pass
|
// Start at low ranks so we only have to pass over the information once.
|
||||||
// over the information once.
|
|
||||||
for (index, table) in rank_table.iter().enumerate() {
|
for (index, table) in rank_table.iter().enumerate() {
|
||||||
for &var in table.iter() {
|
for &var in table.iter() {
|
||||||
adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var);
|
adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var);
|
||||||
|
@ -392,7 +479,7 @@ fn generalize(
|
||||||
|
|
||||||
let (last_pool, all_but_last_pool) = rank_table.split_last();
|
let (last_pool, all_but_last_pool) = rank_table.split_last();
|
||||||
|
|
||||||
// For variables that have rank lowerer than youngRank, register them in
|
// For variables that have rank lowerer than young_rank, register them in
|
||||||
// the appropriate old pool if they are not redundant.
|
// the appropriate old pool if they are not redundant.
|
||||||
for vars in all_but_last_pool {
|
for vars in all_but_last_pool {
|
||||||
for &var in vars {
|
for &var in vars {
|
||||||
|
@ -404,8 +491,7 @@ fn generalize(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For variables with rank youngRank
|
// For variables with rank young_rank, if rank < young_rank: register in old pool,
|
||||||
// If rank < youngRank: register in oldPool
|
|
||||||
// otherwise generalize
|
// otherwise generalize
|
||||||
for &var in last_pool {
|
for &var in last_pool {
|
||||||
if !subs.redundant(var) {
|
if !subs.redundant(var) {
|
||||||
|
@ -414,7 +500,7 @@ fn generalize(
|
||||||
if desc.rank < young_rank {
|
if desc.rank < young_rank {
|
||||||
pools.get_mut(desc.rank).push(var);
|
pools.get_mut(desc.rank).push(var);
|
||||||
} else {
|
} else {
|
||||||
desc.rank = Rank::none();
|
desc.rank = Rank::NONE;
|
||||||
|
|
||||||
subs.set(var, desc);
|
subs.set(var, desc);
|
||||||
}
|
}
|
||||||
|
@ -432,9 +518,18 @@ fn pool_to_rank_table(
|
||||||
|
|
||||||
// Sort the variables into buckets by rank.
|
// Sort the variables into buckets by rank.
|
||||||
for &var in young_vars.iter() {
|
for &var in young_vars.iter() {
|
||||||
let rank = subs.get(var).rank;
|
let desc = subs.get(var);
|
||||||
|
let rank = desc.rank;
|
||||||
|
|
||||||
subs.set_mark(var, young_mark);
|
subs.set(
|
||||||
|
var,
|
||||||
|
Descriptor {
|
||||||
|
rank,
|
||||||
|
mark: young_mark,
|
||||||
|
content: desc.content,
|
||||||
|
copy: desc.copy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pools.get_mut(rank).push(var);
|
pools.get_mut(rank).push(var);
|
||||||
}
|
}
|
||||||
|
@ -444,7 +539,6 @@ fn pool_to_rank_table(
|
||||||
|
|
||||||
/// Adjust variable ranks such that ranks never increase as you move deeper.
|
/// Adjust variable ranks such that ranks never increase as you move deeper.
|
||||||
/// This way the outermost rank is representative of the entire structure.
|
/// This way the outermost rank is representative of the entire structure.
|
||||||
///
|
|
||||||
fn adjust_rank(
|
fn adjust_rank(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
young_mark: Mark,
|
young_mark: Mark,
|
||||||
|
@ -473,7 +567,7 @@ fn adjust_rank(
|
||||||
} else if mark == visit_mark {
|
} else if mark == visit_mark {
|
||||||
desc.rank
|
desc.rank
|
||||||
} else {
|
} else {
|
||||||
let min_rank = desc.rank.min(group_rank);
|
let min_rank = group_rank.min(desc.rank);
|
||||||
|
|
||||||
// TODO from elm-compiler: how can min_rank ever be group_rank?
|
// TODO from elm-compiler: how can min_rank ever be group_rank?
|
||||||
desc.rank = min_rank;
|
desc.rank = min_rank;
|
||||||
|
@ -501,7 +595,7 @@ fn adjust_rank_content(
|
||||||
Structure(flat_type) => {
|
Structure(flat_type) => {
|
||||||
match flat_type {
|
match flat_type {
|
||||||
Apply { args, .. } => {
|
Apply { args, .. } => {
|
||||||
let mut rank = Rank::outermost();
|
let mut rank = Rank::toplevel();
|
||||||
|
|
||||||
for var in args {
|
for var in args {
|
||||||
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
||||||
|
@ -522,7 +616,7 @@ fn adjust_rank_content(
|
||||||
|
|
||||||
EmptyRecord => {
|
EmptyRecord => {
|
||||||
// from elm-compiler: THEORY: an empty record never needs to get generalized
|
// from elm-compiler: THEORY: an empty record never needs to get generalized
|
||||||
Rank::outermost()
|
Rank::toplevel()
|
||||||
}
|
}
|
||||||
|
|
||||||
Record(fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
|
@ -539,9 +633,9 @@ fn adjust_rank_content(
|
||||||
}
|
}
|
||||||
|
|
||||||
Alias(_, _, args, _) => {
|
Alias(_, _, args, _) => {
|
||||||
let mut rank = Rank::outermost();
|
let mut rank = Rank::toplevel();
|
||||||
|
|
||||||
// from elm-compiler: THEORY: anything in the realVar would be outermostRank
|
// from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel()
|
||||||
for (_, var) in args {
|
for (_, var) in args {
|
||||||
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
||||||
}
|
}
|
||||||
|
@ -551,6 +645,8 @@ fn adjust_rank_content(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Introduce some variables to Pools at the given rank.
|
||||||
|
/// Also, set each of their ranks in Subs to be the given rank.
|
||||||
fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) {
|
fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) {
|
||||||
let pool: &mut Vec<Variable> = pools.get_mut(rank);
|
let pool: &mut Vec<Variable> = pools.get_mut(rank);
|
||||||
|
|
||||||
|
@ -560,3 +656,16 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable])
|
||||||
|
|
||||||
pool.extend(vars);
|
pool.extend(vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable {
|
||||||
|
let var = subs.fresh(Descriptor {
|
||||||
|
content,
|
||||||
|
rank,
|
||||||
|
mark: Mark::NONE,
|
||||||
|
copy: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
pools.get_mut(rank).push(var);
|
||||||
|
|
||||||
|
var
|
||||||
|
}
|
||||||
|
|
144
src/subs.rs
144
src/subs.rs
|
@ -1,7 +1,7 @@
|
||||||
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||||
use crate::collections::{ImMap, ImSet, MutSet, SendMap};
|
use crate::collections::{ImMap, ImSet, MutSet, SendMap};
|
||||||
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
|
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
|
||||||
use crate::types::{name_type_var, ErrorType, Problem, RecordExt};
|
use crate::types::{name_type_var, ErrorType, Problem, RecordExt, RecordFieldLabel};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
@ -9,20 +9,9 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
pub struct Mark(i32);
|
pub struct Mark(i32);
|
||||||
|
|
||||||
impl Mark {
|
impl Mark {
|
||||||
#[inline(always)]
|
pub const NONE: Mark = Mark(2);
|
||||||
pub fn none() -> Mark {
|
pub const OCCURS: Mark = Mark(1);
|
||||||
Mark(2)
|
pub const GET_VAR_NAMES: Mark = Mark(1);
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn occurs() -> Mark {
|
|
||||||
Mark(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn get_var_names() -> Mark {
|
|
||||||
Mark(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn next(self) -> Mark {
|
pub fn next(self) -> Mark {
|
||||||
|
@ -32,11 +21,11 @@ impl Mark {
|
||||||
|
|
||||||
impl fmt::Debug for Mark {
|
impl fmt::Debug for Mark {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if self == &Mark::none() {
|
if self == &Mark::NONE {
|
||||||
write!(f, "none")
|
write!(f, "none")
|
||||||
} else if self == &Mark::occurs() {
|
} else if self == &Mark::OCCURS {
|
||||||
write!(f, "occurs")
|
write!(f, "occurs")
|
||||||
} else if self == &Mark::get_var_names() {
|
} else if self == &Mark::GET_VAR_NAMES {
|
||||||
write!(f, "get_var_names")
|
write!(f, "get_var_names")
|
||||||
} else {
|
} else {
|
||||||
write!(f, "Mark({})", self.0)
|
write!(f, "Mark({})", self.0)
|
||||||
|
@ -50,11 +39,17 @@ struct NameState {
|
||||||
normals: u32,
|
normals: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Default)]
|
||||||
pub struct Subs {
|
pub struct Subs {
|
||||||
utable: UnificationTable<InPlace<Variable>>,
|
utable: UnificationTable<InPlace<Variable>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Subs {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.utable.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct VarStore {
|
pub struct VarStore {
|
||||||
next: AtomicUsize,
|
next: AtomicUsize,
|
||||||
|
@ -209,12 +204,6 @@ impl Subs {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_var(&mut self, var: Variable) -> Variable {
|
|
||||||
// TODO understand the purpose of using a "deep copy" approach here,
|
|
||||||
// and perform it if necessary. (Seems to be about setting maxRank?)
|
|
||||||
var
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool {
|
pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool {
|
||||||
self.utable.unioned(left, right)
|
self.utable.unioned(left, right)
|
||||||
}
|
}
|
||||||
|
@ -239,6 +228,18 @@ impl Subs {
|
||||||
|
|
||||||
var_to_err_type(self, &mut state, var)
|
var_to_err_type(self, &mut state, var)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn restore(&mut self, var: Variable) {
|
||||||
|
let desc = self.get(var);
|
||||||
|
|
||||||
|
if desc.copy.is_some() {
|
||||||
|
let content = desc.content;
|
||||||
|
|
||||||
|
self.set(var, content.clone().into());
|
||||||
|
|
||||||
|
restore_content(self, &content);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -251,15 +252,13 @@ fn unnamed_flex_var() -> Content {
|
||||||
Content::FlexVar(None)
|
Content::FlexVar(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Rank(usize);
|
pub struct Rank(usize);
|
||||||
|
|
||||||
impl Rank {
|
impl Rank {
|
||||||
pub fn none() -> Self {
|
pub const NONE: Rank = Rank(0);
|
||||||
Rank(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outermost() -> Self {
|
pub fn toplevel() -> Self {
|
||||||
Rank(1)
|
Rank(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,6 +277,12 @@ impl fmt::Display for Rank {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Rank {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Into<usize> for Rank {
|
impl Into<usize> for Rank {
|
||||||
fn into(self) -> usize {
|
fn into(self) -> usize {
|
||||||
self.0
|
self.0
|
||||||
|
@ -290,7 +295,7 @@ impl From<usize> for Rank {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct Descriptor {
|
pub struct Descriptor {
|
||||||
pub content: Content,
|
pub content: Content,
|
||||||
pub rank: Rank,
|
pub rank: Rank,
|
||||||
|
@ -298,6 +303,16 @@ pub struct Descriptor {
|
||||||
pub copy: Option<Variable>,
|
pub copy: Option<Variable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Descriptor {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:?}, r: {:?}, m: {:?} c: {:?}",
|
||||||
|
self.content, self.rank, self.mark, self.copy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Descriptor {
|
impl Default for Descriptor {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
unnamed_flex_var().into()
|
unnamed_flex_var().into()
|
||||||
|
@ -308,22 +323,20 @@ impl From<Content> for Descriptor {
|
||||||
fn from(content: Content) -> Descriptor {
|
fn from(content: Content) -> Descriptor {
|
||||||
Descriptor {
|
Descriptor {
|
||||||
content,
|
content,
|
||||||
rank: Rank::none(),
|
rank: Rank::NONE,
|
||||||
mark: Mark::none(),
|
mark: Mark::NONE,
|
||||||
copy: None,
|
copy: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Descriptor {
|
impl Descriptor {
|
||||||
pub fn error() -> Self {
|
pub const ERROR: Descriptor = Descriptor {
|
||||||
Descriptor {
|
|
||||||
content: Content::Error,
|
content: Content::Error,
|
||||||
rank: Rank::none(),
|
rank: Rank::NONE,
|
||||||
mark: Mark::none(),
|
mark: Mark::NONE,
|
||||||
copy: None,
|
copy: None,
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -350,7 +363,7 @@ pub enum FlatType {
|
||||||
args: Vec<Variable>,
|
args: Vec<Variable>,
|
||||||
},
|
},
|
||||||
Func(Vec<Variable>, Variable),
|
Func(Vec<Variable>, Variable),
|
||||||
Record(ImMap<Lowercase, Variable>, Variable),
|
Record(ImMap<RecordFieldLabel, Variable>, Variable),
|
||||||
Erroneous(Problem),
|
Erroneous(Problem),
|
||||||
EmptyRecord,
|
EmptyRecord,
|
||||||
}
|
}
|
||||||
|
@ -413,10 +426,10 @@ fn get_var_names(
|
||||||
use self::Content::*;
|
use self::Content::*;
|
||||||
let desc = subs.get(var);
|
let desc = subs.get(var);
|
||||||
|
|
||||||
if desc.mark == Mark::get_var_names() {
|
if desc.mark == Mark::GET_VAR_NAMES {
|
||||||
taken_names
|
taken_names
|
||||||
} else {
|
} else {
|
||||||
subs.set_mark(var, Mark::get_var_names());
|
subs.set_mark(var, Mark::GET_VAR_NAMES);
|
||||||
|
|
||||||
match desc.content {
|
match desc.content {
|
||||||
Error | FlexVar(None) => taken_names,
|
Error | FlexVar(None) => taken_names,
|
||||||
|
@ -511,10 +524,10 @@ where
|
||||||
fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> ErrorType {
|
fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> ErrorType {
|
||||||
let desc = subs.get(var);
|
let desc = subs.get(var);
|
||||||
|
|
||||||
if desc.mark == Mark::occurs() {
|
if desc.mark == Mark::OCCURS {
|
||||||
ErrorType::Infinite
|
ErrorType::Infinite
|
||||||
} else {
|
} else {
|
||||||
subs.set_mark(var, Mark::occurs());
|
subs.set_mark(var, Mark::OCCURS);
|
||||||
|
|
||||||
let err_type = content_to_err_type(subs, state, var, desc.content);
|
let err_type = content_to_err_type(subs, state, var, desc.content);
|
||||||
|
|
||||||
|
@ -633,3 +646,46 @@ fn get_fresh_var_name(state: &mut NameState) -> Lowercase {
|
||||||
|
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn restore_content(subs: &mut Subs, content: &Content) {
|
||||||
|
use crate::subs::Content::*;
|
||||||
|
use crate::subs::FlatType::*;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
FlexVar(_) | RigidVar(_) | Error => (),
|
||||||
|
|
||||||
|
Structure(flat_type) => match flat_type {
|
||||||
|
Apply { args, .. } => {
|
||||||
|
for &var in args {
|
||||||
|
subs.restore(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Func(arg_vars, ret_var) => {
|
||||||
|
for &var in arg_vars {
|
||||||
|
subs.restore(var);
|
||||||
|
}
|
||||||
|
|
||||||
|
subs.restore(*ret_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmptyRecord => (),
|
||||||
|
|
||||||
|
Record(fields, ext_var) => {
|
||||||
|
for (_, var) in fields {
|
||||||
|
subs.restore(*var);
|
||||||
|
}
|
||||||
|
|
||||||
|
subs.restore(*ext_var);
|
||||||
|
}
|
||||||
|
Erroneous(_) => (),
|
||||||
|
},
|
||||||
|
Alias(_, _, args, var) => {
|
||||||
|
for (_, arg_var) in args {
|
||||||
|
subs.restore(*arg_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
subs.restore(*var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
76
src/types.rs
76
src/types.rs
|
@ -1,4 +1,5 @@
|
||||||
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||||
|
use crate::can::pattern::Pattern;
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{MutSet, SendMap};
|
use crate::collections::{MutSet, SendMap};
|
||||||
use crate::operator::{ArgSide, BinOp};
|
use crate::operator::{ArgSide, BinOp};
|
||||||
|
@ -27,7 +28,7 @@ pub enum Type {
|
||||||
EmptyRec,
|
EmptyRec,
|
||||||
/// A function. The types of its arguments, then the type of its return value.
|
/// A function. The types of its arguments, then the type of its return value.
|
||||||
Function(Vec<Type>, Box<Type>),
|
Function(Vec<Type>, Box<Type>),
|
||||||
Record(SendMap<Lowercase, Type>, Box<Type>),
|
Record(SendMap<RecordFieldLabel, Type>, Box<Type>),
|
||||||
Alias(ModuleName, Uppercase, Vec<(Lowercase, Type)>, Box<Type>),
|
Alias(ModuleName, Uppercase, Vec<(Lowercase, Type)>, Box<Type>),
|
||||||
/// Applying a type to some arguments (e.g. Map.Map String Int)
|
/// Applying a type to some arguments (e.g. Map.Map String Int)
|
||||||
Apply {
|
Apply {
|
||||||
|
@ -40,11 +41,37 @@ pub enum Type {
|
||||||
Erroneous(Problem),
|
Erroneous(Problem),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub enum RecordFieldLabel {
|
||||||
|
Required(Lowercase),
|
||||||
|
Optional(Lowercase),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Lowercase> for RecordFieldLabel {
|
||||||
|
fn into(self) -> Lowercase {
|
||||||
|
match self {
|
||||||
|
RecordFieldLabel::Required(label) => label,
|
||||||
|
RecordFieldLabel::Optional(label) => label,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RecordFieldLabel {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
RecordFieldLabel::Required(label) => write!(f, "{}", label),
|
||||||
|
RecordFieldLabel::Optional(label) => write!(f, "{}?", label),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Type {
|
impl fmt::Debug for Type {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Type::EmptyRec => write!(f, "{{}}"),
|
Type::EmptyRec => write!(f, "{{}}"),
|
||||||
Type::Function(args, ret) => {
|
Type::Function(args, ret) => {
|
||||||
|
write!(f, "Fn(")?;
|
||||||
|
|
||||||
for (index, arg) in args.iter().enumerate() {
|
for (index, arg) in args.iter().enumerate() {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
", ".fmt(f)?;
|
", ".fmt(f)?;
|
||||||
|
@ -55,7 +82,9 @@ impl fmt::Debug for Type {
|
||||||
|
|
||||||
write!(f, " -> ")?;
|
write!(f, " -> ")?;
|
||||||
|
|
||||||
ret.fmt(f)
|
ret.fmt(f)?;
|
||||||
|
|
||||||
|
write!(f, ")")
|
||||||
}
|
}
|
||||||
Type::Variable(var) => write!(f, "<{:?}>", var),
|
Type::Variable(var) => write!(f, "<{:?}>", var),
|
||||||
|
|
||||||
|
@ -100,7 +129,7 @@ impl fmt::Debug for Type {
|
||||||
let mut any_written_yet = false;
|
let mut any_written_yet = false;
|
||||||
|
|
||||||
for (label, field_type) in fields {
|
for (label, field_type) in fields {
|
||||||
write!(f, "{} : {:?}", label, field_type)?;
|
write!(f, "{:?} : {:?}", label, field_type)?;
|
||||||
|
|
||||||
if any_written_yet {
|
if any_written_yet {
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
|
@ -179,12 +208,20 @@ impl Type {
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn arity(&self) -> usize {
|
||||||
|
if let Type::Function(args, _) = self {
|
||||||
|
args.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Expected<T> {
|
pub enum Expected<T> {
|
||||||
NoExpectation(T),
|
NoExpectation(T),
|
||||||
FromAnnotation(String, usize, AnnotationSource, T),
|
FromAnnotation(Located<Pattern>, usize, AnnotationSource, T),
|
||||||
ForReason(Reason, T, Region),
|
ForReason(Reason, T, Region),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,12 +239,19 @@ impl<T> PExpected<T> {
|
||||||
PExpected::ForReason(_, val, _) => val,
|
PExpected::ForReason(_, val, _) => val,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_type_ref(&self) -> &T {
|
||||||
|
match self {
|
||||||
|
PExpected::NoExpectation(val) => val,
|
||||||
|
PExpected::ForReason(_, val, _) => val,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum PReason {
|
pub enum PReason {
|
||||||
TypedArg { name: Box<str>, index: usize },
|
TypedArg { name: Box<str>, index: usize },
|
||||||
CaseMatch { index: usize },
|
WhenMatch { index: usize },
|
||||||
CtorArg { name: Box<str>, index: usize },
|
CtorArg { name: Box<str>, index: usize },
|
||||||
ListEntry { index: usize },
|
ListEntry { index: usize },
|
||||||
Tail,
|
Tail,
|
||||||
|
@ -221,12 +265,20 @@ impl<T> Expected<T> {
|
||||||
Expected::FromAnnotation(_, _, _, val) => val,
|
Expected::FromAnnotation(_, _, _, val) => val,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_type_ref(&self) -> &T {
|
||||||
|
match self {
|
||||||
|
Expected::NoExpectation(val) => val,
|
||||||
|
Expected::ForReason(_, val, _) => val,
|
||||||
|
Expected::FromAnnotation(_, _, _, val) => val,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum AnnotationSource {
|
pub enum AnnotationSource {
|
||||||
TypedIfBranch(usize /* index */),
|
TypedIfBranch(usize /* index */),
|
||||||
TypedCaseBranch(usize /* index */),
|
TypedWhenBranch(usize /* index */),
|
||||||
TypedBody,
|
TypedBody,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,11 +293,11 @@ pub enum Reason {
|
||||||
FloatLiteral,
|
FloatLiteral,
|
||||||
IntLiteral,
|
IntLiteral,
|
||||||
InterpolatedStringVar,
|
InterpolatedStringVar,
|
||||||
CaseBranch { index: usize },
|
WhenBranch { index: usize },
|
||||||
ElemInList,
|
ElemInList,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum Constraint {
|
pub enum Constraint {
|
||||||
Eq(Type, Expected<Type>, Region),
|
Eq(Type, Expected<Type>, Region),
|
||||||
|
@ -269,7 +321,7 @@ pub enum PatternCategory {
|
||||||
Float,
|
Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct LetConstraint {
|
pub struct LetConstraint {
|
||||||
pub rigid_vars: Vec<Variable>,
|
pub rigid_vars: Vec<Variable>,
|
||||||
pub flex_vars: Vec<Variable>,
|
pub flex_vars: Vec<Variable>,
|
||||||
|
@ -292,7 +344,7 @@ pub enum Mismatch {
|
||||||
MissingArguments { expected: usize, actual: usize },
|
MissingArguments { expected: usize, actual: usize },
|
||||||
IfConditionNotBool,
|
IfConditionNotBool,
|
||||||
InconsistentIfElse,
|
InconsistentIfElse,
|
||||||
InconsistentCaseBranches,
|
InconsistentWhenBranches,
|
||||||
CanonicalizationProblem,
|
CanonicalizationProblem,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +354,7 @@ pub enum ErrorType {
|
||||||
Type(ModuleName, Uppercase, Vec<ErrorType>),
|
Type(ModuleName, Uppercase, Vec<ErrorType>),
|
||||||
FlexVar(Lowercase),
|
FlexVar(Lowercase),
|
||||||
RigidVar(Lowercase),
|
RigidVar(Lowercase),
|
||||||
Record(SendMap<Lowercase, ErrorType>, RecordExt),
|
Record(SendMap<RecordFieldLabel, ErrorType>, RecordExt),
|
||||||
Function(Vec<ErrorType>, Box<ErrorType>),
|
Function(Vec<ErrorType>, Box<ErrorType>),
|
||||||
Alias(
|
Alias(
|
||||||
ModuleName,
|
ModuleName,
|
||||||
|
|
16
src/unify.rs
16
src/unify.rs
|
@ -2,6 +2,7 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||||
use crate::collections::ImMap;
|
use crate::collections::ImMap;
|
||||||
use crate::subs::Content::{self, *};
|
use crate::subs::Content::{self, *};
|
||||||
use crate::subs::{Descriptor, FlatType, Mark, Subs, Variable};
|
use crate::subs::{Descriptor, FlatType, Mark, Subs, Variable};
|
||||||
|
use crate::types::RecordFieldLabel;
|
||||||
use crate::types::{Mismatch, Problem};
|
use crate::types::{Mismatch, Problem};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ struct Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RecordStructure {
|
struct RecordStructure {
|
||||||
fields: ImMap<Lowercase, Variable>,
|
fields: ImMap<RecordFieldLabel, Variable>,
|
||||||
ext: Variable,
|
ext: Variable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) -> Unified {
|
||||||
let type1 = subs.var_to_error_type(var1);
|
let type1 = subs.var_to_error_type(var1);
|
||||||
let type2 = subs.var_to_error_type(var2);
|
let type2 = subs.var_to_error_type(var2);
|
||||||
|
|
||||||
subs.union(var1, var2, Descriptor::error());
|
subs.union(var1, var2, Descriptor::ERROR);
|
||||||
|
|
||||||
Problem::Mismatch(problem, type1, type2)
|
Problem::Mismatch(problem, type1, type2)
|
||||||
})
|
})
|
||||||
|
@ -160,6 +161,7 @@ fn unify_record(
|
||||||
rec1: RecordStructure,
|
rec1: RecordStructure,
|
||||||
rec2: RecordStructure,
|
rec2: RecordStructure,
|
||||||
) -> Outcome {
|
) -> Outcome {
|
||||||
|
// This is a bit more complicated because of optional fields
|
||||||
let fields1 = rec1.fields;
|
let fields1 = rec1.fields;
|
||||||
let fields2 = rec2.fields;
|
let fields2 = rec2.fields;
|
||||||
let shared_fields = fields1
|
let shared_fields = fields1
|
||||||
|
@ -224,8 +226,8 @@ fn unify_shared_fields(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
pool: &mut Pool,
|
pool: &mut Pool,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
shared_fields: ImMap<Lowercase, (Variable, Variable)>,
|
shared_fields: ImMap<RecordFieldLabel, (Variable, Variable)>,
|
||||||
other_fields: ImMap<Lowercase, Variable>,
|
other_fields: ImMap<RecordFieldLabel, Variable>,
|
||||||
ext: Variable,
|
ext: Variable,
|
||||||
) -> Outcome {
|
) -> Outcome {
|
||||||
let mut matching_fields = ImMap::default();
|
let mut matching_fields = ImMap::default();
|
||||||
|
@ -389,7 +391,7 @@ fn unify_flex(
|
||||||
|
|
||||||
fn gather_fields(
|
fn gather_fields(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
fields: ImMap<Lowercase, Variable>,
|
fields: ImMap<RecordFieldLabel, Variable>,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
) -> RecordStructure {
|
) -> RecordStructure {
|
||||||
use crate::subs::FlatType::*;
|
use crate::subs::FlatType::*;
|
||||||
|
@ -413,7 +415,7 @@ fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
|
||||||
let desc = Descriptor {
|
let desc = Descriptor {
|
||||||
content,
|
content,
|
||||||
rank,
|
rank,
|
||||||
mark: Mark::none(),
|
mark: Mark::NONE,
|
||||||
copy: None,
|
copy: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -436,7 +438,7 @@ fn fresh(subs: &mut Subs, pool: &mut Pool, ctx: &Context, content: Content) -> V
|
||||||
Descriptor {
|
Descriptor {
|
||||||
content,
|
content,
|
||||||
rank: ctx.first_desc.rank.min(ctx.second_desc.rank),
|
rank: ctx.first_desc.rank.min(ctx.second_desc.rank),
|
||||||
mark: Mark::none(),
|
mark: Mark::NONE,
|
||||||
copy: None,
|
copy: None,
|
||||||
},
|
},
|
||||||
pool,
|
pool,
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
use crate::can::def::Def;
|
use crate::can::def::Def;
|
||||||
use crate::can::expr::Expr;
|
use crate::can::expr::Expr;
|
||||||
use crate::can::expr::Output;
|
use crate::can::expr::Output;
|
||||||
use crate::can::pattern::Pattern;
|
use crate::can::pattern;
|
||||||
|
use crate::can::pattern::{Pattern, RecordDestruct};
|
||||||
use crate::can::procedure::{Procedure, References};
|
use crate::can::procedure::{Procedure, References};
|
||||||
use crate::can::symbol::Symbol;
|
use crate::can::symbol::Symbol;
|
||||||
use crate::collections::{ImMap, SendMap};
|
use crate::collections::{ImMap, SendMap};
|
||||||
// use crate::constrain::{self, exists};
|
|
||||||
use crate::can::pattern;
|
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
use crate::region::{Located, Region};
|
use crate::region::{Located, Region};
|
||||||
use crate::subs::{VarStore, Variable};
|
use crate::subs::{VarStore, Variable};
|
||||||
use crate::types::AnnotationSource::TypedCaseBranch;
|
use crate::types::AnnotationSource::TypedWhenBranch;
|
||||||
use crate::types::Constraint::{self, *};
|
use crate::types::Constraint::{self, *};
|
||||||
use crate::types::Expected::{self};
|
use crate::types::Expected::{self};
|
||||||
use crate::types::LetConstraint;
|
use crate::types::LetConstraint;
|
||||||
use crate::types::PExpected::{self};
|
use crate::types::PExpected::{self};
|
||||||
use crate::types::PReason::{self};
|
use crate::types::PReason::{self};
|
||||||
use crate::types::Reason;
|
use crate::types::Reason;
|
||||||
|
use crate::types::RecordFieldLabel;
|
||||||
use crate::types::Type::{self, *};
|
use crate::types::Type::{self, *};
|
||||||
use crate::uniqueness::constrain::exists;
|
use crate::uniqueness::constrain::exists;
|
||||||
use crate::uniqueness::sharing::VarUsage;
|
use crate::uniqueness::sharing::VarUsage;
|
||||||
|
@ -32,7 +32,6 @@ pub struct Env {
|
||||||
pub procedures: ImMap<Symbol, Procedure>,
|
pub procedures: ImMap<Symbol, Procedure>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn canonicalize_declaration(
|
pub fn canonicalize_declaration(
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
region: Region,
|
region: Region,
|
||||||
|
@ -60,6 +59,7 @@ pub struct PatternState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize_pattern(
|
fn canonicalize_pattern(
|
||||||
|
var_store: &VarStore,
|
||||||
state: &mut PatternState,
|
state: &mut PatternState,
|
||||||
pattern: &Located<Pattern>,
|
pattern: &Located<Pattern>,
|
||||||
expected: PExpected<Type>,
|
expected: PExpected<Type>,
|
||||||
|
@ -68,7 +68,7 @@ fn canonicalize_pattern(
|
||||||
use crate::types::PatternCategory;
|
use crate::types::PatternCategory;
|
||||||
|
|
||||||
match &pattern.value {
|
match &pattern.value {
|
||||||
Identifier(_, symbol) => {
|
Identifier(symbol) => {
|
||||||
state.headers.insert(
|
state.headers.insert(
|
||||||
symbol.clone(),
|
symbol.clone(),
|
||||||
Located {
|
Located {
|
||||||
|
@ -95,7 +95,7 @@ fn canonicalize_pattern(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
ExactString(_) => {
|
StrLiteral(_) => {
|
||||||
state.constraints.push(Constraint::Pattern(
|
state.constraints.push(Constraint::Pattern(
|
||||||
pattern.region,
|
pattern.region,
|
||||||
PatternCategory::Str,
|
PatternCategory::Str,
|
||||||
|
@ -104,11 +104,58 @@ fn canonicalize_pattern(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag(_, _) | AppliedTag(_, _, _) | EmptyRecordLiteral(_) => {
|
RecordDestructure(ext_var, patterns) => {
|
||||||
|
let ext_type = Type::Variable(*ext_var);
|
||||||
|
|
||||||
|
let mut field_types: SendMap<RecordFieldLabel, Type> = SendMap::default();
|
||||||
|
for RecordDestruct {
|
||||||
|
var,
|
||||||
|
label,
|
||||||
|
symbol,
|
||||||
|
guard,
|
||||||
|
} in patterns
|
||||||
|
{
|
||||||
|
let pat_type = Type::Variable(*var);
|
||||||
|
let pattern_expected = PExpected::NoExpectation(pat_type.clone());
|
||||||
|
|
||||||
|
match 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.vars.push(*var);
|
||||||
|
field_types.insert(RecordFieldLabel::Required(label.clone()), pat_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
let record_type =
|
||||||
|
constrain::lift(var_store, Type::Record(field_types, Box::new(ext_type)));
|
||||||
|
let record_con = Constraint::Pattern(
|
||||||
|
pattern.region,
|
||||||
|
PatternCategory::Record,
|
||||||
|
record_type,
|
||||||
|
expected,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.constraints.push(record_con);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag(_) | AppliedTag(_, _) => {
|
||||||
panic!("TODO add_constraints for {:?}", pattern);
|
panic!("TODO add_constraints for {:?}", pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
Underscore(_) | Shadowed(_) | UnsupportedPattern(_) => {
|
Underscore | Shadowed(_) | UnsupportedPattern(_) => {
|
||||||
// no constraints
|
// no constraints
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,11 +177,11 @@ pub fn canonicalize_expr(
|
||||||
pub use crate::can::expr::Expr::*;
|
pub use crate::can::expr::Expr::*;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Int(_) => {
|
Int(_, _) => {
|
||||||
let constraint = constrain::int_literal(var_store, expected, region);
|
let constraint = constrain::int_literal(var_store, expected, region);
|
||||||
(Output::default(), constraint)
|
(Output::default(), constraint)
|
||||||
}
|
}
|
||||||
Float(_) => {
|
Float(_, _) => {
|
||||||
let constraint = constrain::float_literal(var_store, expected, region);
|
let constraint = constrain::float_literal(var_store, expected, region);
|
||||||
(Output::default(), constraint)
|
(Output::default(), constraint)
|
||||||
}
|
}
|
||||||
|
@ -144,27 +191,78 @@ pub fn canonicalize_expr(
|
||||||
(Output::default(), constraint)
|
(Output::default(), constraint)
|
||||||
}
|
}
|
||||||
EmptyRecord => {
|
EmptyRecord => {
|
||||||
let inferred = constrain::lift(var_store, EmptyRec);
|
let constraint = Eq(constrain::lift(var_store, EmptyRec), expected, region);
|
||||||
let constraint = Eq(inferred, expected, region);
|
|
||||||
(Output::default(), constraint)
|
(Output::default(), constraint)
|
||||||
}
|
}
|
||||||
Record(_, _) => panic!("TODO implement records"),
|
Record(variable, fields) => {
|
||||||
List(_variable, loc_elems) => {
|
// NOTE: canonicalization guarantees at least one field
|
||||||
|
// zero fields generates an EmptyRecord
|
||||||
|
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());
|
||||||
|
let mut output = Output::default();
|
||||||
|
|
||||||
|
for (label, (_, loc_expr)) in fields.iter() {
|
||||||
|
let field_var = var_store.fresh();
|
||||||
|
let field_type = Variable(field_var);
|
||||||
|
let field_expected = Expected::NoExpectation(field_type.clone());
|
||||||
|
let (field_out, field_con) = canonicalize_expr(
|
||||||
|
rigids,
|
||||||
|
var_store,
|
||||||
|
var_usage,
|
||||||
|
loc_expr.region,
|
||||||
|
&loc_expr.value,
|
||||||
|
field_expected,
|
||||||
|
);
|
||||||
|
|
||||||
|
field_vars.push(field_var);
|
||||||
|
field_types.insert(RecordFieldLabel::Required(label.clone()), field_type);
|
||||||
|
|
||||||
|
constraints.push(field_con);
|
||||||
|
output.references = output.references.union(field_out.references);
|
||||||
|
}
|
||||||
|
|
||||||
|
let record_type = constrain::lift(
|
||||||
|
var_store,
|
||||||
|
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);
|
||||||
|
let ext_con = Eq(Type::Variable(*variable), expected, region);
|
||||||
|
|
||||||
|
constraints.push(record_con);
|
||||||
|
constraints.push(ext_con);
|
||||||
|
|
||||||
|
let constraint = exists(field_vars, And(constraints));
|
||||||
|
|
||||||
|
(output, constraint)
|
||||||
|
}
|
||||||
|
Tag(name, arguments) => {
|
||||||
|
panic!("TODO implement tag {:?} {:?}", name, arguments);
|
||||||
|
}
|
||||||
|
List(variable, loc_elems) => {
|
||||||
if loc_elems.is_empty() {
|
if loc_elems.is_empty() {
|
||||||
let list_var = var_store.fresh();
|
let list_var = *variable;
|
||||||
let inferred = constrain::lift(var_store, constrain::empty_list_type(list_var));
|
let inferred = constrain::lift(var_store, constrain::empty_list_type(list_var));
|
||||||
let constraint = Eq(inferred, expected, region);
|
let constraint = Eq(inferred, expected, region);
|
||||||
(Output::default(), constraint)
|
(Output::default(), constraint)
|
||||||
} else {
|
} else {
|
||||||
// constrain `expected ~ List a` and that all elements `~ a`.
|
// constrain `expected ~ List a` and that all elements `~ a`.
|
||||||
let list_var = var_store.fresh(); // `v` in the type (List v)
|
let list_var = *variable; // `v` in the type (List v)
|
||||||
let list_type = Type::Variable(list_var);
|
let list_type = Type::Variable(list_var);
|
||||||
let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2));
|
let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2));
|
||||||
let mut references = References::new();
|
let mut references = References::new();
|
||||||
|
|
||||||
for loc_elem in loc_elems.iter() {
|
for (elem_var, loc_elem) in loc_elems.iter() {
|
||||||
let elem_var = var_store.fresh();
|
let elem_type = Variable(*elem_var);
|
||||||
let elem_type = Variable(elem_var);
|
|
||||||
let elem_expected = Expected::NoExpectation(elem_type.clone());
|
let elem_expected = Expected::NoExpectation(elem_type.clone());
|
||||||
let list_elem_constraint = Eq(
|
let list_elem_constraint = Eq(
|
||||||
list_type.clone(),
|
list_type.clone(),
|
||||||
|
@ -198,9 +296,11 @@ pub fn canonicalize_expr(
|
||||||
(output, And(constraints))
|
(output, And(constraints))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Var(_variable, symbol) => {
|
Var {
|
||||||
var_usage.register(symbol);
|
symbol_for_lookup, ..
|
||||||
match var_usage.get_usage(symbol) {
|
} => {
|
||||||
|
var_usage.register(symbol_for_lookup);
|
||||||
|
match var_usage.get_usage(symbol_for_lookup) {
|
||||||
Some(sharing::ReferenceCount::Shared) => {
|
Some(sharing::ReferenceCount::Shared) => {
|
||||||
// the variable is used/consumed more than once, so it must be Shared
|
// the variable is used/consumed more than once, so it must be Shared
|
||||||
let val_var = var_store.fresh();
|
let val_var = var_store.fresh();
|
||||||
|
@ -208,12 +308,13 @@ pub fn canonicalize_expr(
|
||||||
|
|
||||||
let val_type = Variable(val_var);
|
let val_type = Variable(val_var);
|
||||||
let uniq_type = Variable(uniq_var);
|
let uniq_type = Variable(uniq_var);
|
||||||
|
|
||||||
let attr_type = constrain::attr_type(uniq_type.clone(), val_type);
|
let attr_type = constrain::attr_type(uniq_type.clone(), val_type);
|
||||||
|
|
||||||
(
|
(
|
||||||
Output::default(),
|
Output::default(),
|
||||||
And(vec![
|
And(vec![
|
||||||
Lookup(symbol.clone(), expected.clone(), region),
|
Lookup(symbol_for_lookup.clone(), expected.clone(), region),
|
||||||
Eq(attr_type, expected, region),
|
Eq(attr_type, expected, region),
|
||||||
Eq(
|
Eq(
|
||||||
uniq_type,
|
uniq_type,
|
||||||
|
@ -227,20 +328,15 @@ pub fn canonicalize_expr(
|
||||||
// no additional constraints, keep uniqueness unbound
|
// no additional constraints, keep uniqueness unbound
|
||||||
(
|
(
|
||||||
Output::default(),
|
Output::default(),
|
||||||
Lookup(symbol.clone(), expected.clone(), region),
|
Lookup(symbol_for_lookup.clone(), expected.clone(), region),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => panic!("symbol not analyzed"),
|
None => panic!("symbol not analyzed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
Closure(_symbol, _recursion, args, boxed_body) => {
|
||||||
FunctionPointer(_variable, symbol) => match env.bound_names.get(symbol) {
|
let (body, ret_var) = &**boxed_body;
|
||||||
// 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())),
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
Closure(_symbol, _recursion, args, body) => {
|
|
||||||
// first, generate constraints for the arguments
|
// first, generate constraints for the arguments
|
||||||
let mut arg_types = Vec::new();
|
let mut arg_types = Vec::new();
|
||||||
let mut arg_vars = Vec::new();
|
let mut arg_vars = Vec::new();
|
||||||
|
@ -251,23 +347,25 @@ pub fn canonicalize_expr(
|
||||||
constraints: Vec::with_capacity(1),
|
constraints: Vec::with_capacity(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
for pattern in args {
|
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
|
||||||
let arg_var = var_store.fresh();
|
let ret_type = Variable(*ret_var);
|
||||||
let arg_typ = Variable(arg_var);
|
|
||||||
|
vars.push(*ret_var);
|
||||||
|
|
||||||
|
for (arg_var, pattern) in args {
|
||||||
|
let arg_typ = Variable(*arg_var);
|
||||||
canonicalize_pattern(
|
canonicalize_pattern(
|
||||||
|
var_store,
|
||||||
&mut state,
|
&mut state,
|
||||||
&pattern,
|
&pattern,
|
||||||
PExpected::NoExpectation(arg_typ.clone()),
|
PExpected::NoExpectation(arg_typ.clone()),
|
||||||
);
|
);
|
||||||
arg_types.push(arg_typ);
|
arg_types.push(arg_typ);
|
||||||
arg_vars.push(arg_var);
|
arg_vars.push(arg_var);
|
||||||
|
|
||||||
|
vars.push(*arg_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret_var = var_store.fresh();
|
|
||||||
let ret_type = Variable(ret_var);
|
|
||||||
|
|
||||||
state.vars.push(ret_var);
|
|
||||||
|
|
||||||
let fn_typ = constrain::lift(
|
let fn_typ = constrain::lift(
|
||||||
var_store,
|
var_store,
|
||||||
Type::Function(arg_types, Box::new(ret_type.clone())),
|
Type::Function(arg_types, Box::new(ret_type.clone())),
|
||||||
|
@ -283,7 +381,7 @@ pub fn canonicalize_expr(
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove identifiers bound in the arguments from VarUsage
|
// remove identifiers bound in the arguments from VarUsage
|
||||||
for pattern in args {
|
for (_, pattern) in args {
|
||||||
for identifier in pattern::symbols_from_pattern(&pattern.value) {
|
for identifier in pattern::symbols_from_pattern(&pattern.value) {
|
||||||
var_usage.unregister(&identifier);
|
var_usage.unregister(&identifier);
|
||||||
}
|
}
|
||||||
|
@ -291,7 +389,7 @@ pub fn canonicalize_expr(
|
||||||
|
|
||||||
let defs_constraint = And(state.constraints);
|
let defs_constraint = And(state.constraints);
|
||||||
let constraint = exists(
|
let constraint = exists(
|
||||||
state.vars.clone(),
|
vars,
|
||||||
And(vec![
|
And(vec![
|
||||||
Let(Box::new(LetConstraint {
|
Let(Box::new(LetConstraint {
|
||||||
rigid_vars: Vec::new(),
|
rigid_vars: Vec::new(),
|
||||||
|
@ -308,13 +406,12 @@ pub fn canonicalize_expr(
|
||||||
(output, constraint)
|
(output, constraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
Call(fn_expr, loc_args, _) => {
|
Call(boxed, loc_args, _) => {
|
||||||
let fn_var = var_store.fresh();
|
let (fn_var, fn_expr, ret_var) = &**boxed;
|
||||||
let fn_type = Variable(fn_var);
|
let fn_type = Variable(*fn_var);
|
||||||
let ret_var = var_store.fresh();
|
let ret_type = Variable(*ret_var);
|
||||||
let ret_type = Variable(ret_var);
|
|
||||||
let fn_expected = Expected::NoExpectation(fn_type.clone());
|
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());
|
let mut vars = Vec::with_capacity(2 + loc_args.len());
|
||||||
|
|
||||||
|
@ -324,7 +421,7 @@ pub fn canonicalize_expr(
|
||||||
var_store,
|
var_store,
|
||||||
var_usage,
|
var_usage,
|
||||||
fn_region,
|
fn_region,
|
||||||
&fn_expr,
|
&fn_expr.value,
|
||||||
fn_expected,
|
fn_expected,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -336,10 +433,9 @@ pub fn canonicalize_expr(
|
||||||
let mut arg_types = Vec::with_capacity(loc_args.len());
|
let mut arg_types = Vec::with_capacity(loc_args.len());
|
||||||
let mut arg_cons = Vec::with_capacity(loc_args.len());
|
let mut arg_cons = Vec::with_capacity(loc_args.len());
|
||||||
|
|
||||||
for (index, loc_arg) in loc_args.iter().enumerate() {
|
for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() {
|
||||||
let region = loc_arg.region;
|
let region = loc_arg.region;
|
||||||
let arg_var = var_store.fresh();
|
let arg_type = Variable(*arg_var);
|
||||||
let arg_type = Variable(arg_var);
|
|
||||||
|
|
||||||
let reason = Reason::AnonymousFnArg {
|
let reason = Reason::AnonymousFnArg {
|
||||||
arg_index: index as u8,
|
arg_index: index as u8,
|
||||||
|
@ -355,7 +451,7 @@ pub fn canonicalize_expr(
|
||||||
expected_arg,
|
expected_arg,
|
||||||
);
|
);
|
||||||
|
|
||||||
vars.push(arg_var);
|
vars.push(*arg_var);
|
||||||
arg_types.push(arg_type);
|
arg_types.push(arg_type);
|
||||||
arg_cons.push(arg_con);
|
arg_cons.push(arg_con);
|
||||||
}
|
}
|
||||||
|
@ -380,18 +476,17 @@ pub fn canonicalize_expr(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Defs(_, defs, loc_ret) => {
|
Defs(defs, loc_ret) => (
|
||||||
// The body expression gets a new scope for canonicalization,
|
|
||||||
// so clone it.
|
|
||||||
|
|
||||||
(
|
|
||||||
Output::default(),
|
Output::default(),
|
||||||
can_defs(rigids, var_store, var_usage, defs, expected, loc_ret),
|
can_defs(rigids, var_store, var_usage, defs, expected, loc_ret),
|
||||||
)
|
),
|
||||||
}
|
When {
|
||||||
// Case( Variable, Box<Located<Expr>>, Vec<(Located<Pattern>, Located<Expr>)>,
|
cond_var,
|
||||||
Case(_variable, loc_cond, branches) => {
|
loc_cond,
|
||||||
let cond_var = var_store.fresh();
|
branches,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let cond_var = *cond_var;
|
||||||
let cond_type = Variable(cond_var);
|
let cond_type = Variable(cond_var);
|
||||||
let (mut output, expr_con) = canonicalize_expr(
|
let (mut output, expr_con) = canonicalize_expr(
|
||||||
rigids,
|
rigids,
|
||||||
|
@ -410,7 +505,7 @@ pub fn canonicalize_expr(
|
||||||
Expected::FromAnnotation(name, arity, _, typ) => {
|
Expected::FromAnnotation(name, arity, _, typ) => {
|
||||||
for (index, (loc_pattern, 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 mut branch_var_usage = old_var_usage.clone();
|
||||||
let branch_con = canonicalize_case_branch(
|
let branch_con = canonicalize_when_branch(
|
||||||
var_store,
|
var_store,
|
||||||
&mut branch_var_usage,
|
&mut branch_var_usage,
|
||||||
rigids,
|
rigids,
|
||||||
|
@ -418,14 +513,14 @@ pub fn canonicalize_expr(
|
||||||
loc_pattern,
|
loc_pattern,
|
||||||
loc_expr,
|
loc_expr,
|
||||||
PExpected::ForReason(
|
PExpected::ForReason(
|
||||||
PReason::CaseMatch { index },
|
PReason::WhenMatch { index },
|
||||||
cond_type.clone(),
|
cond_type.clone(),
|
||||||
region,
|
region,
|
||||||
),
|
),
|
||||||
Expected::FromAnnotation(
|
Expected::FromAnnotation(
|
||||||
name.clone(),
|
name.clone(),
|
||||||
arity,
|
arity,
|
||||||
TypedCaseBranch(index),
|
TypedWhenBranch(index),
|
||||||
typ.clone(),
|
typ.clone(),
|
||||||
),
|
),
|
||||||
&mut output,
|
&mut output,
|
||||||
|
@ -433,7 +528,7 @@ pub fn canonicalize_expr(
|
||||||
|
|
||||||
// required for a case like
|
// required for a case like
|
||||||
//
|
//
|
||||||
// case b when
|
// when b is
|
||||||
// Foo x -> x + x
|
// Foo x -> x + x
|
||||||
// Bar x -> x
|
// Bar x -> x
|
||||||
//
|
//
|
||||||
|
@ -460,7 +555,7 @@ pub fn canonicalize_expr(
|
||||||
|
|
||||||
for (index, (loc_pattern, 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 mut branch_var_usage = old_var_usage.clone();
|
||||||
let branch_con = canonicalize_case_branch(
|
let branch_con = canonicalize_when_branch(
|
||||||
var_store,
|
var_store,
|
||||||
&mut branch_var_usage,
|
&mut branch_var_usage,
|
||||||
rigids,
|
rigids,
|
||||||
|
@ -468,12 +563,12 @@ pub fn canonicalize_expr(
|
||||||
loc_pattern,
|
loc_pattern,
|
||||||
loc_expr,
|
loc_expr,
|
||||||
PExpected::ForReason(
|
PExpected::ForReason(
|
||||||
PReason::CaseMatch { index },
|
PReason::WhenMatch { index },
|
||||||
cond_type.clone(),
|
cond_type.clone(),
|
||||||
region,
|
region,
|
||||||
),
|
),
|
||||||
Expected::ForReason(
|
Expected::ForReason(
|
||||||
Reason::CaseBranch { index },
|
Reason::WhenBranch { index },
|
||||||
branch_type.clone(),
|
branch_type.clone(),
|
||||||
region,
|
region,
|
||||||
),
|
),
|
||||||
|
@ -514,14 +609,82 @@ pub fn canonicalize_expr(
|
||||||
|
|
||||||
(output, And(constraints))
|
(output, And(constraints))
|
||||||
}
|
}
|
||||||
_ => panic!("{:?}", expr),
|
|
||||||
|
Access {
|
||||||
|
ext_var,
|
||||||
|
field_var,
|
||||||
|
loc_expr,
|
||||||
|
field,
|
||||||
|
} => {
|
||||||
|
let ext_type = Type::Variable(*ext_var);
|
||||||
|
let field_type = Type::Variable(*field_var);
|
||||||
|
|
||||||
|
let mut rec_field_types = SendMap::default();
|
||||||
|
|
||||||
|
rec_field_types.insert(
|
||||||
|
RecordFieldLabel::Required(field.clone()),
|
||||||
|
field_type.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let record_type =
|
||||||
|
constrain::lift(var_store, Type::Record(rec_field_types, Box::new(ext_type)));
|
||||||
|
let record_expected = Expected::NoExpectation(record_type);
|
||||||
|
|
||||||
|
let (output, mut constraint) = canonicalize_expr(
|
||||||
|
rigids,
|
||||||
|
var_store,
|
||||||
|
var_usage,
|
||||||
|
loc_expr.region,
|
||||||
|
&loc_expr.value,
|
||||||
|
record_expected,
|
||||||
|
);
|
||||||
|
|
||||||
|
constraint = exists(
|
||||||
|
vec![*field_var, *ext_var],
|
||||||
|
And(vec![constraint, Eq(field_type, expected, region)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
(output, constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
Accessor {
|
||||||
|
field,
|
||||||
|
field_var,
|
||||||
|
ext_var,
|
||||||
|
} => {
|
||||||
|
let ext_type = Variable(*ext_var);
|
||||||
|
let field_type = Variable(*field_var);
|
||||||
|
let mut field_types = SendMap::default();
|
||||||
|
|
||||||
|
field_types.insert(
|
||||||
|
RecordFieldLabel::Required(field.clone()),
|
||||||
|
field_type.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let record_type =
|
||||||
|
constrain::lift(var_store, Type::Record(field_types, Box::new(ext_type)));
|
||||||
|
|
||||||
|
(
|
||||||
|
Output::default(),
|
||||||
|
exists(
|
||||||
|
vec![*field_var, *ext_var],
|
||||||
|
Eq(
|
||||||
|
Type::Function(vec![record_type], Box::new(field_type)),
|
||||||
|
expected,
|
||||||
|
region,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RuntimeError(_) => (Output::default(), True),
|
||||||
|
// _ => panic!("{:?}", expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO trim down these arguments
|
// TODO trim down these arguments
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn canonicalize_case_branch(
|
fn canonicalize_when_branch(
|
||||||
var_store: &VarStore,
|
var_store: &VarStore,
|
||||||
var_usage: &mut VarUsage,
|
var_usage: &mut VarUsage,
|
||||||
rigids: &Rigids,
|
rigids: &Rigids,
|
||||||
|
@ -548,7 +711,7 @@ fn canonicalize_case_branch(
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutates the state, so return value is not used
|
// mutates the state, so return value is not used
|
||||||
canonicalize_pattern(&mut state, &loc_pattern, pattern_expected);
|
canonicalize_pattern(var_store, &mut state, &loc_pattern, pattern_expected);
|
||||||
|
|
||||||
Constraint::Let(Box::new(LetConstraint {
|
Constraint::Let(Box::new(LetConstraint {
|
||||||
rigid_vars: Vec::new(),
|
rigid_vars: Vec::new(),
|
||||||
|
@ -583,7 +746,7 @@ fn add_pattern_to_lookup_types(
|
||||||
let region = loc_pattern.region;
|
let region = loc_pattern.region;
|
||||||
|
|
||||||
match loc_pattern.value {
|
match loc_pattern.value {
|
||||||
Pattern::Identifier(_, symbol) => {
|
Pattern::Identifier(symbol) => {
|
||||||
let loc_type = Located {
|
let loc_type = Located {
|
||||||
region,
|
region,
|
||||||
value: expr_type,
|
value: expr_type,
|
||||||
|
@ -618,7 +781,7 @@ fn can_defs(
|
||||||
constraints: Vec::with_capacity(1),
|
constraints: Vec::with_capacity(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
canonicalize_pattern(&mut state, &def.pattern, pattern_expected);
|
canonicalize_pattern(var_store, &mut state, &def.loc_pattern, pattern_expected);
|
||||||
|
|
||||||
flex_info.vars.push(pattern_var);
|
flex_info.vars.push(pattern_var);
|
||||||
|
|
||||||
|
@ -628,19 +791,19 @@ fn can_defs(
|
||||||
rigids,
|
rigids,
|
||||||
var_store,
|
var_store,
|
||||||
var_usage,
|
var_usage,
|
||||||
def.expr.region,
|
def.loc_expr.region,
|
||||||
&def.expr.value,
|
&def.loc_expr.value,
|
||||||
Expected::NoExpectation(expr_type.clone()),
|
Expected::NoExpectation(expr_type.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
add_pattern_to_lookup_types(
|
add_pattern_to_lookup_types(
|
||||||
// TODO can we we avoid this clone?
|
// TODO can we we avoid this clone?
|
||||||
def.pattern.clone(),
|
def.loc_pattern.clone(),
|
||||||
&mut flex_info.def_types,
|
&mut flex_info.def_types,
|
||||||
expr_type.clone(),
|
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 {
|
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
||||||
rigid_vars: Vec::new(),
|
rigid_vars: Vec::new(),
|
||||||
|
|
|
@ -9,13 +9,14 @@ use roc::can::problem::Problem;
|
||||||
use roc::can::scope::Scope;
|
use roc::can::scope::Scope;
|
||||||
use roc::can::symbol::Symbol;
|
use roc::can::symbol::Symbol;
|
||||||
use roc::collections::{ImMap, MutMap, SendSet};
|
use roc::collections::{ImMap, MutMap, SendSet};
|
||||||
|
use roc::constrain::expr::constrain_expr;
|
||||||
use roc::ident::Ident;
|
use roc::ident::Ident;
|
||||||
use roc::parse;
|
use roc::parse;
|
||||||
use roc::parse::ast::{self, Attempting};
|
use roc::parse::ast::{self, Attempting};
|
||||||
use roc::parse::blankspace::space0_before;
|
use roc::parse::blankspace::space0_before;
|
||||||
use roc::parse::parser::{loc, Fail, Parser, State};
|
use roc::parse::parser::{loc, Fail, Parser, State};
|
||||||
use roc::region::{Located, Region};
|
use roc::region::{Located, Region};
|
||||||
use roc::subs::{VarStore, Variable};
|
use roc::subs::{Subs, VarStore, Variable};
|
||||||
use roc::types::{Constraint, Expected, Type};
|
use roc::types::{Constraint, Expected, Type};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -91,9 +92,9 @@ pub fn uniq_expr(
|
||||||
Output,
|
Output,
|
||||||
Output,
|
Output,
|
||||||
Vec<Problem>,
|
Vec<Problem>,
|
||||||
VarStore,
|
Subs,
|
||||||
Variable,
|
Variable,
|
||||||
VarStore,
|
Subs,
|
||||||
Variable,
|
Variable,
|
||||||
Constraint,
|
Constraint,
|
||||||
Constraint,
|
Constraint,
|
||||||
|
@ -110,9 +111,9 @@ pub fn uniq_expr_with(
|
||||||
Output,
|
Output,
|
||||||
Output,
|
Output,
|
||||||
Vec<Problem>,
|
Vec<Problem>,
|
||||||
VarStore,
|
Subs,
|
||||||
Variable,
|
Variable,
|
||||||
VarStore,
|
Subs,
|
||||||
Variable,
|
Variable,
|
||||||
Constraint,
|
Constraint,
|
||||||
Constraint,
|
Constraint,
|
||||||
|
@ -121,8 +122,10 @@ pub fn uniq_expr_with(
|
||||||
let (loc_expr, output, problems, var_store1, variable, constraint1) =
|
let (loc_expr, output, problems, var_store1, variable, constraint1) =
|
||||||
can_expr_with(arena, home, expr_str, &ImMap::default());
|
can_expr_with(arena, home, expr_str, &ImMap::default());
|
||||||
|
|
||||||
|
let var_count: usize = var_store1.into();
|
||||||
|
let subs1 = Subs::new(var_count);
|
||||||
// double check
|
// double check
|
||||||
let var_store2 = VarStore::default();
|
let var_store2 = VarStore::new(var_count);
|
||||||
|
|
||||||
let variable2 = var_store2.fresh();
|
let variable2 = var_store2.fresh();
|
||||||
let expected2 = Expected::NoExpectation(Type::Variable(variable2));
|
let expected2 = Expected::NoExpectation(Type::Variable(variable2));
|
||||||
|
@ -134,13 +137,15 @@ pub fn uniq_expr_with(
|
||||||
expected2,
|
expected2,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let subs2 = Subs::new(var_store2.into());
|
||||||
|
|
||||||
(
|
(
|
||||||
output2,
|
output2,
|
||||||
output,
|
output,
|
||||||
problems,
|
problems,
|
||||||
var_store1,
|
subs1,
|
||||||
variable,
|
variable,
|
||||||
var_store2,
|
subs2,
|
||||||
variable2,
|
variable2,
|
||||||
constraint1,
|
constraint1,
|
||||||
constraint2,
|
constraint2,
|
||||||
|
@ -187,13 +192,18 @@ pub fn can_expr_with(
|
||||||
let scope_prefix = format!("{}.{}$", home, name).into();
|
let scope_prefix = format!("{}.{}$", home, name).into();
|
||||||
let mut scope = Scope::new(scope_prefix, declared_idents.clone());
|
let mut scope = Scope::new(scope_prefix, declared_idents.clone());
|
||||||
let mut env = Env::new(home.into());
|
let mut env = Env::new(home.into());
|
||||||
let (loc_expr, output, constraint) = canonicalize_expr(
|
let (loc_expr, output) = canonicalize_expr(
|
||||||
&ImMap::default(),
|
|
||||||
&mut env,
|
&mut env,
|
||||||
&var_store,
|
&var_store,
|
||||||
&mut scope,
|
&mut scope,
|
||||||
Region::zero(),
|
Region::zero(),
|
||||||
&loc_expr.value,
|
&loc_expr.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
let constraint = constrain_expr(
|
||||||
|
&ImMap::default(),
|
||||||
|
loc_expr.region,
|
||||||
|
&loc_expr.value,
|
||||||
expected,
|
expected,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,33 @@ mod test_canonicalize {
|
||||||
assert_eq!(actual.value, expected);
|
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
|
// NUMBER LITERALS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -106,67 +133,67 @@ mod test_canonicalize {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero() {
|
fn zero() {
|
||||||
assert_can("0", Int(0));
|
assert_can_int("0", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn minus_zero() {
|
fn minus_zero() {
|
||||||
assert_can("-0", Int(0));
|
assert_can_int("-0", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_point_zero() {
|
fn zero_point_zero() {
|
||||||
assert_can("0.0", Float(0.0));
|
assert_can_float("0.0", 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn minus_zero_point_zero() {
|
fn minus_zero_point_zero() {
|
||||||
assert_can("-0.0", Float(-0.0));
|
assert_can_float("-0.0", -0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hex_zero() {
|
fn hex_zero() {
|
||||||
assert_can("0x0", Int(0x0));
|
assert_can_int("0x0", 0x0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hex_one_b() {
|
fn hex_one_b() {
|
||||||
assert_can("0x1b", Int(0x1b));
|
assert_can_int("0x1b", 0x1b);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn minus_hex_one_b() {
|
fn minus_hex_one_b() {
|
||||||
assert_can("-0x1b", Int(-0x1b));
|
assert_can_int("-0x1b", -0x1b);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn octal_zero() {
|
fn octal_zero() {
|
||||||
assert_can("0o0", Int(0o0));
|
assert_can_int("0o0", 0o0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn octal_one_two() {
|
fn octal_one_two() {
|
||||||
assert_can("0o12", Int(0o12));
|
assert_can_int("0o12", 0o12);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn minus_octal_one_two() {
|
fn minus_octal_one_two() {
|
||||||
assert_can("-0o12", Int(-0o12));
|
assert_can_int("-0o12", -0o12);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn binary_zero() {
|
fn binary_zero() {
|
||||||
assert_can("0b0", Int(0b0));
|
assert_can_int("0b0", 0b0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn binary_one_one() {
|
fn binary_one_one() {
|
||||||
assert_can("0b11", Int(0b11));
|
assert_can_int("0b11", 0b11);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn minus_binary_one_one() {
|
fn minus_binary_one_one() {
|
||||||
assert_can("-0b11", Int(-0b11));
|
assert_can_int("-0b11", -0b11);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOCALS
|
// LOCALS
|
||||||
|
@ -232,7 +259,7 @@ mod test_canonicalize {
|
||||||
|
|
||||||
fn get_closure(expr: &Expr, i: usize) -> roc::can::expr::Recursive {
|
fn get_closure(expr: &Expr, i: usize) -> roc::can::expr::Recursive {
|
||||||
match expr {
|
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(Closure(_, recursion, _, _)) => recursion.clone(),
|
||||||
Some(other @ _) => {
|
Some(other @ _) => {
|
||||||
panic!("assignment at {} is not a closure, but a {:?}", i, other)
|
panic!("assignment at {} is not a closure, but a {:?}", i, other)
|
||||||
|
@ -249,17 +276,17 @@ mod test_canonicalize {
|
||||||
let src = indoc!(
|
let src = indoc!(
|
||||||
r#"
|
r#"
|
||||||
g = \x ->
|
g = \x ->
|
||||||
case x when
|
when x is
|
||||||
0 -> 0
|
0 -> 0
|
||||||
_ -> g (x - 1)
|
_ -> g (x - 1)
|
||||||
|
|
||||||
h = \x ->
|
h = \x ->
|
||||||
case x when
|
when x is
|
||||||
0 -> 0
|
0 -> 0
|
||||||
_ -> g (x - 1)
|
_ -> g (x - 1)
|
||||||
|
|
||||||
p = \x ->
|
p = \x ->
|
||||||
case x when
|
when x is
|
||||||
0 -> 0
|
0 -> 0
|
||||||
1 -> g (x - 1)
|
1 -> g (x - 1)
|
||||||
_ -> p (x - 1)
|
_ -> p (x - 1)
|
||||||
|
@ -283,12 +310,12 @@ mod test_canonicalize {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_tail_call() {
|
fn when_tail_call() {
|
||||||
with_larger_debug_stack(|| {
|
with_larger_debug_stack(|| {
|
||||||
let src = indoc!(
|
let src = indoc!(
|
||||||
r#"
|
r#"
|
||||||
g = \x ->
|
g = \x ->
|
||||||
case x when
|
when x is
|
||||||
0 -> 0
|
0 -> 0
|
||||||
_ -> g (x + 1)
|
_ -> g (x + 1)
|
||||||
|
|
||||||
|
@ -322,12 +349,12 @@ mod test_canonicalize {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_condition_is_no_tail_call() {
|
fn when_condition_is_no_tail_call() {
|
||||||
// TODO when a case witn no branches parses, remove the pattern wildcard here
|
// TODO when a case witn no branches parses, remove the pattern wildcard here
|
||||||
let src = indoc!(
|
let src = indoc!(
|
||||||
r#"
|
r#"
|
||||||
q = \x ->
|
q = \x ->
|
||||||
case q x when
|
when q x is
|
||||||
_ -> 0
|
_ -> 0
|
||||||
|
|
||||||
0
|
0
|
||||||
|
@ -348,12 +375,12 @@ mod test_canonicalize {
|
||||||
let src = indoc!(
|
let src = indoc!(
|
||||||
r#"
|
r#"
|
||||||
q = \x ->
|
q = \x ->
|
||||||
case x when
|
when x is
|
||||||
0 -> 0
|
0 -> 0
|
||||||
_ -> p (x - 1)
|
_ -> p (x - 1)
|
||||||
|
|
||||||
p = \x ->
|
p = \x ->
|
||||||
case x when
|
when x is
|
||||||
0 -> 0
|
0 -> 0
|
||||||
_ -> q (x - 1)
|
_ -> q (x - 1)
|
||||||
|
|
||||||
|
|
|
@ -83,11 +83,11 @@ mod test_gen {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_case_take_first_branch() {
|
fn gen_when_take_first_branch() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case 1 when
|
when 1 is
|
||||||
1 -> 12
|
1 -> 12
|
||||||
_ -> 34
|
_ -> 34
|
||||||
"#
|
"#
|
||||||
|
@ -98,11 +98,11 @@ mod test_gen {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_case_take_second_branch() {
|
fn gen_when_take_second_branch() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case 2 when
|
when 2 is
|
||||||
1 -> 63
|
1 -> 63
|
||||||
_ -> 48
|
_ -> 48
|
||||||
"#
|
"#
|
||||||
|
|
|
@ -38,7 +38,7 @@ mod test_format {
|
||||||
Ok(actual) => {
|
Ok(actual) => {
|
||||||
let mut buf = String::new_in(&arena);
|
let mut buf = String::new_in(&arena);
|
||||||
|
|
||||||
fmt_expr(&mut buf, &actual, 0, false);
|
fmt_expr(&mut buf, &actual, 0, false, true);
|
||||||
|
|
||||||
assert_eq!(buf, expected)
|
assert_eq!(buf, expected)
|
||||||
},
|
},
|
||||||
|
@ -467,7 +467,7 @@ mod test_format {
|
||||||
// fn record_field_destructuring() {
|
// fn record_field_destructuring() {
|
||||||
// expr_formats_same(indoc!(
|
// expr_formats_same(indoc!(
|
||||||
// r#"
|
// r#"
|
||||||
// case foo when
|
// when foo is
|
||||||
// { x: 5 } -> 42
|
// { x: 5 } -> 42
|
||||||
// "#
|
// "#
|
||||||
// ));
|
// ));
|
||||||
|
@ -540,6 +540,24 @@ mod test_format {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LIST
|
||||||
|
#[test]
|
||||||
|
fn empty_list() {
|
||||||
|
expr_formats_same("[]");
|
||||||
|
expr_formats_to("[ ]", "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_item_list() {
|
||||||
|
expr_formats_same(indoc!("[ 4 ] "));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_item_list() {
|
||||||
|
expr_formats_same(indoc!("[ 7, 8 ] "));
|
||||||
|
expr_formats_to(indoc!("[ 7 , 8 ] "), indoc!("[ 7, 8 ] "));
|
||||||
|
}
|
||||||
|
|
||||||
// RECORD LITERALS
|
// RECORD LITERALS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -618,13 +636,187 @@ mod test_format {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integer_case() {
|
fn multi_line_if_condition() {
|
||||||
|
// expr_formats_same(indoc!(
|
||||||
|
// r#"
|
||||||
|
// if
|
||||||
|
// waterWillBoil pressure temperature
|
||||||
|
// then
|
||||||
|
// turnOnAc
|
||||||
|
//
|
||||||
|
// else
|
||||||
|
// identity
|
||||||
|
// "#
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// expr_formats_to(
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// if
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// willBoil home water
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// then
|
||||||
|
// \_ -> leave
|
||||||
|
//
|
||||||
|
// else
|
||||||
|
// identity
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// if
|
||||||
|
// willBoil home water
|
||||||
|
// then
|
||||||
|
// \_ -> leave
|
||||||
|
//
|
||||||
|
// else
|
||||||
|
// identity
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn if_removes_newlines() {
|
||||||
|
// expr_formats_to(
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// if
|
||||||
|
//
|
||||||
|
// # You never know!
|
||||||
|
// isPrime 8
|
||||||
|
//
|
||||||
|
// # Top Comment
|
||||||
|
//
|
||||||
|
// # Bottom Comment
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// then
|
||||||
|
//
|
||||||
|
// # A
|
||||||
|
//
|
||||||
|
// # B
|
||||||
|
//
|
||||||
|
// nothing
|
||||||
|
//
|
||||||
|
// # B again
|
||||||
|
//
|
||||||
|
// else
|
||||||
|
//
|
||||||
|
// # C
|
||||||
|
// # D
|
||||||
|
//
|
||||||
|
// # E
|
||||||
|
// # F
|
||||||
|
//
|
||||||
|
// just (div 1 8)
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// if
|
||||||
|
// # You never know!
|
||||||
|
// isPrime 8
|
||||||
|
// # Top Comment
|
||||||
|
// # Bottom Comment
|
||||||
|
// then
|
||||||
|
// # A
|
||||||
|
// # B
|
||||||
|
// nothing
|
||||||
|
// # B again
|
||||||
|
//
|
||||||
|
// else
|
||||||
|
// # C
|
||||||
|
// # D
|
||||||
|
// # E
|
||||||
|
// # F
|
||||||
|
// just (div 1 8)
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn multi_line_if() {
|
||||||
|
expr_formats_to(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
if lessThan four five then
|
||||||
|
four
|
||||||
|
else
|
||||||
|
five
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
if lessThan four five then
|
||||||
|
four
|
||||||
|
|
||||||
|
else
|
||||||
|
five
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expr_formats_to(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
if lessThan three four then
|
||||||
|
|
||||||
|
|
||||||
|
three
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
|
||||||
|
four
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
if lessThan three four then
|
||||||
|
three
|
||||||
|
|
||||||
|
else
|
||||||
|
four
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
if foo bar then
|
||||||
|
a b c
|
||||||
|
|
||||||
|
else
|
||||||
|
d e f
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn multi_line_application() {
|
||||||
|
// expr_formats_same(indoc!(
|
||||||
|
// r#"
|
||||||
|
// combine
|
||||||
|
// peanutButter
|
||||||
|
// chocolate
|
||||||
|
// "#
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn integer_when() {
|
||||||
|
expr_formats_same(indoc!(
|
||||||
|
r#"
|
||||||
|
when b is
|
||||||
1 ->
|
1 ->
|
||||||
1
|
1
|
||||||
|
|
||||||
|
@ -635,13 +827,44 @@ mod test_format {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_comments() {
|
fn integer_when_with_space() {
|
||||||
|
expr_formats_to(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when year is
|
||||||
|
1999 ->
|
||||||
|
|
||||||
|
|
||||||
|
1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
|
||||||
|
0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when year is
|
||||||
|
1999 ->
|
||||||
|
1
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_with_comments() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
when b is
|
||||||
# look at cases
|
# look at cases
|
||||||
1 ->
|
1 ->
|
||||||
# case 1
|
# when 1
|
||||||
1
|
1
|
||||||
|
|
||||||
# important
|
# important
|
||||||
|
@ -656,12 +879,12 @@ mod test_format {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_case() {
|
fn nested_when() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
when b is
|
||||||
_ ->
|
_ ->
|
||||||
case c when
|
when c is
|
||||||
_ ->
|
_ ->
|
||||||
1
|
1
|
||||||
"#
|
"#
|
||||||
|
@ -669,13 +892,13 @@ mod test_format {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_moving_comments() {
|
fn when_with_moving_comments() {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
when b is
|
||||||
1 ->
|
1 ->
|
||||||
1 # case 1
|
1 # when 1
|
||||||
|
|
||||||
# fall through
|
# fall through
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -684,11 +907,11 @@ mod test_format {
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
when b is
|
||||||
1 ->
|
1 ->
|
||||||
1
|
1
|
||||||
|
|
||||||
# case 1
|
# when 1
|
||||||
# fall through
|
# fall through
|
||||||
_ ->
|
_ ->
|
||||||
2
|
2
|
||||||
|
|
|
@ -495,6 +495,39 @@ mod test_infer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn identity_infers_principal_type() {
|
||||||
|
// infer_eq(
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// identity = \a -> a
|
||||||
|
|
||||||
|
// x = identity 5
|
||||||
|
|
||||||
|
// identity
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// "a -> a",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn identity_works_on_incompatible_types() {
|
||||||
|
// infer_eq(
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// identity = \a -> a
|
||||||
|
|
||||||
|
// x = identity 5
|
||||||
|
// y = identity "hi"
|
||||||
|
|
||||||
|
// x
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// "Int",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn call_returns_list() {
|
fn call_returns_list() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
|
@ -796,11 +829,11 @@ mod test_infer {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_int_literals() {
|
fn when_with_int_literals() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case 1 when
|
when 1 is
|
||||||
1 -> 2
|
1 -> 2
|
||||||
3 -> 4
|
3 -> 4
|
||||||
"#
|
"#
|
||||||
|
@ -870,8 +903,111 @@ mod test_infer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_signature_without_body() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
foo: Int -> Bool
|
||||||
|
|
||||||
|
foo 2
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Bool",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_signature_without_body_rigid() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
foo : Int -> custom
|
||||||
|
|
||||||
|
foo 2
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"custom",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn accessor_function() {
|
fn accessor_function() {
|
||||||
infer_eq(".foo", "{ foo : a }* -> a");
|
infer_eq(".foo", "{ foo : a }* -> a");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_signature_without_body_record() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
{ x, y } : { x : (Int -> custom) , y : Int }
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Int -> custom",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_pattern_match_infer() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when foo is
|
||||||
|
{ x: 4 }-> x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Int",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_record_pattern() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
# technically, an empty record can be destructured
|
||||||
|
{} = {}
|
||||||
|
bar = \{} -> 42
|
||||||
|
|
||||||
|
when foo is
|
||||||
|
{ x: {} } -> x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{}*",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_type_annotation() {
|
||||||
|
// check that a closed record remains closed
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
foo : { x : custom } -> custom
|
||||||
|
foo = \{ x } -> x
|
||||||
|
|
||||||
|
foo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x : custom } -> custom",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_field() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
foo : { x? : Int } -> Int
|
||||||
|
foo = \_ -> 42
|
||||||
|
|
||||||
|
foo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x? : Int } -> Int",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,10 +173,10 @@ mod test_load {
|
||||||
assert_eq!(expected_types.len(), module.defs.len());
|
assert_eq!(expected_types.len(), module.defs.len());
|
||||||
|
|
||||||
for def in module.defs {
|
for def in module.defs {
|
||||||
for (symbol, var) in def.variables_by_symbol {
|
for (symbol, expr_var) in def.pattern_vars {
|
||||||
let content = subs.get(var).content;
|
let content = subs.get(expr_var).content;
|
||||||
|
|
||||||
name_all_type_vars(var, &mut subs);
|
name_all_type_vars(expr_var, &mut subs);
|
||||||
|
|
||||||
let actual_str = content_to_string(content, &mut subs);
|
let actual_str = content_to_string(content, &mut subs);
|
||||||
let expected_type = expected_types
|
let expected_type = expected_types
|
||||||
|
|
|
@ -669,6 +669,17 @@ mod test_parse {
|
||||||
assert_eq!(Ok(expected), actual);
|
assert_eq!(Ok(expected), actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spaces_inside_empty_list() {
|
||||||
|
// This is a regression test!
|
||||||
|
let arena = Bump::new();
|
||||||
|
let elems = Vec::new_in(&arena);
|
||||||
|
let expected = List(elems);
|
||||||
|
let actual = parse_with(&arena, "[ ]");
|
||||||
|
|
||||||
|
assert_eq!(Ok(expected), actual);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn packed_singleton_list() {
|
fn packed_singleton_list() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
|
@ -1296,10 +1307,10 @@ mod test_parse {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE
|
// WHEN
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_branch_case() {
|
fn two_branch_when() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let pattern1 =
|
let pattern1 =
|
||||||
|
@ -1317,12 +1328,12 @@ mod test_parse {
|
||||||
let branch2 = &*arena.alloc((loc_pattern2, loc_expr2));
|
let branch2 = &*arena.alloc((loc_pattern2, loc_expr2));
|
||||||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||||||
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
||||||
let expected = Expr::Case(arena.alloc(loc_cond), branches);
|
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||||
let actual = parse_with(
|
let actual = parse_with(
|
||||||
&arena,
|
&arena,
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case x when
|
when x is
|
||||||
"blah" -> 1
|
"blah" -> 1
|
||||||
"mise" -> 2
|
"mise" -> 2
|
||||||
"#
|
"#
|
||||||
|
@ -1333,7 +1344,7 @@ mod test_parse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_numbers() {
|
fn when_with_numbers() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let pattern1 =
|
let pattern1 =
|
||||||
|
@ -1351,12 +1362,12 @@ mod test_parse {
|
||||||
let branch2 = &*arena.alloc((loc_pattern2, loc_expr2));
|
let branch2 = &*arena.alloc((loc_pattern2, loc_expr2));
|
||||||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||||||
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
||||||
let expected = Expr::Case(arena.alloc(loc_cond), branches);
|
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||||
let actual = parse_with(
|
let actual = parse_with(
|
||||||
&arena,
|
&arena,
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case x when
|
when x is
|
||||||
1 -> 2
|
1 -> 2
|
||||||
3 -> 4
|
3 -> 4
|
||||||
"#
|
"#
|
||||||
|
@ -1367,7 +1378,7 @@ mod test_parse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_records() {
|
fn when_with_records() {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let newlines = bumpalo::vec![in &arena; Newline];
|
let newlines = bumpalo::vec![in &arena; Newline];
|
||||||
let identifiers1 = bumpalo::vec![in &arena; Located::new(1, 1, 3, 4, Identifier("y")) ];
|
let identifiers1 = bumpalo::vec![in &arena; Located::new(1, 1, 3, 4, Identifier("y")) ];
|
||||||
|
@ -1391,12 +1402,12 @@ mod test_parse {
|
||||||
let branch2 = &*arena.alloc((loc_pattern2, loc_expr2));
|
let branch2 = &*arena.alloc((loc_pattern2, loc_expr2));
|
||||||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||||||
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
||||||
let expected = Expr::Case(arena.alloc(loc_cond), branches);
|
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||||
let actual = parse_with(
|
let actual = parse_with(
|
||||||
&arena,
|
&arena,
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case x when
|
when x is
|
||||||
{ y } -> 2
|
{ y } -> 2
|
||||||
{ z, w } -> 4
|
{ z, w } -> 4
|
||||||
"#
|
"#
|
||||||
|
|
|
@ -13,7 +13,6 @@ mod test_infer_uniq {
|
||||||
use crate::helpers::uniq_expr;
|
use crate::helpers::uniq_expr;
|
||||||
use roc::infer::infer_expr;
|
use roc::infer::infer_expr;
|
||||||
use roc::pretty_print_types::{content_to_string, name_all_type_vars};
|
use roc::pretty_print_types::{content_to_string, name_all_type_vars};
|
||||||
use roc::subs::Subs;
|
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
|
|
||||||
|
@ -22,17 +21,14 @@ mod test_infer_uniq {
|
||||||
_output2,
|
_output2,
|
||||||
_output1,
|
_output1,
|
||||||
_,
|
_,
|
||||||
var_store1,
|
mut subs1,
|
||||||
variable1,
|
variable1,
|
||||||
var_store2,
|
mut subs2,
|
||||||
variable2,
|
variable2,
|
||||||
constraint1,
|
constraint1,
|
||||||
constraint2,
|
constraint2,
|
||||||
) = uniq_expr(src);
|
) = uniq_expr(src);
|
||||||
|
|
||||||
let mut subs1 = Subs::new(var_store1.into());
|
|
||||||
let mut subs2 = Subs::new(var_store2.into());
|
|
||||||
|
|
||||||
let mut unify_problems = Vec::new();
|
let mut unify_problems = Vec::new();
|
||||||
let content1 = infer_expr(&mut subs1, &mut unify_problems, &constraint1, variable1);
|
let content1 = infer_expr(&mut subs1, &mut unify_problems, &constraint1, variable1);
|
||||||
let content2 = infer_expr(&mut subs2, &mut unify_problems, &constraint2, variable2);
|
let content2 = infer_expr(&mut subs2, &mut unify_problems, &constraint2, variable2);
|
||||||
|
@ -513,6 +509,39 @@ mod test_infer_uniq {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn identity_infers_principal_type() {
|
||||||
|
// infer_eq(
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// identity = \a -> a
|
||||||
|
|
||||||
|
// x = identity 5
|
||||||
|
|
||||||
|
// identity
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// "Attr.Attr * (a -> a)",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn identity_works_on_incompatible_types() {
|
||||||
|
// infer_eq(
|
||||||
|
// indoc!(
|
||||||
|
// r#"
|
||||||
|
// identity = \a -> a
|
||||||
|
|
||||||
|
// x = identity 5
|
||||||
|
// y = identity "hi"
|
||||||
|
|
||||||
|
// x
|
||||||
|
// "#
|
||||||
|
// ),
|
||||||
|
// "Attr.Attr Int",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn call_returns_list() {
|
fn call_returns_list() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
|
@ -826,11 +855,11 @@ mod test_infer_uniq {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_int_literals() {
|
fn when_with_int_literals() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case 1 when
|
when 1 is
|
||||||
1 -> 2
|
1 -> 2
|
||||||
3 -> 4
|
3 -> 4
|
||||||
"#
|
"#
|
||||||
|
@ -838,6 +867,49 @@ mod test_infer_uniq {
|
||||||
"Attr.Attr * Int",
|
"Attr.Attr * Int",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
*/
|
#[test]
|
||||||
|
fn accessor_function() {
|
||||||
|
infer_eq(".foo", "Attr.Attr * { foo : a }* -> a");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record() {
|
||||||
|
infer_eq("{ foo: 42 }", "Attr.Attr * { foo : (Attr.Attr * Int) }");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_access() {
|
||||||
|
infer_eq("{ foo: 42 }.foo", "Attr.Attr * Int");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_pattern_match_infer() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when foo is
|
||||||
|
{ x: 4 }-> x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Int",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_record_pattern() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
# technically, an empty record can be destructured
|
||||||
|
{} = {}
|
||||||
|
bar = \{} -> 42
|
||||||
|
|
||||||
|
when foo is
|
||||||
|
{ x: {} } -> x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr.Attr * {}*",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue