Merge remote-tracking branch 'origin/trunk' into array-wrappers

This commit is contained in:
Richard Feldman 2020-03-14 20:46:15 -04:00
commit a54db8bf92
22 changed files with 1095 additions and 361 deletions

View file

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

View file

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

View file

@ -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();
let field_var = var_store.fresh();
let field_name: Lowercase = (*field).into();
(
Accessor { Accessor {
field: field_name, record_var: var_store.fresh(),
ext_var, ext_var: var_store.fresh(),
field_var, field_var: var_store.fresh(),
field: (*field).into(),
}, },
Output::default(), 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();

View file

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

View file

@ -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],
And(vec![
Eq( Eq(
Type::Function(vec![record_type], Box::new(field_type)), Type::Function(vec![record_type], Box::new(field_type)),
expected, expected,
region, region,
), ),
record_con,
]),
) )
} }
LetRec(defs, loc_ret, var, aliases) => { LetRec(defs, loc_ret, var, aliases) => {

View file

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

View file

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

View file

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

View file

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

View file

@ -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!(

View file

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

View file

@ -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,7 +631,8 @@ 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 =
match Layout::from_var(arena, record_var, env.subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid field! // Invalid field!
@ -635,14 +681,16 @@ 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 =
match Layout::from_var(arena, record_var, env.subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid field! // Invalid field!
@ -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)),
},
}
}

View file

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

View file

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

View file

@ -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]);
} }
if i != region.end_line {
buf.push('\n'); 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);
}
}
} }
} }

View file

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

View file

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

View file

@ -1408,6 +1408,7 @@ mod test_uniq_solve {
#[test] #[test]
fn quicksort() { fn quicksort() {
with_larger_debug_stack(|| {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
@ -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,7 +2330,6 @@ 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
@ -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(

View file

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

View file

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

View file

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

View file

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