mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Merge remote-tracking branch 'origin/trunk' into array-wrappers
This commit is contained in:
commit
a54db8bf92
22 changed files with 1095 additions and 361 deletions
|
@ -394,16 +394,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
|
|
||||||
// Bool module
|
// Bool module
|
||||||
|
|
||||||
// isEq or (==) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool
|
// isEq or (==) : a, a -> Attr u Bool
|
||||||
add_type(
|
add_type(
|
||||||
Symbol::BOOL_EQ,
|
Symbol::BOOL_EQ,
|
||||||
unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)),
|
unique_function(vec![flex(TVAR1), flex(TVAR1)], bool_type(UVAR3)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// isNeq or (!=) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool
|
// isNeq or (!=) : a, a -> Attr u Bool
|
||||||
add_type(
|
add_type(
|
||||||
Symbol::BOOL_NEQ,
|
Symbol::BOOL_NEQ,
|
||||||
unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)),
|
unique_function(vec![flex(TVAR1), flex(TVAR1)], bool_type(UVAR3)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// and or (&&) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool
|
// and or (&&) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool
|
||||||
|
|
|
@ -662,13 +662,13 @@ fn pattern_to_vars_by_symbol(
|
||||||
vars_by_symbol.insert(symbol.clone(), expr_var);
|
vars_by_symbol.insert(symbol.clone(), expr_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppliedTag(_, _, arguments) => {
|
AppliedTag { arguments, .. } => {
|
||||||
for (var, nested) in arguments {
|
for (var, nested) in arguments {
|
||||||
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
|
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordDestructure(_, destructs) => {
|
RecordDestructure { destructs, .. } => {
|
||||||
for destruct in destructs {
|
for destruct in destructs {
|
||||||
vars_by_symbol.insert(destruct.value.symbol.clone(), destruct.value.var);
|
vars_by_symbol.insert(destruct.value.symbol.clone(), destruct.value.var);
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,13 +85,17 @@ pub enum Expr {
|
||||||
),
|
),
|
||||||
|
|
||||||
// Product Types
|
// Product Types
|
||||||
Record(Variable, SendMap<Lowercase, Field>),
|
Record {
|
||||||
|
record_var: Variable,
|
||||||
|
fields: SendMap<Lowercase, Field>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Empty record constant
|
/// Empty record constant
|
||||||
EmptyRecord,
|
EmptyRecord,
|
||||||
|
|
||||||
/// Look up exactly one field on a record, e.g. (expr).foo.
|
/// Look up exactly one field on a record, e.g. (expr).foo.
|
||||||
Access {
|
Access {
|
||||||
|
record_var: Variable,
|
||||||
ext_var: Variable,
|
ext_var: Variable,
|
||||||
field_var: Variable,
|
field_var: Variable,
|
||||||
loc_expr: Box<Located<Expr>>,
|
loc_expr: Box<Located<Expr>>,
|
||||||
|
@ -99,6 +103,7 @@ pub enum Expr {
|
||||||
},
|
},
|
||||||
/// field accessor as a function, e.g. (.foo) expr
|
/// field accessor as a function, e.g. (.foo) expr
|
||||||
Accessor {
|
Accessor {
|
||||||
|
record_var: Variable,
|
||||||
ext_var: Variable,
|
ext_var: Variable,
|
||||||
field_var: Variable,
|
field_var: Variable,
|
||||||
field: Lowercase,
|
field: Lowercase,
|
||||||
|
@ -195,7 +200,13 @@ pub fn canonicalize_expr<'a>(
|
||||||
} else {
|
} else {
|
||||||
let (can_fields, output) = canonicalize_fields(env, var_store, scope, fields);
|
let (can_fields, output) = canonicalize_fields(env, var_store, scope, fields);
|
||||||
|
|
||||||
(Record(var_store.fresh(), can_fields), output)
|
(
|
||||||
|
Record {
|
||||||
|
record_var: var_store.fresh(),
|
||||||
|
fields: can_fields,
|
||||||
|
},
|
||||||
|
output,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::Expr::Str(string) => (Str((*string).into()), Output::default()),
|
ast::Expr::Str(string) => (Str((*string).into()), Output::default()),
|
||||||
|
@ -479,6 +490,7 @@ pub fn canonicalize_expr<'a>(
|
||||||
|
|
||||||
(
|
(
|
||||||
Access {
|
Access {
|
||||||
|
record_var: var_store.fresh(),
|
||||||
field_var: var_store.fresh(),
|
field_var: var_store.fresh(),
|
||||||
ext_var: var_store.fresh(),
|
ext_var: var_store.fresh(),
|
||||||
loc_expr: Box::new(loc_expr),
|
loc_expr: Box::new(loc_expr),
|
||||||
|
@ -487,20 +499,15 @@ pub fn canonicalize_expr<'a>(
|
||||||
output,
|
output,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::AccessorFunction(field) => {
|
ast::Expr::AccessorFunction(field) => (
|
||||||
let ext_var = var_store.fresh();
|
Accessor {
|
||||||
let field_var = var_store.fresh();
|
record_var: var_store.fresh(),
|
||||||
let field_name: Lowercase = (*field).into();
|
ext_var: var_store.fresh(),
|
||||||
|
field_var: var_store.fresh(),
|
||||||
(
|
field: (*field).into(),
|
||||||
Accessor {
|
},
|
||||||
field: field_name,
|
Output::default(),
|
||||||
ext_var,
|
),
|
||||||
field_var,
|
|
||||||
},
|
|
||||||
Output::default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ast::Expr::GlobalTag(tag) => {
|
ast::Expr::GlobalTag(tag) => {
|
||||||
let variant_var = var_store.fresh();
|
let variant_var = var_store.fresh();
|
||||||
let ext_var = var_store.fresh();
|
let ext_var = var_store.fresh();
|
||||||
|
|
|
@ -14,12 +14,21 @@ use roc_types::subs::{VarStore, Variable};
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Pattern {
|
pub enum Pattern {
|
||||||
Identifier(Symbol),
|
Identifier(Symbol),
|
||||||
AppliedTag(Variable, TagName, Vec<(Variable, Located<Pattern>)>),
|
AppliedTag {
|
||||||
|
whole_var: Variable,
|
||||||
|
ext_var: Variable,
|
||||||
|
tag_name: TagName,
|
||||||
|
arguments: Vec<(Variable, Located<Pattern>)>,
|
||||||
|
},
|
||||||
|
RecordDestructure {
|
||||||
|
whole_var: Variable,
|
||||||
|
ext_var: Variable,
|
||||||
|
destructs: Vec<Located<RecordDestruct>>,
|
||||||
|
},
|
||||||
IntLiteral(i64),
|
IntLiteral(i64),
|
||||||
NumLiteral(Variable, i64),
|
NumLiteral(Variable, i64),
|
||||||
FloatLiteral(f64),
|
FloatLiteral(f64),
|
||||||
StrLiteral(Box<str>),
|
StrLiteral(Box<str>),
|
||||||
RecordDestructure(Variable, Vec<Located<RecordDestruct>>),
|
|
||||||
Underscore,
|
Underscore,
|
||||||
|
|
||||||
// Runtime Exceptions
|
// Runtime Exceptions
|
||||||
|
@ -51,12 +60,12 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||||
symbols.push(symbol.clone());
|
symbols.push(symbol.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
AppliedTag(_, _, arguments) => {
|
AppliedTag { arguments, .. } => {
|
||||||
for (_, nested) in arguments {
|
for (_, nested) in arguments {
|
||||||
symbols_from_pattern_help(&nested.value, symbols);
|
symbols_from_pattern_help(&nested.value, symbols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RecordDestructure(_, destructs) => {
|
RecordDestructure { destructs, .. } => {
|
||||||
for destruct in destructs {
|
for destruct in destructs {
|
||||||
symbols.push(destruct.value.symbol.clone());
|
symbols.push(destruct.value.symbol.clone());
|
||||||
}
|
}
|
||||||
|
@ -103,17 +112,23 @@ pub fn canonicalize_pattern<'a>(
|
||||||
},
|
},
|
||||||
GlobalTag(name) => {
|
GlobalTag(name) => {
|
||||||
// Canonicalize the tag's name.
|
// Canonicalize the tag's name.
|
||||||
Pattern::AppliedTag(var_store.fresh(), TagName::Global((*name).into()), vec![])
|
Pattern::AppliedTag {
|
||||||
|
whole_var: var_store.fresh(),
|
||||||
|
ext_var: var_store.fresh(),
|
||||||
|
tag_name: TagName::Global((*name).into()),
|
||||||
|
arguments: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PrivateTag(name) => {
|
PrivateTag(name) => {
|
||||||
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
|
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
|
||||||
|
|
||||||
// Canonicalize the tag's name.
|
// Canonicalize the tag's name.
|
||||||
Pattern::AppliedTag(
|
Pattern::AppliedTag {
|
||||||
var_store.fresh(),
|
whole_var: var_store.fresh(),
|
||||||
TagName::Private(Symbol::new(env.home, ident_id)),
|
ext_var: var_store.fresh(),
|
||||||
vec![],
|
tag_name: TagName::Private(Symbol::new(env.home, ident_id)),
|
||||||
)
|
arguments: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Apply(tag, patterns) => {
|
Apply(tag, patterns) => {
|
||||||
let tag_name = match tag.value {
|
let tag_name = match tag.value {
|
||||||
|
@ -141,7 +156,12 @@ pub fn canonicalize_pattern<'a>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern::AppliedTag(var_store.fresh(), tag_name, can_patterns)
|
Pattern::AppliedTag {
|
||||||
|
whole_var: var_store.fresh(),
|
||||||
|
ext_var: var_store.fresh(),
|
||||||
|
tag_name,
|
||||||
|
arguments: can_patterns,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatLiteral(ref string) => match pattern_type {
|
FloatLiteral(ref string) => match pattern_type {
|
||||||
|
@ -208,7 +228,8 @@ pub fn canonicalize_pattern<'a>(
|
||||||
}
|
}
|
||||||
RecordDestructure(patterns) => {
|
RecordDestructure(patterns) => {
|
||||||
let ext_var = var_store.fresh();
|
let ext_var = var_store.fresh();
|
||||||
let mut fields = Vec::with_capacity(patterns.len());
|
let whole_var = var_store.fresh();
|
||||||
|
let mut destructs = Vec::with_capacity(patterns.len());
|
||||||
let mut opt_erroneous = None;
|
let mut opt_erroneous = None;
|
||||||
|
|
||||||
for loc_pattern in *patterns {
|
for loc_pattern in *patterns {
|
||||||
|
@ -221,7 +242,7 @@ pub fn canonicalize_pattern<'a>(
|
||||||
region,
|
region,
|
||||||
) {
|
) {
|
||||||
Ok(symbol) => {
|
Ok(symbol) => {
|
||||||
fields.push(Located {
|
destructs.push(Located {
|
||||||
region: loc_pattern.region,
|
region: loc_pattern.region,
|
||||||
value: RecordDestruct {
|
value: RecordDestruct {
|
||||||
var: var_store.fresh(),
|
var: var_store.fresh(),
|
||||||
|
@ -262,7 +283,7 @@ pub fn canonicalize_pattern<'a>(
|
||||||
loc_guard.region,
|
loc_guard.region,
|
||||||
);
|
);
|
||||||
|
|
||||||
fields.push(Located {
|
destructs.push(Located {
|
||||||
region: loc_pattern.region,
|
region: loc_pattern.region,
|
||||||
value: RecordDestruct {
|
value: RecordDestruct {
|
||||||
var: var_store.fresh(),
|
var: var_store.fresh(),
|
||||||
|
@ -292,7 +313,11 @@ pub fn canonicalize_pattern<'a>(
|
||||||
|
|
||||||
// If we encountered an erroneous pattern (e.g. one with shadowing),
|
// If we encountered an erroneous pattern (e.g. one with shadowing),
|
||||||
// use the resulting RuntimeError. Otherwise, return a successful record destructure.
|
// use the resulting RuntimeError. Otherwise, return a successful record destructure.
|
||||||
opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure(ext_var, fields))
|
opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure {
|
||||||
|
whole_var,
|
||||||
|
ext_var,
|
||||||
|
destructs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
RecordField(_name, _loc_pattern) => {
|
RecordField(_name, _loc_pattern) => {
|
||||||
unreachable!("should have been handled in RecordDestructure");
|
unreachable!("should have been handled in RecordDestructure");
|
||||||
|
@ -345,12 +370,15 @@ fn add_bindings_from_patterns(
|
||||||
Identifier(symbol) => {
|
Identifier(symbol) => {
|
||||||
answer.push((*symbol, *region));
|
answer.push((*symbol, *region));
|
||||||
}
|
}
|
||||||
AppliedTag(_, _, loc_args) => {
|
AppliedTag {
|
||||||
|
arguments: loc_args,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
for (_, loc_arg) in loc_args {
|
for (_, loc_arg) in loc_args {
|
||||||
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, scope, answer);
|
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, scope, answer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RecordDestructure(_, destructs) => {
|
RecordDestructure { destructs, .. } => {
|
||||||
for Located {
|
for Located {
|
||||||
region,
|
region,
|
||||||
value: RecordDestruct { symbol, .. },
|
value: RecordDestruct { symbol, .. },
|
||||||
|
|
|
@ -90,7 +90,7 @@ pub fn constrain_expr(
|
||||||
),
|
),
|
||||||
Float(var, _) => float_literal(*var, expected, region),
|
Float(var, _) => float_literal(*var, expected, region),
|
||||||
EmptyRecord => constrain_empty_record(region, expected),
|
EmptyRecord => constrain_empty_record(region, expected),
|
||||||
Expr::Record(stored_var, fields) => {
|
Expr::Record { record_var, fields } => {
|
||||||
if fields.is_empty() {
|
if fields.is_empty() {
|
||||||
constrain_empty_record(region, expected)
|
constrain_empty_record(region, expected)
|
||||||
} else {
|
} else {
|
||||||
|
@ -125,9 +125,9 @@ pub fn constrain_expr(
|
||||||
constraints.push(record_con);
|
constraints.push(record_con);
|
||||||
|
|
||||||
// variable to store in the AST
|
// variable to store in the AST
|
||||||
let stored_con = Eq(Type::Variable(*stored_var), expected, region);
|
let stored_con = Eq(Type::Variable(*record_var), expected, region);
|
||||||
|
|
||||||
field_vars.push(*stored_var);
|
field_vars.push(*record_var);
|
||||||
constraints.push(stored_con);
|
constraints.push(stored_con);
|
||||||
|
|
||||||
exists(field_vars, And(constraints))
|
exists(field_vars, And(constraints))
|
||||||
|
@ -328,16 +328,27 @@ pub fn constrain_expr(
|
||||||
} => {
|
} => {
|
||||||
let bool_type = Type::Variable(Variable::BOOL);
|
let bool_type = Type::Variable(Variable::BOOL);
|
||||||
let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region);
|
let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region);
|
||||||
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2);
|
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3);
|
||||||
|
|
||||||
|
// TODO why does this cond var exist? is it for error messages?
|
||||||
|
let cond_var_is_bool_con = Eq(
|
||||||
|
Type::Variable(*cond_var),
|
||||||
|
expect_bool.clone(),
|
||||||
|
Region::zero(),
|
||||||
|
);
|
||||||
|
|
||||||
|
branch_cons.push(cond_var_is_bool_con);
|
||||||
|
|
||||||
match expected {
|
match expected {
|
||||||
FromAnnotation(name, arity, _, tipe) => {
|
FromAnnotation(name, arity, _, tipe) => {
|
||||||
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
||||||
let cond_con = Eq(
|
let cond_con = constrain_expr(
|
||||||
Type::Variable(*cond_var),
|
env,
|
||||||
expect_bool.clone(),
|
|
||||||
loc_cond.region,
|
loc_cond.region,
|
||||||
|
&loc_cond.value,
|
||||||
|
expect_bool.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let then_con = constrain_expr(
|
let then_con = constrain_expr(
|
||||||
env,
|
env,
|
||||||
loc_body.region,
|
loc_body.region,
|
||||||
|
@ -374,11 +385,13 @@ pub fn constrain_expr(
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
||||||
let cond_con = Eq(
|
let cond_con = constrain_expr(
|
||||||
Type::Variable(*cond_var),
|
env,
|
||||||
expect_bool.clone(),
|
|
||||||
loc_cond.region,
|
loc_cond.region,
|
||||||
|
&loc_cond.value,
|
||||||
|
expect_bool.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let then_con = constrain_expr(
|
let then_con = constrain_expr(
|
||||||
env,
|
env,
|
||||||
loc_body.region,
|
loc_body.region,
|
||||||
|
@ -502,6 +515,7 @@ pub fn constrain_expr(
|
||||||
exists(vec![cond_var, *expr_var], And(constraints))
|
exists(vec![cond_var, *expr_var], And(constraints))
|
||||||
}
|
}
|
||||||
Access {
|
Access {
|
||||||
|
record_var,
|
||||||
ext_var,
|
ext_var,
|
||||||
field_var,
|
field_var,
|
||||||
loc_expr,
|
loc_expr,
|
||||||
|
@ -520,6 +534,8 @@ pub fn constrain_expr(
|
||||||
let record_type = Type::Record(rec_field_types, Box::new(ext_type));
|
let record_type = Type::Record(rec_field_types, Box::new(ext_type));
|
||||||
let record_expected = Expected::NoExpectation(record_type);
|
let record_expected = Expected::NoExpectation(record_type);
|
||||||
|
|
||||||
|
let record_con = Eq(Type::Variable(*record_var), record_expected.clone(), region);
|
||||||
|
|
||||||
let constraint = constrain_expr(
|
let constraint = constrain_expr(
|
||||||
&Env {
|
&Env {
|
||||||
home: env.home,
|
home: env.home,
|
||||||
|
@ -531,12 +547,17 @@ pub fn constrain_expr(
|
||||||
);
|
);
|
||||||
|
|
||||||
exists(
|
exists(
|
||||||
vec![field_var, ext_var],
|
vec![*record_var, field_var, ext_var],
|
||||||
And(vec![constraint, Eq(field_type, expected, region)]),
|
And(vec![
|
||||||
|
constraint,
|
||||||
|
Eq(field_type, expected, region),
|
||||||
|
record_con,
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Accessor {
|
Accessor {
|
||||||
field,
|
field,
|
||||||
|
record_var,
|
||||||
ext_var,
|
ext_var,
|
||||||
field_var,
|
field_var,
|
||||||
} => {
|
} => {
|
||||||
|
@ -550,13 +571,19 @@ pub fn constrain_expr(
|
||||||
field_types.insert(label, field_type.clone());
|
field_types.insert(label, field_type.clone());
|
||||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
let record_type = Type::Record(field_types, Box::new(ext_type));
|
||||||
|
|
||||||
|
let record_expected = Expected::NoExpectation(record_type.clone());
|
||||||
|
let record_con = Eq(Type::Variable(*record_var), record_expected, region);
|
||||||
|
|
||||||
exists(
|
exists(
|
||||||
vec![field_var, ext_var],
|
vec![*record_var, field_var, ext_var],
|
||||||
Eq(
|
And(vec![
|
||||||
Type::Function(vec![record_type], Box::new(field_type)),
|
Eq(
|
||||||
expected,
|
Type::Function(vec![record_type], Box::new(field_type)),
|
||||||
region,
|
expected,
|
||||||
),
|
region,
|
||||||
|
),
|
||||||
|
record_con,
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LetRec(defs, loc_ret, var, aliases) => {
|
LetRec(defs, loc_ret, var, aliases) => {
|
||||||
|
|
|
@ -58,7 +58,7 @@ fn headers_from_annotation_help(
|
||||||
| FloatLiteral(_)
|
| FloatLiteral(_)
|
||||||
| StrLiteral(_) => true,
|
| StrLiteral(_) => true,
|
||||||
|
|
||||||
RecordDestructure(_, destructs) => match annotation.value.shallow_dealias() {
|
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
|
||||||
Type::Record(fields, _) => {
|
Type::Record(fields, _) => {
|
||||||
for destruct in destructs {
|
for destruct in destructs {
|
||||||
// NOTE ignores the .guard field.
|
// NOTE ignores the .guard field.
|
||||||
|
@ -77,7 +77,11 @@ fn headers_from_annotation_help(
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
AppliedTag(_, tag_name, arguments) => match annotation.value.shallow_dealias() {
|
AppliedTag {
|
||||||
|
tag_name,
|
||||||
|
arguments,
|
||||||
|
..
|
||||||
|
} => match annotation.value.shallow_dealias() {
|
||||||
Type::TagUnion(tags, _) => {
|
Type::TagUnion(tags, _) => {
|
||||||
if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) {
|
if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) {
|
||||||
if !arguments.len() == arg_types.len() {
|
if !arguments.len() == arg_types.len() {
|
||||||
|
@ -164,7 +168,12 @@ pub fn constrain_pattern(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordDestructure(ext_var, patterns) => {
|
RecordDestructure {
|
||||||
|
whole_var,
|
||||||
|
ext_var,
|
||||||
|
destructs,
|
||||||
|
} => {
|
||||||
|
state.vars.push(*whole_var);
|
||||||
state.vars.push(*ext_var);
|
state.vars.push(*ext_var);
|
||||||
let ext_type = Type::Variable(*ext_var);
|
let ext_type = Type::Variable(*ext_var);
|
||||||
|
|
||||||
|
@ -179,7 +188,7 @@ pub fn constrain_pattern(
|
||||||
guard,
|
guard,
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} in patterns
|
} in destructs
|
||||||
{
|
{
|
||||||
let pat_type = Type::Variable(*var);
|
let pat_type = Type::Variable(*var);
|
||||||
let expected = PExpected::NoExpectation(pat_type.clone());
|
let expected = PExpected::NoExpectation(pat_type.clone());
|
||||||
|
@ -207,14 +216,31 @@ pub fn constrain_pattern(
|
||||||
}
|
}
|
||||||
|
|
||||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
let record_type = Type::Record(field_types, Box::new(ext_type));
|
||||||
let record_con =
|
|
||||||
Constraint::Pattern(region, PatternCategory::Record, record_type, expected);
|
|
||||||
|
|
||||||
|
let whole_con = Constraint::Eq(
|
||||||
|
Type::Variable(*whole_var),
|
||||||
|
Expected::NoExpectation(record_type),
|
||||||
|
region,
|
||||||
|
);
|
||||||
|
|
||||||
|
let record_con = Constraint::Pattern(
|
||||||
|
region,
|
||||||
|
PatternCategory::Record,
|
||||||
|
Type::Variable(*whole_var),
|
||||||
|
expected,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.constraints.push(whole_con);
|
||||||
state.constraints.push(record_con);
|
state.constraints.push(record_con);
|
||||||
}
|
}
|
||||||
AppliedTag(ext_var, tag_name, patterns) => {
|
AppliedTag {
|
||||||
let mut argument_types = Vec::with_capacity(patterns.len());
|
whole_var,
|
||||||
for (pattern_var, loc_pattern) in patterns {
|
ext_var,
|
||||||
|
tag_name,
|
||||||
|
arguments,
|
||||||
|
} => {
|
||||||
|
let mut argument_types = Vec::with_capacity(arguments.len());
|
||||||
|
for (pattern_var, loc_pattern) in arguments {
|
||||||
state.vars.push(*pattern_var);
|
state.vars.push(*pattern_var);
|
||||||
|
|
||||||
let pattern_type = Type::Variable(*pattern_var);
|
let pattern_type = Type::Variable(*pattern_var);
|
||||||
|
@ -224,17 +250,25 @@ pub fn constrain_pattern(
|
||||||
constrain_pattern(&loc_pattern.value, loc_pattern.region, expected, state);
|
constrain_pattern(&loc_pattern.value, loc_pattern.region, expected, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let whole_con = Constraint::Eq(
|
||||||
|
Type::Variable(*whole_var),
|
||||||
|
Expected::NoExpectation(Type::TagUnion(
|
||||||
|
vec![(tag_name.clone(), argument_types)],
|
||||||
|
Box::new(Type::Variable(*ext_var)),
|
||||||
|
)),
|
||||||
|
region,
|
||||||
|
);
|
||||||
|
|
||||||
let tag_con = Constraint::Pattern(
|
let tag_con = Constraint::Pattern(
|
||||||
region,
|
region,
|
||||||
PatternCategory::Ctor(tag_name.clone()),
|
PatternCategory::Ctor(tag_name.clone()),
|
||||||
Type::TagUnion(
|
Type::Variable(*whole_var),
|
||||||
vec![(tag_name.clone(), argument_types)],
|
|
||||||
Box::new(Type::Variable(*ext_var)),
|
|
||||||
),
|
|
||||||
expected,
|
expected,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
state.vars.push(*whole_var);
|
||||||
state.vars.push(*ext_var);
|
state.vars.push(*ext_var);
|
||||||
|
state.constraints.push(whole_con);
|
||||||
state.constraints.push(tag_con);
|
state.constraints.push(tag_con);
|
||||||
}
|
}
|
||||||
Shadowed(_, _) => {
|
Shadowed(_, _) => {
|
||||||
|
|
|
@ -143,6 +143,8 @@ fn constrain_pattern(
|
||||||
use roc_can::pattern::Pattern::*;
|
use roc_can::pattern::Pattern::*;
|
||||||
use roc_types::types::PatternCategory;
|
use roc_types::types::PatternCategory;
|
||||||
|
|
||||||
|
let region = pattern.region;
|
||||||
|
|
||||||
match &pattern.value {
|
match &pattern.value {
|
||||||
Identifier(symbol) => {
|
Identifier(symbol) => {
|
||||||
state.headers.insert(
|
state.headers.insert(
|
||||||
|
@ -190,10 +192,15 @@ fn constrain_pattern(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordDestructure(ext_var, patterns) => {
|
RecordDestructure {
|
||||||
|
whole_var,
|
||||||
|
ext_var,
|
||||||
|
destructs,
|
||||||
|
} => {
|
||||||
// TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness
|
// TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness
|
||||||
let mut pattern_uniq_vars = Vec::with_capacity(patterns.len());
|
let mut pattern_uniq_vars = Vec::with_capacity(destructs.len());
|
||||||
|
|
||||||
|
state.vars.push(*whole_var);
|
||||||
state.vars.push(*ext_var);
|
state.vars.push(*ext_var);
|
||||||
let ext_type = Type::Variable(*ext_var);
|
let ext_type = Type::Variable(*ext_var);
|
||||||
|
|
||||||
|
@ -207,7 +214,7 @@ fn constrain_pattern(
|
||||||
guard,
|
guard,
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} in patterns
|
} in destructs
|
||||||
{
|
{
|
||||||
let pat_uniq_var = var_store.fresh();
|
let pat_uniq_var = var_store.fresh();
|
||||||
pattern_uniq_vars.push(pat_uniq_var);
|
pattern_uniq_vars.push(pat_uniq_var);
|
||||||
|
@ -251,22 +258,35 @@ fn constrain_pattern(
|
||||||
record_uniq_type,
|
record_uniq_type,
|
||||||
Type::Record(field_types, Box::new(ext_type)),
|
Type::Record(field_types, Box::new(ext_type)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let whole_con = Constraint::Eq(
|
||||||
|
Type::Variable(*whole_var),
|
||||||
|
Expected::NoExpectation(record_type),
|
||||||
|
region,
|
||||||
|
);
|
||||||
|
|
||||||
let record_con = Constraint::Pattern(
|
let record_con = Constraint::Pattern(
|
||||||
pattern.region,
|
region,
|
||||||
PatternCategory::Record,
|
PatternCategory::Record,
|
||||||
record_type,
|
Type::Variable(*whole_var),
|
||||||
expected,
|
expected,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
state.constraints.push(whole_con);
|
||||||
state.constraints.push(record_con);
|
state.constraints.push(record_con);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppliedTag(ext_var, symbol, patterns) => {
|
AppliedTag {
|
||||||
|
whole_var,
|
||||||
|
ext_var,
|
||||||
|
tag_name,
|
||||||
|
arguments,
|
||||||
|
} => {
|
||||||
// TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness
|
// TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness
|
||||||
let mut argument_types = Vec::with_capacity(patterns.len());
|
let mut argument_types = Vec::with_capacity(arguments.len());
|
||||||
let mut pattern_uniq_vars = Vec::with_capacity(patterns.len());
|
let mut pattern_uniq_vars = Vec::with_capacity(arguments.len());
|
||||||
|
|
||||||
for (pattern_var, loc_pattern) in patterns {
|
for (pattern_var, loc_pattern) in arguments {
|
||||||
state.vars.push(*pattern_var);
|
state.vars.push(*pattern_var);
|
||||||
|
|
||||||
let pat_uniq_var = var_store.fresh();
|
let pat_uniq_var = var_store.fresh();
|
||||||
|
@ -292,19 +312,28 @@ fn constrain_pattern(
|
||||||
let union_type = attr_type(
|
let union_type = attr_type(
|
||||||
tag_union_uniq_type,
|
tag_union_uniq_type,
|
||||||
Type::TagUnion(
|
Type::TagUnion(
|
||||||
vec![(symbol.clone(), argument_types)],
|
vec![(tag_name.clone(), argument_types)],
|
||||||
Box::new(Type::Variable(*ext_var)),
|
Box::new(Type::Variable(*ext_var)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let whole_con = Constraint::Eq(
|
||||||
|
Type::Variable(*whole_var),
|
||||||
|
Expected::NoExpectation(union_type),
|
||||||
|
region,
|
||||||
|
);
|
||||||
|
|
||||||
let tag_con = Constraint::Pattern(
|
let tag_con = Constraint::Pattern(
|
||||||
pattern.region,
|
region,
|
||||||
PatternCategory::Ctor(symbol.clone()),
|
PatternCategory::Ctor(tag_name.clone()),
|
||||||
union_type,
|
Type::Variable(*whole_var),
|
||||||
expected,
|
expected,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
state.vars.push(*whole_var);
|
||||||
state.vars.push(*ext_var);
|
state.vars.push(*ext_var);
|
||||||
|
|
||||||
|
state.constraints.push(whole_con);
|
||||||
state.constraints.push(tag_con);
|
state.constraints.push(tag_con);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,12 +457,12 @@ pub fn constrain_expr(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Record(variable, fields) => {
|
Record { record_var, fields } => {
|
||||||
// NOTE: canonicalization guarantees at least one field
|
// NOTE: canonicalization guarantees at least one field
|
||||||
// zero fields generates an EmptyRecord
|
// zero fields generates an EmptyRecord
|
||||||
let mut field_types = SendMap::default();
|
let mut field_types = SendMap::default();
|
||||||
let mut field_vars = Vec::with_capacity(fields.len());
|
let mut field_vars = Vec::with_capacity(fields.len());
|
||||||
field_vars.push(*variable);
|
field_vars.push(*record_var);
|
||||||
|
|
||||||
// Constraints need capacity for each field + 1 for the record itself + 1 for ext
|
// Constraints need capacity for each field + 1 for the record itself + 1 for ext
|
||||||
let mut constraints = Vec::with_capacity(2 + fields.len());
|
let mut constraints = Vec::with_capacity(2 + fields.len());
|
||||||
|
@ -472,7 +501,7 @@ pub fn constrain_expr(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
let record_con = Eq(record_type, expected.clone(), region);
|
let record_con = Eq(record_type, expected.clone(), region);
|
||||||
let ext_con = Eq(Type::Variable(*variable), expected, region);
|
let ext_con = Eq(Type::Variable(*record_var), expected, region);
|
||||||
|
|
||||||
constraints.push(record_con);
|
constraints.push(record_con);
|
||||||
constraints.push(ext_con);
|
constraints.push(ext_con);
|
||||||
|
@ -806,6 +835,21 @@ pub fn constrain_expr(
|
||||||
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2);
|
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2);
|
||||||
let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2);
|
let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2);
|
||||||
|
|
||||||
|
// TODO why does this cond var exist? is it for error messages?
|
||||||
|
let cond_uniq_var = var_store.fresh();
|
||||||
|
cond_uniq_vars.push(cond_uniq_var);
|
||||||
|
let cond_var_is_bool_con = Eq(
|
||||||
|
Type::Variable(*cond_var),
|
||||||
|
Expected::ForReason(
|
||||||
|
Reason::IfCondition,
|
||||||
|
attr_type(Bool::variable(cond_uniq_var), bool_type.clone()),
|
||||||
|
region,
|
||||||
|
),
|
||||||
|
Region::zero(),
|
||||||
|
);
|
||||||
|
|
||||||
|
branch_cons.push(cond_var_is_bool_con);
|
||||||
|
|
||||||
match expected {
|
match expected {
|
||||||
Expected::FromAnnotation(name, arity, _, tipe) => {
|
Expected::FromAnnotation(name, arity, _, tipe) => {
|
||||||
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
||||||
|
@ -817,11 +861,16 @@ pub fn constrain_expr(
|
||||||
);
|
);
|
||||||
cond_uniq_vars.push(cond_uniq_var);
|
cond_uniq_vars.push(cond_uniq_var);
|
||||||
|
|
||||||
let cond_con = Eq(
|
let cond_con = constrain_expr(
|
||||||
Type::Variable(*cond_var),
|
env,
|
||||||
expect_bool.clone(),
|
var_store,
|
||||||
|
var_usage,
|
||||||
|
applied_usage_constraint,
|
||||||
loc_cond.region,
|
loc_cond.region,
|
||||||
|
&loc_cond.value,
|
||||||
|
expect_bool,
|
||||||
);
|
);
|
||||||
|
|
||||||
let then_con = constrain_expr(
|
let then_con = constrain_expr(
|
||||||
env,
|
env,
|
||||||
var_store,
|
var_store,
|
||||||
|
@ -879,11 +928,16 @@ pub fn constrain_expr(
|
||||||
);
|
);
|
||||||
cond_uniq_vars.push(cond_uniq_var);
|
cond_uniq_vars.push(cond_uniq_var);
|
||||||
|
|
||||||
let cond_con = Eq(
|
let cond_con = constrain_expr(
|
||||||
Type::Variable(*cond_var),
|
env,
|
||||||
expect_bool.clone(),
|
var_store,
|
||||||
|
var_usage,
|
||||||
|
applied_usage_constraint,
|
||||||
loc_cond.region,
|
loc_cond.region,
|
||||||
|
&loc_cond.value,
|
||||||
|
expect_bool,
|
||||||
);
|
);
|
||||||
|
|
||||||
let then_con = constrain_expr(
|
let then_con = constrain_expr(
|
||||||
env,
|
env,
|
||||||
var_store,
|
var_store,
|
||||||
|
@ -1087,6 +1141,7 @@ pub fn constrain_expr(
|
||||||
}
|
}
|
||||||
|
|
||||||
Access {
|
Access {
|
||||||
|
record_var,
|
||||||
ext_var,
|
ext_var,
|
||||||
field_var,
|
field_var,
|
||||||
loc_expr,
|
loc_expr,
|
||||||
|
@ -1109,6 +1164,8 @@ pub fn constrain_expr(
|
||||||
);
|
);
|
||||||
|
|
||||||
let record_expected = Expected::NoExpectation(record_type);
|
let record_expected = Expected::NoExpectation(record_type);
|
||||||
|
let record_con = Eq(Type::Variable(*record_var), record_expected.clone(), region);
|
||||||
|
|
||||||
let inner_constraint = constrain_expr(
|
let inner_constraint = constrain_expr(
|
||||||
env,
|
env,
|
||||||
var_store,
|
var_store,
|
||||||
|
@ -1120,13 +1177,24 @@ pub fn constrain_expr(
|
||||||
);
|
);
|
||||||
|
|
||||||
exists(
|
exists(
|
||||||
vec![*field_var, *ext_var, field_uniq_var, record_uniq_var],
|
vec![
|
||||||
And(vec![Eq(field_type, expected, region), inner_constraint]),
|
*record_var,
|
||||||
|
*field_var,
|
||||||
|
*ext_var,
|
||||||
|
field_uniq_var,
|
||||||
|
record_uniq_var,
|
||||||
|
],
|
||||||
|
And(vec![
|
||||||
|
Eq(field_type, expected, region),
|
||||||
|
inner_constraint,
|
||||||
|
record_con,
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Accessor {
|
Accessor {
|
||||||
field,
|
field,
|
||||||
|
record_var,
|
||||||
field_var,
|
field_var,
|
||||||
ext_var,
|
ext_var,
|
||||||
} => {
|
} => {
|
||||||
|
@ -1146,6 +1214,9 @@ pub fn constrain_expr(
|
||||||
Type::Record(field_types, Box::new(Type::Variable(*ext_var))),
|
Type::Record(field_types, Box::new(Type::Variable(*ext_var))),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let record_expected = Expected::NoExpectation(record_type.clone());
|
||||||
|
let record_con = Eq(Type::Variable(*record_var), record_expected, region);
|
||||||
|
|
||||||
let fn_uniq_var = var_store.fresh();
|
let fn_uniq_var = var_store.fresh();
|
||||||
let fn_type = attr_type(
|
let fn_type = attr_type(
|
||||||
Bool::variable(fn_uniq_var),
|
Bool::variable(fn_uniq_var),
|
||||||
|
@ -1154,13 +1225,14 @@ pub fn constrain_expr(
|
||||||
|
|
||||||
exists(
|
exists(
|
||||||
vec![
|
vec![
|
||||||
|
*record_var,
|
||||||
*field_var,
|
*field_var,
|
||||||
*ext_var,
|
*ext_var,
|
||||||
fn_uniq_var,
|
fn_uniq_var,
|
||||||
field_uniq_var,
|
field_uniq_var,
|
||||||
record_uniq_var,
|
record_uniq_var,
|
||||||
],
|
],
|
||||||
And(vec![Eq(fn_type, expected, region)]),
|
And(vec![Eq(fn_type, expected, region), record_con]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RuntimeError(_) => True,
|
RuntimeError(_) => True,
|
||||||
|
|
|
@ -61,16 +61,14 @@ pub fn build_expr<'a, B: Backend>(
|
||||||
Bool(val) => builder.ins().bconst(types::B1, *val),
|
Bool(val) => builder.ins().bconst(types::B1, *val),
|
||||||
Byte(val) => builder.ins().iconst(types::I8, *val as i64),
|
Byte(val) => builder.ins().iconst(types::I8, *val as i64),
|
||||||
Cond {
|
Cond {
|
||||||
cond_lhs,
|
cond,
|
||||||
cond_rhs,
|
|
||||||
pass,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
cond_layout,
|
cond_layout,
|
||||||
ret_layout,
|
ret_layout,
|
||||||
} => {
|
} => {
|
||||||
let branch = Branch2 {
|
let branch = Branch2 {
|
||||||
cond_lhs,
|
cond,
|
||||||
cond_rhs,
|
|
||||||
pass,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
cond_layout,
|
cond_layout,
|
||||||
|
@ -337,8 +335,7 @@ pub fn build_expr<'a, B: Backend>(
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Branch2<'a> {
|
struct Branch2<'a> {
|
||||||
cond_lhs: &'a Expr<'a>,
|
cond: &'a Expr<'a>,
|
||||||
cond_rhs: &'a Expr<'a>,
|
|
||||||
cond_layout: &'a Layout<'a>,
|
cond_layout: &'a Layout<'a>,
|
||||||
pass: &'a Expr<'a>,
|
pass: &'a Expr<'a>,
|
||||||
fail: &'a Expr<'a>,
|
fail: &'a Expr<'a>,
|
||||||
|
@ -364,24 +361,13 @@ fn build_branch2<'a, B: Backend>(
|
||||||
|
|
||||||
builder.declare_var(ret, ret_type);
|
builder.declare_var(ret, ret_type);
|
||||||
|
|
||||||
let lhs = build_expr(env, scope, module, builder, branch.cond_lhs, procs);
|
let cond = build_expr(env, scope, module, builder, branch.cond, procs);
|
||||||
let rhs = build_expr(env, scope, module, builder, branch.cond_rhs, procs);
|
|
||||||
let pass_block = builder.create_block();
|
let pass_block = builder.create_block();
|
||||||
let fail_block = builder.create_block();
|
let fail_block = builder.create_block();
|
||||||
|
|
||||||
match branch.cond_layout {
|
match branch.cond_layout {
|
||||||
Layout::Builtin(Builtin::Float64) => {
|
Layout::Builtin(Builtin::Bool(_, _)) => {
|
||||||
// For floats, first do a `fcmp` comparison to get a bool answer about equality,
|
builder.ins().brnz(cond, pass_block, &[]);
|
||||||
// then use `brnz` to branch if that bool equality answer was nonzero (aka true).
|
|
||||||
let is_eq = builder.ins().fcmp(FloatCC::Equal, lhs, rhs);
|
|
||||||
|
|
||||||
builder.ins().brnz(is_eq, pass_block, &[]);
|
|
||||||
}
|
|
||||||
Layout::Builtin(Builtin::Int64) => {
|
|
||||||
// For ints, we can compare and branch in the same instruction: `icmp`
|
|
||||||
builder
|
|
||||||
.ins()
|
|
||||||
.br_icmp(IntCC::Equal, lhs, rhs, pass_block, &[]);
|
|
||||||
}
|
}
|
||||||
other => panic!("I don't know how to build a conditional for {:?}", other),
|
other => panic!("I don't know how to build a conditional for {:?}", other),
|
||||||
}
|
}
|
||||||
|
@ -606,6 +592,7 @@ fn build_arg<'a, B: Backend>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn call_by_name<'a, B: Backend>(
|
fn call_by_name<'a, B: Backend>(
|
||||||
env: &Env<'a>,
|
env: &Env<'a>,
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
|
@ -670,6 +657,20 @@ fn call_by_name<'a, B: Backend>(
|
||||||
Offset32::new(env.cfg.pointer_bytes() as i32),
|
Offset32::new(env.cfg.pointer_bytes() as i32),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Symbol::INT_EQ_I64 | Symbol::INT_EQ_I8 | Symbol::INT_EQ_I1 => {
|
||||||
|
debug_assert!(args.len() == 2);
|
||||||
|
let a = build_arg(&args[0], env, scope, module, builder, procs);
|
||||||
|
let b = build_arg(&args[1], env, scope, module, builder, procs);
|
||||||
|
|
||||||
|
builder.ins().icmp(IntCC::Equal, a, b)
|
||||||
|
}
|
||||||
|
Symbol::FLOAT_EQ => {
|
||||||
|
debug_assert!(args.len() == 2);
|
||||||
|
let a = build_arg(&args[0], env, scope, module, builder, procs);
|
||||||
|
let b = build_arg(&args[1], env, scope, module, builder, procs);
|
||||||
|
|
||||||
|
builder.ins().fcmp(FloatCC::Equal, a, b)
|
||||||
|
}
|
||||||
Symbol::LIST_GET_UNSAFE => {
|
Symbol::LIST_GET_UNSAFE => {
|
||||||
debug_assert!(args.len() == 2);
|
debug_assert!(args.len() == 2);
|
||||||
|
|
||||||
|
|
|
@ -47,23 +47,23 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
||||||
match expr {
|
match expr {
|
||||||
Int(num) => env.context.i64_type().const_int(*num as u64, true).into(),
|
Int(num) => env.context.i64_type().const_int(*num as u64, true).into(),
|
||||||
Float(num) => env.context.f64_type().const_float(*num).into(),
|
Float(num) => env.context.f64_type().const_float(*num).into(),
|
||||||
|
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
|
||||||
|
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
|
||||||
Cond {
|
Cond {
|
||||||
cond_lhs,
|
cond,
|
||||||
cond_rhs,
|
|
||||||
pass,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
ret_layout,
|
ret_layout,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let cond = Branch2 {
|
let conditional = Branch2 {
|
||||||
cond_lhs,
|
cond,
|
||||||
cond_rhs,
|
|
||||||
pass,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
ret_layout: ret_layout.clone(),
|
ret_layout: ret_layout.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
build_branch2(env, scope, parent, cond, procs)
|
build_branch2(env, scope, parent, conditional, procs)
|
||||||
}
|
}
|
||||||
Branches { .. } => {
|
Branches { .. } => {
|
||||||
panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)");
|
panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)");
|
||||||
|
@ -365,8 +365,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Branch2<'a> {
|
struct Branch2<'a> {
|
||||||
cond_lhs: &'a Expr<'a>,
|
cond: &'a Expr<'a>,
|
||||||
cond_rhs: &'a Expr<'a>,
|
|
||||||
pass: &'a Expr<'a>,
|
pass: &'a Expr<'a>,
|
||||||
fail: &'a Expr<'a>,
|
fail: &'a Expr<'a>,
|
||||||
ret_layout: Layout<'a>,
|
ret_layout: Layout<'a>,
|
||||||
|
@ -379,33 +378,18 @@ fn build_branch2<'a, 'ctx, 'env>(
|
||||||
cond: Branch2<'a>,
|
cond: Branch2<'a>,
|
||||||
procs: &Procs<'a>,
|
procs: &Procs<'a>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let builder = env.builder;
|
|
||||||
let ret_layout = cond.ret_layout;
|
let ret_layout = cond.ret_layout;
|
||||||
let ret_type = basic_type_from_layout(env.context, &ret_layout);
|
let ret_type = basic_type_from_layout(env.context, &ret_layout);
|
||||||
|
|
||||||
let lhs = build_expr(env, scope, parent, cond.cond_lhs, procs);
|
let cond_expr = build_expr(env, scope, parent, cond.cond, procs);
|
||||||
let rhs = build_expr(env, scope, parent, cond.cond_rhs, procs);
|
|
||||||
|
|
||||||
match (lhs, rhs) {
|
match cond_expr {
|
||||||
(FloatValue(lhs_float), FloatValue(rhs_float)) => {
|
IntValue(value) => build_phi2(
|
||||||
let comparison =
|
env, scope, parent, value, cond.pass, cond.fail, ret_type, procs,
|
||||||
builder.build_float_compare(FloatPredicate::OEQ, lhs_float, rhs_float, "cond");
|
),
|
||||||
|
|
||||||
build_phi2(
|
|
||||||
env, scope, parent, comparison, cond.pass, cond.fail, ret_type, procs,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
(IntValue(lhs_int), IntValue(rhs_int)) => {
|
|
||||||
let comparison = builder.build_int_compare(IntPredicate::EQ, lhs_int, rhs_int, "cond");
|
|
||||||
|
|
||||||
build_phi2(
|
|
||||||
env, scope, parent, comparison, cond.pass, cond.fail, ret_type, procs,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"Tried to make a branch out of incompatible conditions: lhs = {:?} and rhs = {:?}",
|
"Tried to make a branch out of an invalid condition: cond_expr = {:?}",
|
||||||
cond.cond_lhs, cond.cond_rhs
|
cond_expr,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,6 +415,7 @@ fn build_switch<'a, 'ctx, 'env>(
|
||||||
let SwitchArgs {
|
let SwitchArgs {
|
||||||
branches,
|
branches,
|
||||||
cond_expr,
|
cond_expr,
|
||||||
|
cond_layout,
|
||||||
default_branch,
|
default_branch,
|
||||||
ret_type,
|
ret_type,
|
||||||
..
|
..
|
||||||
|
@ -446,7 +431,24 @@ fn build_switch<'a, 'ctx, 'env>(
|
||||||
let mut cases = Vec::with_capacity_in(branches.len(), arena);
|
let mut cases = Vec::with_capacity_in(branches.len(), arena);
|
||||||
|
|
||||||
for (int, _) in branches.iter() {
|
for (int, _) in branches.iter() {
|
||||||
let int_val = context.i64_type().const_int(*int as u64, false);
|
// Switch constants must all be same type as switch value!
|
||||||
|
// e.g. this is incorrect, and will trigger a LLVM warning:
|
||||||
|
//
|
||||||
|
// switch i8 %apple1, label %default [
|
||||||
|
// i64 2, label %branch2
|
||||||
|
// i64 0, label %branch0
|
||||||
|
// i64 1, label %branch1
|
||||||
|
// ]
|
||||||
|
//
|
||||||
|
// they either need to all be i8, or i64
|
||||||
|
let int_val = match cond_layout {
|
||||||
|
Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false),
|
||||||
|
Layout::Builtin(Builtin::Bool(_, _)) => {
|
||||||
|
context.bool_type().const_int(*int as u64, false)
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Byte(_)) => context.i8_type().const_int(*int as u64, false),
|
||||||
|
_ => panic!("Can't cast to cond_layout = {:?}", cond_layout),
|
||||||
|
};
|
||||||
let block = context.append_basic_block(parent, format!("branch{}", int).as_str());
|
let block = context.append_basic_block(parent, format!("branch{}", int).as_str());
|
||||||
|
|
||||||
cases.push((int_val, block));
|
cases.push((int_val, block));
|
||||||
|
@ -644,6 +646,7 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn call_with_args<'a, 'ctx, 'env>(
|
fn call_with_args<'a, 'ctx, 'env>(
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
args: &[BasicValueEnum<'ctx>],
|
args: &[BasicValueEnum<'ctx>],
|
||||||
|
@ -737,6 +740,54 @@ fn call_with_args<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
BasicValueEnum::IntValue(answer)
|
BasicValueEnum::IntValue(answer)
|
||||||
}
|
}
|
||||||
|
Symbol::INT_EQ_I64 => {
|
||||||
|
debug_assert!(args.len() == 2);
|
||||||
|
|
||||||
|
let int_val = env.builder.build_int_compare(
|
||||||
|
IntPredicate::EQ,
|
||||||
|
args[0].into_int_value(),
|
||||||
|
args[1].into_int_value(),
|
||||||
|
"cmp_i64",
|
||||||
|
);
|
||||||
|
|
||||||
|
BasicValueEnum::IntValue(int_val)
|
||||||
|
}
|
||||||
|
Symbol::INT_EQ_I1 => {
|
||||||
|
debug_assert!(args.len() == 2);
|
||||||
|
|
||||||
|
let int_val = env.builder.build_int_compare(
|
||||||
|
IntPredicate::EQ,
|
||||||
|
args[0].into_int_value(),
|
||||||
|
args[1].into_int_value(),
|
||||||
|
"cmp_i1",
|
||||||
|
);
|
||||||
|
|
||||||
|
BasicValueEnum::IntValue(int_val)
|
||||||
|
}
|
||||||
|
Symbol::INT_EQ_I8 => {
|
||||||
|
debug_assert!(args.len() == 2);
|
||||||
|
|
||||||
|
let int_val = env.builder.build_int_compare(
|
||||||
|
IntPredicate::EQ,
|
||||||
|
args[0].into_int_value(),
|
||||||
|
args[1].into_int_value(),
|
||||||
|
"cmp_i8",
|
||||||
|
);
|
||||||
|
|
||||||
|
BasicValueEnum::IntValue(int_val)
|
||||||
|
}
|
||||||
|
Symbol::FLOAT_EQ => {
|
||||||
|
debug_assert!(args.len() == 2);
|
||||||
|
|
||||||
|
let int_val = env.builder.build_float_compare(
|
||||||
|
FloatPredicate::OEQ,
|
||||||
|
args[0].into_float_value(),
|
||||||
|
args[1].into_float_value(),
|
||||||
|
"cmp_f64",
|
||||||
|
);
|
||||||
|
|
||||||
|
BasicValueEnum::IntValue(int_val)
|
||||||
|
}
|
||||||
Symbol::LIST_GET_UNSAFE => {
|
Symbol::LIST_GET_UNSAFE => {
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
|
|
||||||
|
|
|
@ -835,6 +835,66 @@ mod test_gen {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gen_if_fn() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
limitedNegate = \num ->
|
||||||
|
if num == 1 then
|
||||||
|
-1
|
||||||
|
else if num == -1 then
|
||||||
|
1
|
||||||
|
else
|
||||||
|
num
|
||||||
|
|
||||||
|
limitedNegate 1
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
-1,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gen_float_eq() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
1.0 == 1.0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
bool
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gen_literal_true() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
if True then -1 else 1
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
-1,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gen_if_float_fn() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
if True then -1.0 else 1.0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
-1.0,
|
||||||
|
f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_identity_() {
|
fn apply_identity_() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
|
@ -958,6 +1018,49 @@ mod test_gen {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_enum() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Fruit : [ Apple, Orange, Banana ]
|
||||||
|
|
||||||
|
apple : Fruit
|
||||||
|
apple = Apple
|
||||||
|
|
||||||
|
orange : Fruit
|
||||||
|
orange = Orange
|
||||||
|
|
||||||
|
apple == orange
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
bool
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_on_enum() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Fruit : [ Apple, Orange, Banana ]
|
||||||
|
|
||||||
|
apple : Fruit
|
||||||
|
apple = Apple
|
||||||
|
|
||||||
|
when apple is
|
||||||
|
Apple -> 1
|
||||||
|
Banana -> 2
|
||||||
|
Orange -> 3
|
||||||
|
_ -> 4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_record() {
|
fn basic_record() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
|
|
|
@ -591,6 +591,9 @@ define_builtins! {
|
||||||
6 INT_LOWEST: "lowest"
|
6 INT_LOWEST: "lowest"
|
||||||
7 INT_ADD: "#add"
|
7 INT_ADD: "#add"
|
||||||
8 INT_SUB: "#sub"
|
8 INT_SUB: "#sub"
|
||||||
|
9 INT_EQ_I64: "#eqi64" // Equality on 64-bit integers, the standard in Roc
|
||||||
|
10 INT_EQ_I1: "#eqi1" // Equality on boolean (theoretically i1) values
|
||||||
|
11 INT_EQ_I8: "#eqi8" // Equality on byte (theoretically i8) values
|
||||||
}
|
}
|
||||||
3 FLOAT: "Float" => {
|
3 FLOAT: "Float" => {
|
||||||
0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias
|
0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias
|
||||||
|
@ -603,6 +606,7 @@ define_builtins! {
|
||||||
7 FLOAT_LOWEST: "lowest"
|
7 FLOAT_LOWEST: "lowest"
|
||||||
8 FLOAT_ADD: "#add"
|
8 FLOAT_ADD: "#add"
|
||||||
9 FLOAT_SUB: "#sub"
|
9 FLOAT_SUB: "#sub"
|
||||||
|
10 FLOAT_EQ: "#eq"
|
||||||
}
|
}
|
||||||
4 BOOL: "Bool" => {
|
4 BOOL: "Bool" => {
|
||||||
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
||||||
|
|
|
@ -2,11 +2,10 @@ use crate::layout::{Builtin, Layout};
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_can;
|
use roc_can;
|
||||||
use roc_can::pattern::Pattern;
|
|
||||||
use roc_collections::all::{MutMap, MutSet};
|
use roc_collections::all::{MutMap, MutSet};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||||
use roc_region::all::Located;
|
use roc_region::all::{Located, Region};
|
||||||
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
|
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Default)]
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
|
@ -150,8 +149,7 @@ pub enum Expr<'a> {
|
||||||
// The left-hand side of the conditional comparison and the right-hand side.
|
// The left-hand side of the conditional comparison and the right-hand side.
|
||||||
// These are stored separately because there are different machine instructions
|
// These are stored separately because there are different machine instructions
|
||||||
// for e.g. "compare float and jump" vs. "compare integer and jump"
|
// for e.g. "compare float and jump" vs. "compare integer and jump"
|
||||||
cond_lhs: &'a Expr<'a>,
|
cond: &'a Expr<'a>,
|
||||||
cond_rhs: &'a Expr<'a>,
|
|
||||||
cond_layout: Layout<'a>,
|
cond_layout: Layout<'a>,
|
||||||
// What to do if the condition either passes or fails
|
// What to do if the condition either passes or fails
|
||||||
pass: &'a Expr<'a>,
|
pass: &'a Expr<'a>,
|
||||||
|
@ -337,7 +335,7 @@ fn pattern_to_when<'a>(
|
||||||
(env.fresh_symbol(), body)
|
(env.fresh_symbol(), body)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppliedTag(_, _, _) | RecordDestructure(_, _) | Shadowed(_, _) | UnsupportedPattern(_) => {
|
AppliedTag {..} | RecordDestructure {..} | Shadowed(_, _) | UnsupportedPattern(_) => {
|
||||||
let symbol = env.fresh_symbol();
|
let symbol = env.fresh_symbol();
|
||||||
|
|
||||||
let wrapped_body = When {
|
let wrapped_body = When {
|
||||||
|
@ -407,9 +405,10 @@ fn from_can<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it wasn't specifically an Identifier & Closure, proceed as normal.
|
// If it wasn't specifically an Identifier & Closure, proceed as normal.
|
||||||
|
let mono_pattern = from_can_pattern(env, loc_pattern.value);
|
||||||
store_pattern(
|
store_pattern(
|
||||||
env,
|
env,
|
||||||
loc_pattern.value,
|
mono_pattern,
|
||||||
loc_expr.value,
|
loc_expr.value,
|
||||||
def.expr_var,
|
def.expr_var,
|
||||||
procs,
|
procs,
|
||||||
|
@ -456,10 +455,6 @@ fn from_can<'a>(
|
||||||
// by the surrounding context
|
// by the surrounding context
|
||||||
let symbol = env.fresh_symbol();
|
let symbol = env.fresh_symbol();
|
||||||
|
|
||||||
// Has the side-effect of monomorphizing record types
|
|
||||||
// turning the ext_var into EmptyRecord or EmptyTagUnion
|
|
||||||
let _ = ContentHash::from_var(annotation, env.subs);
|
|
||||||
|
|
||||||
let opt_proc = specialize_proc_body(
|
let opt_proc = specialize_proc_body(
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
|
@ -486,16 +481,36 @@ fn from_can<'a>(
|
||||||
|
|
||||||
let (fn_var, loc_expr, ret_var) = *boxed;
|
let (fn_var, loc_expr, ret_var) = *boxed;
|
||||||
|
|
||||||
|
// Optimization: have a cheap "is_builtin" check, that looks at the
|
||||||
|
// module ID to see if it's possibly a builting symbol
|
||||||
let specialize_builtin_functions = {
|
let specialize_builtin_functions = {
|
||||||
|symbol, subs: &Subs| match symbol {
|
|env: &mut Env<'a, '_>, symbol| match symbol {
|
||||||
Symbol::NUM_ADD => match to_int_or_float(subs, ret_var) {
|
Symbol::NUM_ADD => match to_int_or_float(env.subs, ret_var) {
|
||||||
FloatType => Symbol::FLOAT_ADD,
|
FloatType => Symbol::FLOAT_ADD,
|
||||||
IntType => Symbol::INT_ADD,
|
IntType => Symbol::INT_ADD,
|
||||||
},
|
},
|
||||||
Symbol::NUM_SUB => match to_int_or_float(subs, ret_var) {
|
Symbol::NUM_SUB => match to_int_or_float(env.subs, ret_var) {
|
||||||
FloatType => Symbol::FLOAT_SUB,
|
FloatType => Symbol::FLOAT_SUB,
|
||||||
IntType => Symbol::INT_SUB,
|
IntType => Symbol::INT_SUB,
|
||||||
},
|
},
|
||||||
|
// TODO make this work for more than just int/float
|
||||||
|
Symbol::BOOL_EQ => {
|
||||||
|
match Layout::from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size)
|
||||||
|
{
|
||||||
|
Ok(Layout::Builtin(builtin)) => match builtin {
|
||||||
|
Builtin::Int64 => Symbol::INT_EQ_I64,
|
||||||
|
Builtin::Float64 => Symbol::FLOAT_EQ,
|
||||||
|
Builtin::Bool(_, _) => Symbol::INT_EQ_I1,
|
||||||
|
Builtin::Byte(_) => Symbol::INT_EQ_I8,
|
||||||
|
_ => panic!("Equality not implemented for {:?}", builtin),
|
||||||
|
},
|
||||||
|
Ok(complex) => panic!(
|
||||||
|
"TODO support equality on complex layouts like {:?}",
|
||||||
|
complex
|
||||||
|
),
|
||||||
|
Err(()) => panic!("Invalid layout"),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => symbol,
|
_ => symbol,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -504,7 +519,7 @@ fn from_can<'a>(
|
||||||
Expr::Load(proc_name) => {
|
Expr::Load(proc_name) => {
|
||||||
// Some functions can potentially mutate in-place.
|
// Some functions can potentially mutate in-place.
|
||||||
// If we have one of those, switch to the in-place version if appropriate.
|
// If we have one of those, switch to the in-place version if appropriate.
|
||||||
match specialize_builtin_functions(proc_name, &env.subs) {
|
match specialize_builtin_functions(env, proc_name) {
|
||||||
Symbol::LIST_SET => {
|
Symbol::LIST_SET => {
|
||||||
let subs = &env.subs;
|
let subs = &env.subs;
|
||||||
// The first arg is the one with the List in it.
|
// The first arg is the one with the List in it.
|
||||||
|
@ -576,7 +591,37 @@ fn from_can<'a>(
|
||||||
branches,
|
branches,
|
||||||
} => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs),
|
} => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs),
|
||||||
|
|
||||||
Record(ext_var, fields) => {
|
If {
|
||||||
|
cond_var,
|
||||||
|
branch_var,
|
||||||
|
branches,
|
||||||
|
final_else,
|
||||||
|
} => {
|
||||||
|
let mut expr = from_can(env, final_else.value, procs, None);
|
||||||
|
|
||||||
|
let ret_layout = Layout::from_var(env.arena, branch_var, env.subs, env.pointer_size)
|
||||||
|
.expect("invalid ret_layout");
|
||||||
|
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
|
||||||
|
.expect("invalid cond_layout");
|
||||||
|
|
||||||
|
for (loc_cond, loc_then) in branches.into_iter().rev() {
|
||||||
|
let cond = from_can(env, loc_cond.value, procs, None);
|
||||||
|
let then = from_can(env, loc_then.value, procs, None);
|
||||||
|
expr = Expr::Cond {
|
||||||
|
cond: env.arena.alloc(cond),
|
||||||
|
cond_layout: cond_layout.clone(),
|
||||||
|
pass: env.arena.alloc(then),
|
||||||
|
fail: env.arena.alloc(expr),
|
||||||
|
ret_layout: ret_layout.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
|
||||||
|
Record {
|
||||||
|
record_var, fields, ..
|
||||||
|
} => {
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
let mut field_bodies = Vec::with_capacity_in(fields.len(), arena);
|
let mut field_bodies = Vec::with_capacity_in(fields.len(), arena);
|
||||||
|
|
||||||
|
@ -586,13 +631,14 @@ fn from_can<'a>(
|
||||||
field_bodies.push((label, expr));
|
field_bodies.push((label, expr));
|
||||||
}
|
}
|
||||||
|
|
||||||
let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) {
|
let struct_layout =
|
||||||
Ok(layout) => layout,
|
match Layout::from_var(arena, record_var, env.subs, env.pointer_size) {
|
||||||
Err(()) => {
|
Ok(layout) => layout,
|
||||||
// Invalid field!
|
Err(()) => {
|
||||||
panic!("TODO gracefully handle Record with invalid struct_layout");
|
// Invalid field!
|
||||||
}
|
panic!("TODO gracefully handle Record with invalid struct_layout");
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Expr::Struct {
|
Expr::Struct {
|
||||||
fields: field_bodies.into_bump_slice(),
|
fields: field_bodies.into_bump_slice(),
|
||||||
|
@ -635,20 +681,22 @@ fn from_can<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
Access {
|
Access {
|
||||||
ext_var,
|
record_var,
|
||||||
field_var,
|
field_var,
|
||||||
field,
|
field,
|
||||||
loc_expr,
|
loc_expr,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
|
|
||||||
let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) {
|
let struct_layout =
|
||||||
Ok(layout) => layout,
|
match Layout::from_var(arena, record_var, env.subs, env.pointer_size) {
|
||||||
Err(()) => {
|
Ok(layout) => layout,
|
||||||
// Invalid field!
|
Err(()) => {
|
||||||
panic!("TODO gracefully handle Access with invalid struct_layout");
|
// Invalid field!
|
||||||
}
|
panic!("TODO gracefully handle Access with invalid struct_layout");
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let field_layout = match Layout::from_var(arena, field_var, env.subs, env.pointer_size)
|
let field_layout = match Layout::from_var(arena, field_var, env.subs, env.pointer_size)
|
||||||
{
|
{
|
||||||
|
@ -711,7 +759,7 @@ fn store_pattern<'a>(
|
||||||
procs: &mut Procs<'a>,
|
procs: &mut Procs<'a>,
|
||||||
stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
|
stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
|
||||||
) {
|
) {
|
||||||
use roc_can::pattern::Pattern::*;
|
use Pattern::*;
|
||||||
|
|
||||||
let layout = match Layout::from_var(env.arena, var, env.subs, env.pointer_size) {
|
let layout = match Layout::from_var(env.arena, var, env.subs, env.pointer_size) {
|
||||||
Ok(layout) => layout,
|
Ok(layout) => layout,
|
||||||
|
@ -760,7 +808,7 @@ fn from_can_when<'a>(
|
||||||
)>,
|
)>,
|
||||||
procs: &mut Procs<'a>,
|
procs: &mut Procs<'a>,
|
||||||
) -> Expr<'a> {
|
) -> Expr<'a> {
|
||||||
use roc_can::pattern::Pattern::*;
|
use Pattern::*;
|
||||||
|
|
||||||
match branches.len() {
|
match branches.len() {
|
||||||
0 => {
|
0 => {
|
||||||
|
@ -775,9 +823,10 @@ fn from_can_when<'a>(
|
||||||
let mut stored = Vec::with_capacity_in(1, arena);
|
let mut stored = Vec::with_capacity_in(1, arena);
|
||||||
let (loc_when_pattern, loc_branch) = branches.into_iter().next().unwrap();
|
let (loc_when_pattern, loc_branch) = branches.into_iter().next().unwrap();
|
||||||
|
|
||||||
|
let mono_pattern = from_can_pattern(env, loc_when_pattern.value);
|
||||||
store_pattern(
|
store_pattern(
|
||||||
env,
|
env,
|
||||||
loc_when_pattern.value,
|
mono_pattern,
|
||||||
loc_cond.value,
|
loc_cond.value,
|
||||||
cond_var,
|
cond_var,
|
||||||
procs,
|
procs,
|
||||||
|
@ -792,18 +841,30 @@ fn from_can_when<'a>(
|
||||||
// A when-expression with exactly 2 branches compiles to a Cond.
|
// A when-expression with exactly 2 branches compiles to a Cond.
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
let mut iter = branches.into_iter();
|
let mut iter = branches.into_iter();
|
||||||
let (loc_when_pat1, loc_then) = iter.next().unwrap();
|
let (can_loc_when_pat1, loc_then) = iter.next().unwrap();
|
||||||
let (loc_when_pat2, loc_else) = iter.next().unwrap();
|
let (can_loc_when_pat2, loc_else) = iter.next().unwrap();
|
||||||
|
|
||||||
match (&loc_when_pat1.value, &loc_when_pat2.value) {
|
let when_pat1 = from_can_pattern(env, can_loc_when_pat1.value);
|
||||||
(NumLiteral(var, num), NumLiteral(_, _)) | (NumLiteral(var, num), Underscore) => {
|
let when_pat2 = from_can_pattern(env, can_loc_when_pat2.value);
|
||||||
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
|
|
||||||
let (builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) {
|
let cond_layout = Layout::Builtin(Builtin::Bool(
|
||||||
IntOrFloat::IntType => (Builtin::Int64, Expr::Int(*num)),
|
TagName::Global("False".into()),
|
||||||
IntOrFloat::FloatType => (Builtin::Float64, Expr::Float(*num as f64)),
|
TagName::Global("True".into()),
|
||||||
};
|
));
|
||||||
|
|
||||||
|
match (&when_pat1, &when_pat2) {
|
||||||
|
(IntLiteral(int), Underscore) => {
|
||||||
|
let cond_lhs = from_can(env, loc_cond.value, procs, None);
|
||||||
|
let cond_rhs = Expr::Int(*int);
|
||||||
|
|
||||||
|
let cond = arena.alloc(Expr::CallByName(
|
||||||
|
Symbol::INT_EQ_I64,
|
||||||
|
arena.alloc([
|
||||||
|
(cond_lhs, Layout::Builtin(Builtin::Int64)),
|
||||||
|
(cond_rhs, Layout::Builtin(Builtin::Int64)),
|
||||||
|
]),
|
||||||
|
));
|
||||||
|
|
||||||
let cond_rhs = arena.alloc(cond_rhs_expr);
|
|
||||||
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
||||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||||
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
|
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
|
||||||
|
@ -812,17 +873,25 @@ fn from_can_when<'a>(
|
||||||
});
|
});
|
||||||
|
|
||||||
Expr::Cond {
|
Expr::Cond {
|
||||||
cond_layout: Layout::Builtin(builtin),
|
cond_layout,
|
||||||
cond_lhs,
|
cond,
|
||||||
cond_rhs,
|
|
||||||
pass,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
ret_layout,
|
ret_layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => {
|
(FloatLiteral(float), Underscore) => {
|
||||||
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
|
let cond_lhs = from_can(env, loc_cond.value, procs, None);
|
||||||
let cond_rhs = arena.alloc(Expr::Int(*int));
|
let cond_rhs = Expr::Float(*float);
|
||||||
|
|
||||||
|
let cond = arena.alloc(Expr::CallByName(
|
||||||
|
Symbol::FLOAT_EQ,
|
||||||
|
arena.alloc([
|
||||||
|
(cond_lhs, Layout::Builtin(Builtin::Float64)),
|
||||||
|
(cond_rhs, Layout::Builtin(Builtin::Float64)),
|
||||||
|
]),
|
||||||
|
));
|
||||||
|
|
||||||
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
||||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||||
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
|
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
|
||||||
|
@ -831,28 +900,8 @@ fn from_can_when<'a>(
|
||||||
});
|
});
|
||||||
|
|
||||||
Expr::Cond {
|
Expr::Cond {
|
||||||
cond_layout: Layout::Builtin(Builtin::Int64),
|
cond_layout,
|
||||||
cond_lhs,
|
cond,
|
||||||
cond_rhs,
|
|
||||||
pass,
|
|
||||||
fail,
|
|
||||||
ret_layout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => {
|
|
||||||
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
|
|
||||||
let cond_rhs = arena.alloc(Expr::Float(*float));
|
|
||||||
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
|
|
||||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
|
||||||
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
|
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
panic!("TODO turn this into a RuntimeError {:?}", err)
|
|
||||||
});
|
|
||||||
|
|
||||||
Expr::Cond {
|
|
||||||
cond_layout: Layout::Builtin(Builtin::Float64),
|
|
||||||
cond_lhs,
|
|
||||||
cond_rhs,
|
|
||||||
pass,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
ret_layout,
|
ret_layout,
|
||||||
|
@ -880,6 +929,8 @@ fn from_can_when<'a>(
|
||||||
// TODO we can also convert floats to integer representations.
|
// TODO we can also convert floats to integer representations.
|
||||||
let is_switchable = match layout {
|
let is_switchable = match layout {
|
||||||
Layout::Builtin(Builtin::Int64) => true,
|
Layout::Builtin(Builtin::Int64) => true,
|
||||||
|
Layout::Builtin(Builtin::Bool(_, _)) => true,
|
||||||
|
Layout::Builtin(Builtin::Byte(_)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -893,43 +944,31 @@ fn from_can_when<'a>(
|
||||||
|
|
||||||
for (loc_when_pat, loc_expr) in branches {
|
for (loc_when_pat, loc_expr) in branches {
|
||||||
let mono_expr = from_can(env, loc_expr.value, procs, None);
|
let mono_expr = from_can(env, loc_expr.value, procs, None);
|
||||||
|
let when_pat = from_can_pattern(env, loc_when_pat.value);
|
||||||
|
|
||||||
match &loc_when_pat.value {
|
match &when_pat {
|
||||||
NumLiteral(var, num) => {
|
|
||||||
// This is jumpable iff it's an int
|
|
||||||
match to_int_or_float(env.subs, *var) {
|
|
||||||
IntOrFloat::IntType => {
|
|
||||||
jumpable_branches.push((*num as u64, mono_expr));
|
|
||||||
}
|
|
||||||
IntOrFloat::FloatType => {
|
|
||||||
// The type checker should have converted these mismatches into RuntimeErrors already!
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_when_pat);
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
IntLiteral(int) => {
|
IntLiteral(int) => {
|
||||||
// Switch only compares the condition to the
|
// Switch only compares the condition to the
|
||||||
// alternatives based on their bit patterns,
|
// alternatives based on their bit patterns,
|
||||||
// so casting from i64 to u64 makes no difference here.
|
// so casting from i64 to u64 makes no difference here.
|
||||||
jumpable_branches.push((*int as u64, mono_expr));
|
jumpable_branches.push((*int as u64, mono_expr));
|
||||||
}
|
}
|
||||||
Identifier(_symbol) => {
|
BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)),
|
||||||
|
EnumLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)),
|
||||||
|
Identifier(symbol) => {
|
||||||
// Since this is an ident, it must be
|
// Since this is an ident, it must be
|
||||||
// the last pattern in the `when`.
|
// the last pattern in the `when`.
|
||||||
// We can safely treat this like an `_`
|
// We can safely treat this like an `_`
|
||||||
// except that we need to wrap this branch
|
// except that we need to wrap this branch
|
||||||
// in a `Store` so the identifier is in scope!
|
// in a `Store` so the identifier is in scope!
|
||||||
|
|
||||||
opt_default_branch = Some(arena.alloc(if true {
|
// TODO does this evaluate `cond` twice?
|
||||||
// Using `if true` for this TODO panic to avoid a warning
|
let mono_with_store = Expr::Store(
|
||||||
panic!("TODO wrap this expr in an Expr::Store: {:?}", mono_expr)
|
arena.alloc([(*symbol, layout.clone(), cond.clone())]),
|
||||||
} else {
|
arena.alloc(mono_expr),
|
||||||
mono_expr
|
);
|
||||||
}));
|
|
||||||
|
opt_default_branch = Some(arena.alloc(mono_with_store));
|
||||||
}
|
}
|
||||||
Underscore => {
|
Underscore => {
|
||||||
// We should always have exactly one default branch!
|
// We should always have exactly one default branch!
|
||||||
|
@ -950,7 +989,7 @@ fn from_can_when<'a>(
|
||||||
| FloatLiteral(_) => {
|
| FloatLiteral(_) => {
|
||||||
// The type checker should have converted these mismatches into RuntimeErrors already!
|
// The type checker should have converted these mismatches into RuntimeErrors already!
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_when_pat);
|
panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", when_pat);
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
@ -969,6 +1008,7 @@ fn from_can_when<'a>(
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
panic!("TODO turn cond_layout into a RuntimeError {:?}", err)
|
panic!("TODO turn cond_layout into a RuntimeError {:?}", err)
|
||||||
});
|
});
|
||||||
|
|
||||||
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
|
let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
panic!("TODO turn ret_layout into a RuntimeError {:?}", err)
|
panic!("TODO turn ret_layout into a RuntimeError {:?}", err)
|
||||||
|
@ -1130,3 +1170,105 @@ fn specialize_proc_body<'a>(
|
||||||
|
|
||||||
Some(proc)
|
Some(proc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pattern, including possible problems (e.g. shadowing) so that
|
||||||
|
/// codegen can generate a runtime error if this pattern is reached.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Pattern<'a> {
|
||||||
|
Identifier(Symbol),
|
||||||
|
AppliedTag(TagName, Vec<'a, Pattern<'a>>, Layout<'a>),
|
||||||
|
BitLiteral(bool),
|
||||||
|
EnumLiteral(u8),
|
||||||
|
IntLiteral(i64),
|
||||||
|
FloatLiteral(f64),
|
||||||
|
StrLiteral(Box<str>),
|
||||||
|
RecordDestructure(Vec<'a, RecordDestruct<'a>>, Layout<'a>),
|
||||||
|
Underscore,
|
||||||
|
|
||||||
|
// Runtime Exceptions
|
||||||
|
Shadowed(Region, Located<Ident>),
|
||||||
|
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||||
|
UnsupportedPattern(Region),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct RecordDestruct<'a> {
|
||||||
|
pub label: Lowercase,
|
||||||
|
pub symbol: Symbol,
|
||||||
|
pub guard: Option<Pattern<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_can_pattern<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
can_pattern: roc_can::pattern::Pattern,
|
||||||
|
) -> Pattern<'a> {
|
||||||
|
use roc_can::pattern::Pattern::*;
|
||||||
|
match can_pattern {
|
||||||
|
Underscore => Pattern::Underscore,
|
||||||
|
Identifier(symbol) => Pattern::Identifier(symbol),
|
||||||
|
IntLiteral(v) => Pattern::IntLiteral(v),
|
||||||
|
FloatLiteral(v) => Pattern::FloatLiteral(v),
|
||||||
|
StrLiteral(v) => Pattern::StrLiteral(v),
|
||||||
|
Shadowed(region, ident) => Pattern::Shadowed(region, ident),
|
||||||
|
UnsupportedPattern(region) => Pattern::UnsupportedPattern(region),
|
||||||
|
|
||||||
|
NumLiteral(var, num) => match to_int_or_float(env.subs, var) {
|
||||||
|
IntOrFloat::IntType => Pattern::IntLiteral(num),
|
||||||
|
IntOrFloat::FloatType => Pattern::FloatLiteral(num as f64),
|
||||||
|
},
|
||||||
|
|
||||||
|
AppliedTag {
|
||||||
|
whole_var,
|
||||||
|
tag_name,
|
||||||
|
arguments,
|
||||||
|
..
|
||||||
|
} => match Layout::from_var(env.arena, whole_var, env.subs, env.pointer_size) {
|
||||||
|
Ok(Layout::Builtin(Builtin::Bool(_bottom, top))) => {
|
||||||
|
Pattern::BitLiteral(tag_name == top)
|
||||||
|
}
|
||||||
|
Ok(Layout::Builtin(Builtin::Byte(conversion))) => match conversion.get(&tag_name) {
|
||||||
|
Some(index) => Pattern::EnumLiteral(*index),
|
||||||
|
None => unreachable!("Tag must be in its own type"),
|
||||||
|
},
|
||||||
|
Ok(layout) => {
|
||||||
|
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||||
|
for (_, loc_pat) in arguments {
|
||||||
|
mono_args.push(from_can_pattern(env, loc_pat.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern::AppliedTag(tag_name, mono_args, layout)
|
||||||
|
}
|
||||||
|
Err(()) => panic!("Invalid layout"),
|
||||||
|
},
|
||||||
|
|
||||||
|
RecordDestructure {
|
||||||
|
whole_var,
|
||||||
|
destructs,
|
||||||
|
..
|
||||||
|
} => match Layout::from_var(env.arena, whole_var, env.subs, env.pointer_size) {
|
||||||
|
Ok(layout) => {
|
||||||
|
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
|
||||||
|
for loc_rec_des in destructs {
|
||||||
|
mono_destructs.push(from_can_record_destruct(env, loc_rec_des.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern::RecordDestructure(mono_destructs, layout)
|
||||||
|
}
|
||||||
|
Err(()) => panic!("Invalid layout"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_can_record_destruct<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
can_rd: roc_can::pattern::RecordDestruct,
|
||||||
|
) -> RecordDestruct<'a> {
|
||||||
|
RecordDestruct {
|
||||||
|
label: can_rd.label,
|
||||||
|
symbol: can_rd.symbol,
|
||||||
|
guard: match can_rd.guard {
|
||||||
|
None => None,
|
||||||
|
Some((_, loc_pattern)) => Some(from_can_pattern(env, loc_pattern.value)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -130,6 +130,7 @@ impl<'a> Builtin<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn layout_from_flat_type<'a>(
|
fn layout_from_flat_type<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
flat_type: FlatType,
|
flat_type: FlatType,
|
||||||
|
@ -211,8 +212,8 @@ fn layout_from_flat_type<'a>(
|
||||||
arena.alloc(ret),
|
arena.alloc(ret),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Record(mut fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
flatten_record(&mut fields, ext_var, subs);
|
debug_assert!(ext_var_is_empty_record(subs, ext_var));
|
||||||
let ext_content = subs.get_without_compacting(ext_var).content;
|
let ext_content = subs.get_without_compacting(ext_var).content;
|
||||||
let ext_layout = match Layout::from_content(arena, ext_content, subs, pointer_size) {
|
let ext_layout = match Layout::from_content(arena, ext_content, subs, pointer_size) {
|
||||||
Ok(layout) => layout,
|
Ok(layout) => layout,
|
||||||
|
@ -256,10 +257,8 @@ fn layout_from_flat_type<'a>(
|
||||||
|
|
||||||
Ok(Layout::Struct(field_layouts.into_bump_slice()))
|
Ok(Layout::Struct(field_layouts.into_bump_slice()))
|
||||||
}
|
}
|
||||||
TagUnion(mut tags, ext_var) => {
|
TagUnion(tags, ext_var) => {
|
||||||
// Recursively inject the contents of ext_var into tags
|
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
|
||||||
// until we have all the tags in one map.
|
|
||||||
flatten_union(&mut tags, ext_var, subs);
|
|
||||||
|
|
||||||
match tags.len() {
|
match tags.len() {
|
||||||
0 => {
|
0 => {
|
||||||
|
@ -347,6 +346,35 @@ fn layout_from_flat_type<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
|
||||||
|
// the ext_var is empty
|
||||||
|
let mut ext_fields = std::vec::Vec::new();
|
||||||
|
match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) {
|
||||||
|
Ok(()) | Err((_, Content::FlexVar(_))) => {
|
||||||
|
if !ext_fields.is_empty() {
|
||||||
|
println!("ext_tags: {:?}", ext_fields);
|
||||||
|
}
|
||||||
|
ext_fields.is_empty()
|
||||||
|
}
|
||||||
|
Err(content) => panic!("invalid content in ext_var: {:?}", content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
|
||||||
|
// the ext_var is empty
|
||||||
|
let mut ext_fields = MutMap::default();
|
||||||
|
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) {
|
||||||
|
Ok(()) | Err((_, Content::FlexVar(_))) => {
|
||||||
|
if !ext_fields.is_empty() {
|
||||||
|
println!("ext_fields: {:?}", ext_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
ext_fields.is_empty()
|
||||||
|
}
|
||||||
|
Err((_, content)) => panic!("invalid content in ext_var: {:?}", content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, ()> {
|
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, ()> {
|
||||||
use roc_types::subs::Content::*;
|
use roc_types::subs::Content::*;
|
||||||
use roc_types::subs::FlatType::*;
|
use roc_types::subs::FlatType::*;
|
||||||
|
@ -375,55 +403,6 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, ()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively inline the contents ext_var into this union until we have
|
|
||||||
/// a flat union containing all the tags.
|
|
||||||
fn flatten_union(
|
|
||||||
tags: &mut MutMap<TagName, std::vec::Vec<Variable>>,
|
|
||||||
ext_var: Variable,
|
|
||||||
subs: &Subs,
|
|
||||||
) {
|
|
||||||
use roc_types::subs::Content::*;
|
|
||||||
use roc_types::subs::FlatType::*;
|
|
||||||
|
|
||||||
match subs.get_without_compacting(ext_var).content {
|
|
||||||
Structure(EmptyTagUnion) => (),
|
|
||||||
Structure(TagUnion(new_tags, new_ext_var))
|
|
||||||
| Structure(RecursiveTagUnion(_, new_tags, new_ext_var)) => {
|
|
||||||
for (tag_name, vars) in new_tags {
|
|
||||||
tags.insert(tag_name, vars);
|
|
||||||
}
|
|
||||||
|
|
||||||
flatten_union(tags, new_ext_var, subs)
|
|
||||||
}
|
|
||||||
Alias(_, _, actual) => flatten_union(tags, actual, subs),
|
|
||||||
invalid => {
|
|
||||||
panic!("Compiler error: flatten_union got an ext_var in a tag union that wasn't itself a tag union; instead, it was: {:?}", invalid);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively inline the contents ext_var into this record until we have
|
|
||||||
/// a flat record containing all the fields.
|
|
||||||
fn flatten_record(fields: &mut MutMap<Lowercase, Variable>, ext_var: Variable, subs: &Subs) {
|
|
||||||
use roc_types::subs::Content::*;
|
|
||||||
use roc_types::subs::FlatType::*;
|
|
||||||
|
|
||||||
match subs.get_without_compacting(ext_var).content {
|
|
||||||
Structure(EmptyRecord) => (),
|
|
||||||
Structure(Record(new_tags, new_ext_var)) => {
|
|
||||||
for (label, var) in new_tags {
|
|
||||||
fields.insert(label, var);
|
|
||||||
}
|
|
||||||
|
|
||||||
flatten_record(fields, new_ext_var, subs)
|
|
||||||
}
|
|
||||||
Alias(_, _, actual) => flatten_record(fields, actual, subs),
|
|
||||||
invalid => {
|
|
||||||
panic!("Compiler error: flatten_record encountered an ext_var in a record that wasn't itself a record; instead, it was: {:?}", invalid);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, ()> {
|
fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, ()> {
|
||||||
match subs.get_without_compacting(var).content {
|
match subs.get_without_compacting(var).content {
|
||||||
Content::Structure(flat_type) => match flat_type {
|
Content::Structure(flat_type) => match flat_type {
|
||||||
|
|
|
@ -158,6 +158,97 @@ mod test_mono {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_expression() {
|
||||||
|
compiles_to(
|
||||||
|
r#"
|
||||||
|
if True then "bar" else "foo"
|
||||||
|
"#,
|
||||||
|
{
|
||||||
|
use self::Builtin::*;
|
||||||
|
use Layout::Builtin;
|
||||||
|
|
||||||
|
Cond {
|
||||||
|
cond: &Expr::Bool(true),
|
||||||
|
cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))),
|
||||||
|
pass: &Expr::Str("bar"),
|
||||||
|
fail: &Expr::Str("foo"),
|
||||||
|
ret_layout: Builtin(Str),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiway_if_expression() {
|
||||||
|
compiles_to(
|
||||||
|
r#"
|
||||||
|
if True then
|
||||||
|
"bar"
|
||||||
|
else if False then
|
||||||
|
"foo"
|
||||||
|
else
|
||||||
|
"baz"
|
||||||
|
"#,
|
||||||
|
{
|
||||||
|
use self::Builtin::*;
|
||||||
|
use Layout::Builtin;
|
||||||
|
|
||||||
|
Cond {
|
||||||
|
cond: &Expr::Bool(true),
|
||||||
|
cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))),
|
||||||
|
pass: &Expr::Str("bar"),
|
||||||
|
fail: &Cond {
|
||||||
|
cond: &Expr::Bool(false),
|
||||||
|
cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))),
|
||||||
|
pass: &Expr::Str("foo"),
|
||||||
|
fail: &Expr::Str("baz"),
|
||||||
|
ret_layout: Builtin(Str),
|
||||||
|
},
|
||||||
|
ret_layout: Builtin(Str),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn annotated_if_expression() {
|
||||||
|
// an if with an annotation gets constrained differently. Make sure the result is still correct.
|
||||||
|
compiles_to(
|
||||||
|
r#"
|
||||||
|
x : Str
|
||||||
|
x = if True then "bar" else "foo"
|
||||||
|
|
||||||
|
x
|
||||||
|
"#,
|
||||||
|
{
|
||||||
|
use self::Builtin::*;
|
||||||
|
use Layout::Builtin;
|
||||||
|
|
||||||
|
let home = test_home();
|
||||||
|
let symbol_x = Interns::from_index(home, 0);
|
||||||
|
|
||||||
|
Store(
|
||||||
|
&[(
|
||||||
|
symbol_x,
|
||||||
|
Builtin(Str),
|
||||||
|
Cond {
|
||||||
|
cond: &Expr::Bool(true),
|
||||||
|
cond_layout: Builtin(Bool(
|
||||||
|
Global("False".into()),
|
||||||
|
Global("True".into()),
|
||||||
|
)),
|
||||||
|
pass: &Expr::Str("bar"),
|
||||||
|
fail: &Expr::Str("foo"),
|
||||||
|
ret_layout: Builtin(Str),
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
&Load(symbol_x),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn record_pattern() {
|
// fn record_pattern() {
|
||||||
// compiles_to(
|
// compiles_to(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::report::ReportText::{Batch, Region, Value};
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_problem::can::Problem;
|
use roc_problem::can::Problem;
|
||||||
use roc_region::all::Region;
|
|
||||||
use roc_types::pretty_print::content_to_string;
|
use roc_types::pretty_print::content_to_string;
|
||||||
use roc_types::subs::{Content, Subs};
|
use roc_types::subs::{Content, Subs};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -11,19 +11,32 @@ pub struct Report {
|
||||||
pub text: ReportText,
|
pub text: ReportText,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Report {
|
pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
|
||||||
pub fn can_problem(_filename: PathBuf, _problem: Problem) -> Self {
|
let mut texts = Vec::new();
|
||||||
// let text = match problem {
|
|
||||||
// Problem::UnusedDef(symbol, region) => {
|
|
||||||
// panic!("TODO implelment me!");
|
|
||||||
// }
|
|
||||||
// _ => {
|
|
||||||
// panic!("TODO implement others");
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Report { filename, text }
|
match problem {
|
||||||
panic!("TODO implement me!");
|
Problem::UnusedDef(symbol, region) => {
|
||||||
|
texts.push(Value(symbol));
|
||||||
|
texts.push(plain_text(" is not used anywhere in your code."));
|
||||||
|
texts.push(newline());
|
||||||
|
texts.push(newline());
|
||||||
|
texts.push(Region(region));
|
||||||
|
texts.push(newline());
|
||||||
|
texts.push(newline());
|
||||||
|
texts.push(plain_text("If you didn't intend on using "));
|
||||||
|
texts.push(Value(symbol));
|
||||||
|
texts.push(plain_text(
|
||||||
|
" then remove it so future readers of your code don't wonder why it is there.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("TODO implement others");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
text: Batch(texts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,13 +56,25 @@ pub enum ReportText {
|
||||||
EmText(Box<str>),
|
EmText(Box<str>),
|
||||||
|
|
||||||
/// A region in the original source
|
/// A region in the original source
|
||||||
Region(Region),
|
Region(roc_region::all::Region),
|
||||||
|
|
||||||
/// A URL, which should be rendered as a hyperlink.
|
/// A URL, which should be rendered as a hyperlink.
|
||||||
Url(Box<str>),
|
Url(Box<str>),
|
||||||
|
|
||||||
/// The documentation for this symbol.
|
/// The documentation for this symbol.
|
||||||
Docs(Symbol),
|
Docs(Symbol),
|
||||||
|
|
||||||
|
Batch(Vec<ReportText>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plain_text(str: &str) -> ReportText {
|
||||||
|
use ReportText::*;
|
||||||
|
|
||||||
|
Plain(Box::from(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newline() -> ReportText {
|
||||||
|
plain_text("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportText {
|
impl ReportText {
|
||||||
|
@ -89,7 +114,7 @@ impl ReportText {
|
||||||
}
|
}
|
||||||
Type(content) => buf.push_str(content_to_string(content, subs, home, interns).as_str()),
|
Type(content) => buf.push_str(content_to_string(content, subs, home, interns).as_str()),
|
||||||
Region(region) => {
|
Region(region) => {
|
||||||
for i in region.start_line..region.end_line {
|
for i in region.start_line..=region.end_line {
|
||||||
buf.push_str(i.to_string().as_str());
|
buf.push_str(i.to_string().as_str());
|
||||||
buf.push_str(" |");
|
buf.push_str(" |");
|
||||||
|
|
||||||
|
@ -100,12 +125,19 @@ impl ReportText {
|
||||||
buf.push_str(src_lines[i as usize]);
|
buf.push_str(src_lines[i as usize]);
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push('\n');
|
if i != region.end_line {
|
||||||
|
buf.push('\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Docs(_) => {
|
Docs(_) => {
|
||||||
panic!("TODO implment docs");
|
panic!("TODO implment docs");
|
||||||
}
|
}
|
||||||
|
Batch(report_texts) => {
|
||||||
|
for report_text in report_texts {
|
||||||
|
report_text.render_ci(buf, subs, home, src_lines, interns);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,24 +11,29 @@ mod helpers;
|
||||||
mod test_report {
|
mod test_report {
|
||||||
use crate::helpers::test_home;
|
use crate::helpers::test_home;
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
use roc_module::symbol::{Interns, ModuleId};
|
||||||
use roc_reporting::report::{Report, ReportText};
|
use roc_reporting::report::{can_problem, plain_text, Report, ReportText};
|
||||||
use roc_types::pretty_print::name_all_type_vars;
|
use roc_types::pretty_print::name_all_type_vars;
|
||||||
use roc_types::subs::Subs;
|
use roc_types::subs::Subs;
|
||||||
use roc_types::types;
|
use roc_types::types;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
// use roc_region::all;
|
// use roc_region::all;
|
||||||
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
||||||
use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value};
|
use roc_reporting::report::ReportText::{Batch, EmText, Region, Type, Url, Value};
|
||||||
use roc_types::subs::Content::{FlexVar, RigidVar, Structure};
|
use roc_types::subs::Content::{FlexVar, RigidVar, Structure};
|
||||||
use roc_types::subs::FlatType::EmptyRecord;
|
use roc_types::subs::FlatType::EmptyRecord;
|
||||||
|
|
||||||
|
fn filename_from_string(str: &str) -> PathBuf {
|
||||||
|
let mut filename = PathBuf::new();
|
||||||
|
filename.push(str);
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
// use roc_problem::can;
|
// use roc_problem::can;
|
||||||
fn to_simple_report(text: ReportText) -> Report {
|
fn to_simple_report(text: ReportText) -> Report {
|
||||||
let mut filename = PathBuf::new();
|
|
||||||
filename.push(r"\code\proj\Main.roc");
|
|
||||||
Report {
|
Report {
|
||||||
text: text,
|
text: text,
|
||||||
filename: filename,
|
filename: filename_from_string(r"\code\proj\Main.roc"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +72,7 @@ mod test_report {
|
||||||
|
|
||||||
fn report_renders_as_from_src(src: &str, report: Report, expected_rendering: &str) {
|
fn report_renders_as_from_src(src: &str, report: Report, expected_rendering: &str) {
|
||||||
let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src);
|
let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src);
|
||||||
let mut buf = String::new();
|
let mut buf: String = String::new();
|
||||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||||
|
|
||||||
report
|
report
|
||||||
|
@ -94,7 +99,7 @@ mod test_report {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn report_plain() {
|
fn report_plain() {
|
||||||
report_renders_as(to_simple_report(Plain(Box::from("y"))), "y");
|
report_renders_as(to_simple_report(plain_text("y")), "y");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -150,6 +155,62 @@ mod test_report {
|
||||||
report_renders_as(to_simple_report(Type(Structure(EmptyRecord))), "{}");
|
report_renders_as(to_simple_report(Type(Structure(EmptyRecord))), "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn report_batch_of_plain_text() {
|
||||||
|
let mut report_texts = Vec::new();
|
||||||
|
|
||||||
|
report_texts.push(plain_text("Wait a second. "));
|
||||||
|
report_texts.push(plain_text("There is a problem here. -> "));
|
||||||
|
report_texts.push(EmText(Box::from("y")));
|
||||||
|
|
||||||
|
report_renders_as(
|
||||||
|
to_simple_report(Batch(report_texts)),
|
||||||
|
"Wait a second. There is a problem here. -> *y*",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn report_unused_def() {
|
||||||
|
let src: &str = indoc!(
|
||||||
|
r#"
|
||||||
|
x = 1
|
||||||
|
y = 2
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src);
|
||||||
|
|
||||||
|
let mut buf: String = String::new();
|
||||||
|
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||||
|
|
||||||
|
match can_problems.first() {
|
||||||
|
None => {}
|
||||||
|
Some(problem) => {
|
||||||
|
let report = can_problem(
|
||||||
|
filename_from_string(r"\code\proj\Main.roc"),
|
||||||
|
problem.clone(),
|
||||||
|
);
|
||||||
|
report
|
||||||
|
.text
|
||||||
|
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buf,
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
y is not used anywhere in your code.
|
||||||
|
|
||||||
|
1 | y = 2
|
||||||
|
|
||||||
|
If you didn't intend on using y then remove it so future readers of your code don't wonder why it is there."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn report_region() {
|
fn report_region() {
|
||||||
report_renders_as_from_src(
|
report_renders_as_from_src(
|
||||||
|
@ -164,7 +225,7 @@ mod test_report {
|
||||||
),
|
),
|
||||||
to_simple_report(Region(roc_region::all::Region {
|
to_simple_report(Region(roc_region::all::Region {
|
||||||
start_line: 1,
|
start_line: 1,
|
||||||
end_line: 4,
|
end_line: 3,
|
||||||
start_col: 0,
|
start_col: 0,
|
||||||
end_col: 0,
|
end_col: 0,
|
||||||
})),
|
})),
|
||||||
|
@ -172,8 +233,7 @@ mod test_report {
|
||||||
r#"
|
r#"
|
||||||
1 | y = 2
|
1 | y = 2
|
||||||
2 | f = \a -> a + 4
|
2 | f = \a -> a + 4
|
||||||
3 |
|
3 |"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -504,8 +504,17 @@ fn type_to_variable(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext_var = type_to_variable(subs, rank, pools, cached, ext);
|
let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext);
|
||||||
let content = Content::Structure(FlatType::Record(field_vars, ext_var));
|
let new_ext_var = match roc_types::pretty_print::chase_ext_record(
|
||||||
|
subs,
|
||||||
|
temp_ext_var,
|
||||||
|
&mut field_vars,
|
||||||
|
) {
|
||||||
|
Ok(()) => Variable::EMPTY_RECORD,
|
||||||
|
Err((new, _)) => new,
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = Content::Structure(FlatType::Record(field_vars, new_ext_var));
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
|
@ -522,8 +531,19 @@ fn type_to_variable(
|
||||||
tag_vars.insert(tag.clone(), tag_argument_vars);
|
tag_vars.insert(tag.clone(), tag_argument_vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext_var = type_to_variable(subs, rank, pools, cached, ext);
|
let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext);
|
||||||
let content = Content::Structure(FlatType::TagUnion(tag_vars, ext_var));
|
let mut ext_tag_vec = Vec::new();
|
||||||
|
let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(
|
||||||
|
subs,
|
||||||
|
temp_ext_var,
|
||||||
|
&mut ext_tag_vec,
|
||||||
|
) {
|
||||||
|
Ok(()) => Variable::EMPTY_TAG_UNION,
|
||||||
|
Err((new, _)) => new,
|
||||||
|
};
|
||||||
|
tag_vars.extend(ext_tag_vec.into_iter());
|
||||||
|
|
||||||
|
let content = Content::Structure(FlatType::TagUnion(tag_vars, new_ext_var));
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
|
@ -540,9 +560,20 @@ fn type_to_variable(
|
||||||
tag_vars.insert(tag.clone(), tag_argument_vars);
|
tag_vars.insert(tag.clone(), tag_argument_vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext_var = type_to_variable(subs, rank, pools, cached, ext);
|
let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext);
|
||||||
|
let mut ext_tag_vec = Vec::new();
|
||||||
|
let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(
|
||||||
|
subs,
|
||||||
|
temp_ext_var,
|
||||||
|
&mut ext_tag_vec,
|
||||||
|
) {
|
||||||
|
Ok(()) => Variable::EMPTY_TAG_UNION,
|
||||||
|
Err((new, _)) => new,
|
||||||
|
};
|
||||||
|
tag_vars.extend(ext_tag_vec.into_iter());
|
||||||
|
|
||||||
let content =
|
let content =
|
||||||
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, ext_var));
|
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var));
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1408,7 +1408,8 @@ mod test_uniq_solve {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn quicksort() {
|
fn quicksort() {
|
||||||
infer_eq(
|
with_larger_debug_stack(|| {
|
||||||
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
quicksort : List (Num a), Int, Int -> List (Num a)
|
quicksort : List (Num a), Int, Int -> List (Num a)
|
||||||
|
@ -1462,6 +1463,7 @@ mod test_uniq_solve {
|
||||||
),
|
),
|
||||||
"Attr Shared (Attr b (List (Attr Shared (Num (Attr c a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr c a)))))"
|
"Attr Shared (Attr b (List (Attr Shared (Num (Attr c a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr c a)))))"
|
||||||
);
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2083,7 +2085,8 @@ mod test_uniq_solve {
|
||||||
reverse
|
reverse
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr a c)))",
|
"Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr b c)))",
|
||||||
|
//"Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr a c)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2113,8 +2116,8 @@ mod test_uniq_solve {
|
||||||
f
|
f
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))",
|
//"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))",
|
||||||
//"Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))"
|
"Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2327,18 +2330,17 @@ mod test_uniq_solve {
|
||||||
Ok (reconstructPath model.cameFrom goal)
|
Ok (reconstructPath model.cameFrom goal)
|
||||||
|
|
||||||
else
|
else
|
||||||
|
modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current }
|
||||||
|
|
||||||
modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current }
|
neighbours = moveFn current
|
||||||
|
|
||||||
neighbours = moveFn current
|
newNeighbours = Set.diff neighbours modelPopped.evaluated
|
||||||
|
|
||||||
newNeighbours = Set.diff neighbours modelPopped.evaluated
|
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
|
||||||
|
|
||||||
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
|
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
|
||||||
|
|
||||||
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
|
astar costFn moveFn goal modelWithCosts
|
||||||
|
|
||||||
astar costFn moveFn goal modelWithCosts
|
|
||||||
|
|
||||||
findPath
|
findPath
|
||||||
"#
|
"#
|
||||||
|
@ -2348,6 +2350,18 @@ mod test_uniq_solve {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn equals() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\a, b -> a == b
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * (a, a -> Attr * Bool)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn instantiated_alias() {
|
fn instantiated_alias() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
|
|
|
@ -436,7 +436,7 @@ fn write_flat_type(
|
||||||
|
|
||||||
buf.push_str(" ]");
|
buf.push_str(" ]");
|
||||||
|
|
||||||
if let Err(content) = ext_content {
|
if let Err((_, content)) = ext_content {
|
||||||
// This is an open tag union, so print the variable
|
// This is an open tag union, so print the variable
|
||||||
// right after the ']'
|
// right after the ']'
|
||||||
//
|
//
|
||||||
|
@ -483,7 +483,7 @@ fn write_flat_type(
|
||||||
|
|
||||||
buf.push_str(" ]");
|
buf.push_str(" ]");
|
||||||
|
|
||||||
if let Err(content) = ext_content {
|
if let Err((_, content)) = ext_content {
|
||||||
// This is an open tag union, so print the variable
|
// This is an open tag union, so print the variable
|
||||||
// right after the ']'
|
// right after the ']'
|
||||||
//
|
//
|
||||||
|
@ -508,7 +508,7 @@ pub fn chase_ext_tag_union(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
fields: &mut Vec<(TagName, Vec<Variable>)>,
|
fields: &mut Vec<(TagName, Vec<Variable>)>,
|
||||||
) -> Result<(), Content> {
|
) -> Result<(), (Variable, Content)> {
|
||||||
use FlatType::*;
|
use FlatType::*;
|
||||||
match subs.get_without_compacting(var).content {
|
match subs.get_without_compacting(var).content {
|
||||||
Content::Structure(EmptyTagUnion) => Ok(()),
|
Content::Structure(EmptyTagUnion) => Ok(()),
|
||||||
|
@ -521,7 +521,7 @@ pub fn chase_ext_tag_union(
|
||||||
chase_ext_tag_union(subs, ext_var, fields)
|
chase_ext_tag_union(subs, ext_var, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
content => Err(content),
|
content => Err((var, content)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,7 +529,7 @@ pub fn chase_ext_record(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
fields: &mut MutMap<Lowercase, Variable>,
|
fields: &mut MutMap<Lowercase, Variable>,
|
||||||
) -> Result<(), Content> {
|
) -> Result<(), (Variable, Content)> {
|
||||||
use crate::subs::Content::*;
|
use crate::subs::Content::*;
|
||||||
use crate::subs::FlatType::*;
|
use crate::subs::FlatType::*;
|
||||||
|
|
||||||
|
@ -544,7 +544,7 @@ pub fn chase_ext_record(
|
||||||
|
|
||||||
Alias(_, _, var) => chase_ext_record(subs, var, fields),
|
Alias(_, _, var) => chase_ext_record(subs, var, fields),
|
||||||
|
|
||||||
content => Err(content),
|
content => Err((var, content)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -639,7 +639,7 @@ impl ContentHash {
|
||||||
if *ext != Variable::EMPTY_RECORD {
|
if *ext != Variable::EMPTY_RECORD {
|
||||||
let mut fields_map = MutMap::default();
|
let mut fields_map = MutMap::default();
|
||||||
match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields_map) {
|
match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields_map) {
|
||||||
Err(Content::FlexVar(_)) | Ok(()) => {
|
Err((_, Content::FlexVar(_))) | Ok(()) => {
|
||||||
if !fields_map.is_empty() {
|
if !fields_map.is_empty() {
|
||||||
extracted_fields_from_ext = true;
|
extracted_fields_from_ext = true;
|
||||||
fields.extend(fields_map.into_iter());
|
fields.extend(fields_map.into_iter());
|
||||||
|
@ -688,7 +688,7 @@ impl ContentHash {
|
||||||
let mut extracted_fields_from_ext = false;
|
let mut extracted_fields_from_ext = false;
|
||||||
if *ext != Variable::EMPTY_TAG_UNION {
|
if *ext != Variable::EMPTY_TAG_UNION {
|
||||||
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
|
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
|
||||||
Err(Content::FlexVar(_)) | Ok(()) => {
|
Err((_, Content::FlexVar(_))) | Ok(()) => {
|
||||||
extracted_fields_from_ext = !tag_vec.is_empty();
|
extracted_fields_from_ext = !tag_vec.is_empty();
|
||||||
}
|
}
|
||||||
Err(content) => panic!("TagUnion with unexpected ext_var: {:?}", content),
|
Err(content) => panic!("TagUnion with unexpected ext_var: {:?}", content),
|
||||||
|
@ -737,7 +737,7 @@ impl ContentHash {
|
||||||
let mut extracted_fields_from_ext = false;
|
let mut extracted_fields_from_ext = false;
|
||||||
if *ext != Variable::EMPTY_TAG_UNION {
|
if *ext != Variable::EMPTY_TAG_UNION {
|
||||||
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
|
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
|
||||||
Err(Content::FlexVar(_)) | Ok(()) => {
|
Err((_, Content::FlexVar(_))) | Ok(()) => {
|
||||||
extracted_fields_from_ext = !tag_vec.is_empty();
|
extracted_fields_from_ext = !tag_vec.is_empty();
|
||||||
}
|
}
|
||||||
Err(content) => {
|
Err(content) => {
|
||||||
|
|
|
@ -19,6 +19,45 @@ macro_rules! mismatch {
|
||||||
}
|
}
|
||||||
vec![Mismatch::TypeMismatch]
|
vec![Mismatch::TypeMismatch]
|
||||||
}};
|
}};
|
||||||
|
($msg:expr) => {{
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!(
|
||||||
|
"Mismatch in {} Line {} Column {}",
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!($msg);
|
||||||
|
println!("");
|
||||||
|
vec![Mismatch::TypeMismatch]
|
||||||
|
}};
|
||||||
|
($msg:expr,) => {{
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!(
|
||||||
|
"Mismatch in {} Line {} Column {}",
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!($msg);
|
||||||
|
println!("");
|
||||||
|
vec![Mismatch::TypeMismatch]
|
||||||
|
}};
|
||||||
|
($msg:expr, $($arg:tt)*) => {{
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!(
|
||||||
|
"Mismatch in {} Line {} Column {}",
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!($msg, $($arg)*);
|
||||||
|
println!("");
|
||||||
|
vec![Mismatch::TypeMismatch]
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pool = Vec<Variable>;
|
type Pool = Vec<Variable>;
|
||||||
|
@ -154,9 +193,9 @@ fn unify_structure(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RigidVar(_) => {
|
RigidVar(name) => {
|
||||||
// Type mismatch! Rigid can only unify with flex.
|
// Type mismatch! Rigid can only unify with flex.
|
||||||
mismatch!()
|
mismatch!("trying to unify {:?} with rigid var {:?}", &flat_type, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Structure(ref other_flat_type) => {
|
Structure(ref other_flat_type) => {
|
||||||
|
@ -281,7 +320,15 @@ fn unify_shared_fields(
|
||||||
}
|
}
|
||||||
|
|
||||||
if num_shared_fields == matching_fields.len() {
|
if num_shared_fields == matching_fields.len() {
|
||||||
let flat_type = FlatType::Record(union(matching_fields, &other_fields), ext);
|
// pull fields in from the ext_var
|
||||||
|
let mut fields = union(matching_fields, &other_fields);
|
||||||
|
|
||||||
|
let new_ext_var = match roc_types::pretty_print::chase_ext_record(subs, ext, &mut fields) {
|
||||||
|
Ok(()) => Variable::EMPTY_RECORD,
|
||||||
|
Err((new, _)) => new,
|
||||||
|
};
|
||||||
|
|
||||||
|
let flat_type = FlatType::Record(fields, new_ext_var);
|
||||||
|
|
||||||
merge(subs, ctx, Structure(flat_type))
|
merge(subs, ctx, Structure(flat_type))
|
||||||
} else {
|
} else {
|
||||||
|
@ -421,10 +468,21 @@ fn unify_shared_tags(
|
||||||
}
|
}
|
||||||
|
|
||||||
if num_shared_tags == matching_tags.len() {
|
if num_shared_tags == matching_tags.len() {
|
||||||
|
// merge fields from the ext_var into this tag union
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(subs, ext, &mut fields)
|
||||||
|
{
|
||||||
|
Ok(()) => Variable::EMPTY_TAG_UNION,
|
||||||
|
Err((new, _)) => new,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_tags = union(matching_tags, &other_tags);
|
||||||
|
new_tags.extend(fields.into_iter());
|
||||||
|
|
||||||
let flat_type = if let Some(rec) = recursion_var {
|
let flat_type = if let Some(rec) = recursion_var {
|
||||||
FlatType::RecursiveTagUnion(rec, union(matching_tags, &other_tags), ext)
|
FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var)
|
||||||
} else {
|
} else {
|
||||||
FlatType::TagUnion(union(matching_tags, &other_tags), ext)
|
FlatType::TagUnion(new_tags, new_ext_var)
|
||||||
};
|
};
|
||||||
|
|
||||||
merge(subs, ctx, Structure(flat_type))
|
merge(subs, ctx, Structure(flat_type))
|
||||||
|
@ -552,11 +610,11 @@ fn unify_flat_type(
|
||||||
problems
|
problems
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_other1, _other2) => {
|
(other1, other2) => mismatch!(
|
||||||
// Can't unify other1 and other2
|
"Trying to two flat types that are incompatible: {:?} ~ {:?}",
|
||||||
// dbg!(&_other1, &_other2);
|
other1,
|
||||||
mismatch!()
|
other2
|
||||||
}
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -632,7 +632,7 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) {
|
||||||
annotate_usage(&loc_expr.value, usage);
|
annotate_usage(&loc_expr.value, usage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Record(_, fields) => {
|
Record { fields, .. } => {
|
||||||
for (_, field) in fields {
|
for (_, field) in fields {
|
||||||
annotate_usage(&field.loc_expr.value, usage);
|
annotate_usage(&field.loc_expr.value, usage);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue