make a Index data type

This commit is contained in:
Folkert 2020-04-11 20:40:25 +02:00
parent ab19529077
commit 2811f978a4
8 changed files with 441 additions and 158 deletions

View File

@ -93,3 +93,40 @@ where
map
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Index(usize);
impl Index {
pub const FIRST: Self = Index(0);
pub fn zero_based(i: usize) -> Self {
Index(i)
}
pub fn one_based(i: usize) -> Self {
Index(i - 1)
}
pub fn ordinal(self) -> std::string::String {
int_to_ordinal(self.0 + 1)
}
}
fn int_to_ordinal(number: usize) -> std::string::String {
// NOTE: one-based
let remainder10 = number % 10;
let remainder100 = number % 100;
let ending = match remainder100 {
11..=13 => "th",
_ => match remainder10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
},
};
format!("{}{}", number, ending)
}

View File

@ -9,7 +9,7 @@ use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{Field, WhenBranch};
use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, SendMap};
use roc_collections::all::{ImMap, Index, SendMap};
use roc_module::ident::Lowercase;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
@ -156,7 +156,7 @@ pub fn constrain_expr(
cons.push(con);
}
let fields_type = Type::Record(fields.clone(), Box::new(Type::Variable(*ext_var)));
let fields_type = Type::Record(fields, Box::new(Type::Variable(*ext_var)));
let record_type = Type::Variable(*record_var);
// NOTE from elm compiler: fields_type is separate so that Error propagates better
@ -211,7 +211,9 @@ pub fn constrain_expr(
for (index, loc_elem) in loc_elems.iter().enumerate() {
let elem_expected = ForReason(
Reason::ElemInList { index },
Reason::ElemInList {
index: Index::zero_based(index),
},
list_elem_type.clone(),
loc_elem.region,
);
@ -270,7 +272,7 @@ pub fn constrain_expr(
let reason = Reason::FnArg {
name: opt_symbol,
arg_index: index as u8,
arg_index: Index::zero_based(index),
};
let expected_arg = ForReason(reason, arg_type.clone(), region);
let arg_con = constrain_expr(env, loc_arg.region, &loc_arg.value, expected_arg);
@ -401,7 +403,10 @@ pub fn constrain_expr(
FromAnnotation(
name.clone(),
arity,
AnnotationSource::TypedIfBranch(index + 1),
AnnotationSource::TypedIfBranch {
index: Index::zero_based(index),
num_branches: branches.len(),
},
tipe.clone(),
),
);
@ -416,7 +421,10 @@ pub fn constrain_expr(
FromAnnotation(
name,
arity,
AnnotationSource::TypedIfBranch(branches.len() + 1),
AnnotationSource::TypedIfBranch {
index: Index::zero_based(branches.len()),
num_branches: branches.len(),
},
tipe.clone(),
),
);
@ -448,7 +456,7 @@ pub fn constrain_expr(
&loc_body.value,
ForReason(
Reason::IfBranch {
index: index + 1,
index: Index::zero_based(index),
total_branches: branches.len(),
},
Type::Variable(*branch_var),
@ -465,7 +473,7 @@ pub fn constrain_expr(
&final_else.value,
ForReason(
Reason::IfBranch {
index: branches.len() + 1,
index: Index::zero_based(branches.len()),
total_branches: branches.len() + 1,
},
Type::Variable(*branch_var),
@ -521,14 +529,16 @@ pub fn constrain_expr(
when_branch.value.region,
when_branch,
PExpected::ForReason(
PReason::WhenMatch { index },
PReason::WhenMatch {
index: Index::zero_based(index),
},
cond_type.clone(),
Region::across_all(when_branch.patterns.iter().map(|v| &v.region)),
),
FromAnnotation(
name.clone(),
*arity,
TypedWhenBranch(index + 1),
TypedWhenBranch(Index::zero_based(index)),
typ.clone(),
),
);
@ -547,12 +557,16 @@ pub fn constrain_expr(
region,
when_branch,
PExpected::ForReason(
PReason::WhenMatch { index },
PReason::WhenMatch {
index: Index::zero_based(index),
},
cond_type.clone(),
Region::across_all(when_branch.patterns.iter().map(|v| &v.region)),
),
ForReason(
Reason::WhenBranch { index },
Reason::WhenBranch {
index: Index::zero_based(index),
},
branch_type.clone(),
when_branch.value.region,
),

View File

@ -3,7 +3,7 @@ use roc_can::constraint::Constraint;
use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::RecordDestruct;
use roc_collections::all::SendMap;
use roc_collections::all::{Index, SendMap};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
@ -256,7 +256,7 @@ pub fn constrain_pattern(
let expected = PExpected::ForReason(
PReason::TagArg {
tag_name: tag_name.clone(),
index,
index: Index::zero_based(index),
},
pattern_type,
region,

View File

@ -6,7 +6,7 @@ use roc_can::def::{Declaration, Def};
use roc_can::expected::{Expected, PExpected};
use roc_can::expr::{Expr, Field, WhenBranch};
use roc_can::pattern::{Pattern, RecordDestruct};
use roc_collections::all::{ImMap, ImSet, SendMap};
use roc_collections::all::{ImMap, ImSet, Index, SendMap};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
@ -598,7 +598,9 @@ pub fn constrain_expr(
for (index, loc_elem) in loc_elems.iter().enumerate() {
let elem_expected = Expected::ForReason(
Reason::ElemInList { index },
Reason::ElemInList {
index: Index::zero_based(index),
},
entry_type.clone(),
region,
);
@ -755,7 +757,7 @@ pub fn constrain_expr(
let reason = Reason::FnArg {
name: opt_symbol,
arg_index: index as u8,
arg_index: Index::zero_based(index),
};
let expected_arg = Expected::ForReason(reason, arg_type.clone(), region);
@ -936,7 +938,10 @@ pub fn constrain_expr(
Expected::FromAnnotation(
name.clone(),
arity,
AnnotationSource::TypedIfBranch(index + 1),
AnnotationSource::TypedIfBranch {
index: Index::zero_based(index),
num_branches: branches.len(),
},
tipe.clone(),
),
);
@ -954,7 +959,10 @@ pub fn constrain_expr(
Expected::FromAnnotation(
name,
arity,
AnnotationSource::TypedIfBranch(branches.len() + 1),
AnnotationSource::TypedIfBranch {
index: Index::zero_based(branches.len()),
num_branches: branches.len(),
},
tipe.clone(),
),
);
@ -1003,7 +1011,7 @@ pub fn constrain_expr(
&loc_body.value,
Expected::ForReason(
Reason::IfBranch {
index: index + 1,
index: Index::zero_based(index),
total_branches: branches.len(),
},
Type::Variable(*branch_var),
@ -1023,7 +1031,7 @@ pub fn constrain_expr(
&final_else.value,
Expected::ForReason(
Reason::IfBranch {
index: branches.len() + 1,
index: Index::zero_based(branches.len()),
total_branches: branches.len(),
},
Type::Variable(*branch_var),
@ -1085,14 +1093,16 @@ pub fn constrain_expr(
region,
when_branch,
PExpected::ForReason(
PReason::WhenMatch { index },
PReason::WhenMatch {
index: Index::zero_based(index),
},
cond_type.clone(),
region,
),
Expected::FromAnnotation(
name.clone(),
*arity,
TypedWhenBranch(index),
TypedWhenBranch(Index::zero_based(index)),
typ.clone(),
),
);
@ -1118,12 +1128,16 @@ pub fn constrain_expr(
region,
when_branch,
PExpected::ForReason(
PReason::WhenMatch { index },
PReason::WhenMatch {
index: Index::zero_based(index),
},
cond_type.clone(),
region,
),
Expected::ForReason(
Reason::WhenBranch { index },
Reason::WhenBranch {
index: Index::zero_based(index),
},
branch_type.clone(),
region,
),
@ -1176,7 +1190,7 @@ pub fn constrain_expr(
let fields_type = attr_type(
Bool::variable(uniq_var),
Type::Record(fields.clone(), Box::new(Type::Variable(*ext_var))),
Type::Record(fields, Box::new(Type::Variable(*ext_var))),
);
let record_type = Type::Variable(*record_var);

View File

@ -612,6 +612,7 @@ define_builtins! {
8 FLOAT_ADD: "#add"
9 FLOAT_SUB: "#sub"
10 FLOAT_EQ: "#eq"
11 FLOAT_ROUND: "round"
}
4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View File

@ -1,5 +1,5 @@
use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap};
use roc_collections::all::{Index, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_solve::solve;
@ -32,24 +32,6 @@ pub fn type_problem<'b>(
}
}
fn int_to_ordinal(number: usize) -> String {
// NOTE: one-based
let remainder10 = number % 10;
let remainder100 = number % 100;
let ending = match remainder100 {
11..=13 => "th",
_ => match remainder10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
},
};
format!("{}{}", number, ending)
}
#[allow(clippy::too_many_arguments)]
fn report_mismatch<'b>(
alloc: &'b RocDocAllocator<'b>,
@ -162,22 +144,34 @@ fn to_expr_report<'b>(
let (the_name_text, on_name_text) = match pattern_to_doc(alloc, &name.value) {
Some(doc) => (
alloc.concat(vec![alloc.text("the "), doc.clone()]),
alloc.concat(vec![alloc.text(" on "), doc]),
alloc.concat(vec![alloc.reflow("the "), doc.clone()]),
alloc.concat(vec![alloc.reflow(" on "), doc]),
),
None => (alloc.text("this"), alloc.nil()),
};
// TODO special-case 2-branch if
let thing = match annotation_source {
TypedIfBranch(index) => alloc.concat(vec![
alloc.string(format!("{}", int_to_ordinal(index))),
TypedIfBranch {
index,
num_branches,
} if num_branches == 2 => alloc.concat(vec![
alloc.keyword(if index == Index::FIRST {
"then"
} else {
"else"
}),
alloc.reflow(" branch of this "),
alloc.keyword("if"),
alloc.text(" expression:"),
]),
TypedIfBranch { index, .. } => alloc.concat(vec![
alloc.string(index.ordinal()),
alloc.reflow(" branch of this "),
alloc.keyword("if"),
alloc.text(" expression:"),
]),
TypedWhenBranch(index) => alloc.concat(vec![
alloc.string(format!("{}", int_to_ordinal(index))),
alloc.string(index.ordinal()),
alloc.reflow(" branch of this "),
alloc.keyword("when"),
alloc.text(" expression:"),
@ -190,8 +184,8 @@ fn to_expr_report<'b>(
};
let it_is = match annotation_source {
TypedIfBranch(index) => format!("The {} branch is", int_to_ordinal(index)),
TypedWhenBranch(index) => format!("The {} branch is", int_to_ordinal(index)),
TypedIfBranch { index, .. } => format!("The {} branch is", index.ordinal()),
TypedWhenBranch(index) => format!("The {} branch is", index.ordinal()),
TypedBody => "The body is".into(),
};
@ -325,39 +319,7 @@ fn to_expr_report<'b>(
alloc.text(" to have the same type!"),
])),
),
_ => {
let ith = int_to_ordinal(index);
report_mismatch(
alloc,
filename,
&category,
found,
expected_type,
region,
Some(expr_region),
alloc.concat(vec![
alloc.reflow("The "),
alloc.string(format!("{}", ith)),
alloc.reflow(" branch of this "),
alloc.keyword("if"),
alloc.reflow(" does not match all the previous branches:"),
]),
alloc.string(format!("The {} branch is", ith)),
alloc.reflow("But all the previous branches have type:"),
Some(alloc.concat(vec![
alloc.reflow("I need all branches in an "),
alloc.keyword("if"),
alloc.reflow(" to have the same type!"),
])),
)
}
},
Reason::WhenBranch { index } => {
// NOTE: is 0-based
let ith = int_to_ordinal(index + 1);
report_mismatch(
_ => report_mismatch(
alloc,
filename,
&category,
@ -367,28 +329,49 @@ fn to_expr_report<'b>(
Some(expr_region),
alloc.concat(vec![
alloc.reflow("The "),
alloc.string(format!("{}", ith)),
alloc.string(index.ordinal()),
alloc.reflow(" branch of this "),
alloc.keyword("when"),
alloc.keyword("if"),
alloc.reflow(" does not match all the previous branches:"),
]),
alloc.concat(vec![
alloc.reflow("The "),
alloc.string(format!("{}", ith)),
alloc.reflow(" branch is"),
]),
alloc.string(format!("The {} branch is", index.ordinal())),
alloc.reflow("But all the previous branches have type:"),
Some(alloc.concat(vec![
alloc.reflow("I need all branches of a "),
alloc.keyword("when"),
alloc.reflow("I need all branches in an "),
alloc.keyword("if"),
alloc.reflow(" to have the same type!"),
])),
)
}
),
},
Reason::WhenBranch { index } => report_mismatch(
alloc,
filename,
&category,
found,
expected_type,
region,
Some(expr_region),
alloc.concat(vec![
alloc.reflow("The "),
alloc.string(index.ordinal()),
alloc.reflow(" branch of this "),
alloc.keyword("when"),
alloc.reflow(" does not match all the previous branches:"),
]),
alloc.concat(vec![
alloc.reflow("The "),
alloc.string(index.ordinal()),
alloc.reflow(" branch is"),
]),
alloc.reflow("But all the previous branches have type:"),
Some(alloc.concat(vec![
alloc.reflow("I need all branches of a "),
alloc.keyword("when"),
alloc.reflow(" to have the same type!"),
])),
),
Reason::ElemInList { index } => {
// NOTE: is 0-based
let ith = int_to_ordinal(index + 1);
let ith = index.ordinal();
report_mismatch(
alloc,
@ -438,13 +421,9 @@ fn to_expr_report<'b>(
let expected_set: MutSet<_> = expected_fields.keys().cloned().collect();
let actual_set: MutSet<_> = actual_fields.keys().cloned().collect();
let diff = expected_set.difference(&actual_set);
let mut diff = expected_set.difference(&actual_set);
match diff
.into_iter()
.next()
.and_then(|k| Some((k, expected_fields.get(k)?)))
{
match diff.next().and_then(|k| Some((k, expected_fields.get(k)?))) {
None => report_mismatch(
alloc,
filename,
@ -464,7 +443,7 @@ fn to_expr_report<'b>(
),
Some((field, field_region)) => {
let r_doc = alloc.symbol_unqualified(symbol);
let f_doc = alloc.record_field(field.clone().clone());
let f_doc = alloc.record_field(field.clone());
let header = alloc.concat(vec![
alloc.reflow("The "),
@ -632,7 +611,7 @@ fn to_expr_report<'b>(
}
},
Reason::FnArg { name, arg_index } => {
let ith = int_to_ordinal(arg_index as usize + 1);
let ith = arg_index.ordinal();
let this_function = match name {
None => alloc.text("this function"),
@ -661,17 +640,12 @@ fn to_expr_report<'b>(
None,
)
}
other => {
// NamedFnArg(String /* function name */, u8 /* arg index */),
// AnonymousFnCall { arity: u8 },
// NamedFnCall(String /* function name */, u8 /* arity */),
// BinOpArg(BinOp, ArgSide),
// BinOpRet(BinOp),
// FloatLiteral,
// IntLiteral,
// NumLiteral,
// InterpolatedStringVar,
todo!("I don't have a message yet for reason {:?}", other)
Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => {
unreachable!("I don't think these can be reached")
}
Reason::InterpolatedStringVar => {
unimplemented!("string interpolation is not implemented yet")
}
},
}
@ -836,7 +810,7 @@ fn to_pattern_report<'b>(
PExpected::ForReason(reason, expected_type, region) => match reason {
PReason::WhenMatch { index } => {
if index == 0 {
if index == Index::FIRST {
let doc = alloc.stack(vec![
alloc
.text("The 1st pattern in this ")
@ -871,10 +845,7 @@ fn to_pattern_report<'b>(
} else {
let doc = alloc.stack(vec![
alloc
.string(format!(
"The {} pattern in this ",
int_to_ordinal(index + 1)
))
.string(format!("The {} pattern in this ", index.ordinal()))
.append(alloc.keyword("when"))
.append(alloc.text(" does not match the previous ones:")),
alloc.region(region),
@ -886,7 +857,7 @@ fn to_pattern_report<'b>(
alloc,
alloc.string(format!(
"The {} pattern is trying to match",
int_to_ordinal(index + 1)
index.ordinal()
)),
&category,
),
@ -902,11 +873,8 @@ fn to_pattern_report<'b>(
}
}
}
PReason::TagArg { .. } => {
panic!("I didn't think this could trigger. Please tell Folkert about it!")
}
PReason::PatternGuard => {
todo!("Blocked on https://github.com/rtfeldman/roc/issues/304")
PReason::TagArg { .. } | PReason::PatternGuard => {
unreachable!("I didn't think this could trigger. Please tell Folkert about it!")
}
},
}
@ -943,19 +911,19 @@ fn add_pattern_category<'b>(
use PatternCategory::*;
let rest = match category {
Record => alloc.text(" record values of type:"),
EmptyRecord => alloc.text(" an empty record:"),
PatternGuard => alloc.text(" a pattern guard of type:"),
Set => alloc.text(" sets of type:"),
Map => alloc.text(" maps of type:"),
Record => alloc.reflow(" record values of type:"),
EmptyRecord => alloc.reflow(" an empty record:"),
PatternGuard => alloc.reflow(" a pattern guard of type:"),
Set => alloc.reflow(" sets of type:"),
Map => alloc.reflow(" maps of type:"),
Ctor(tag_name) => alloc.concat(vec![
alloc.tag_name(tag_name.clone()),
alloc.text(" values of type:"),
alloc.reflow(" values of type:"),
]),
Str => alloc.text(" strings:"),
Num => alloc.text(" numbers:"),
Int => alloc.text(" integers:"),
Float => alloc.text(" floats"),
Str => alloc.reflow(" strings:"),
Num => alloc.reflow(" numbers:"),
Int => alloc.reflow(" integers:"),
Float => alloc.reflow(" floats"),
};
alloc.concat(vec![i_am_trying_to_match, rest])
@ -972,7 +940,6 @@ fn to_circular_report<'b>(
title: "CIRCULAR TYPE".to_string(),
filename,
doc: {
let line = r#"Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely."#;
alloc.stack(vec![
alloc
.reflow("I'm inferring a weird self-referential type for ")
@ -980,7 +947,11 @@ fn to_circular_report<'b>(
.append(alloc.text(":")),
alloc.region(region),
alloc.stack(vec![
alloc.reflow(line),
alloc.reflow(
"Here is my best effort at writing down the type. \
You will see for parts of the type that repeat \
something already printed out infinitely.",
),
alloc.type_block(to_doc(alloc, Parens::Unnecessary, overall_type)),
]),
])
@ -994,8 +965,6 @@ pub enum Problem {
ArityMismatch(usize, usize),
FieldTypo(Lowercase, Vec<Lowercase>),
FieldsMissing(Vec<Lowercase>),
// TODO maybe these should include the arguments too?
TagTypo(TagName, Vec<TagName>),
TagsMissing(Vec<TagName>),
BadRigidVar(Lowercase, ErrorType),
@ -1375,8 +1344,34 @@ fn to_diff<'b>(
let left = to_doc(alloc, Parens::Unnecessary, type1);
let right = to_doc(alloc, Parens::Unnecessary, type2);
let is_int = |t: &ErrorType| match t {
ErrorType::Type(Symbol::INT_INT, _) => true,
ErrorType::Alias(Symbol::INT_INT, _, _) => true,
ErrorType::Type(Symbol::NUM_NUM, args) => match &args.get(0) {
Some(ErrorType::Type(Symbol::INT_INTEGER, _)) => true,
Some(ErrorType::Alias(Symbol::INT_INTEGER, _, _)) => true,
_ => false,
},
ErrorType::Alias(Symbol::NUM_NUM, args, _) => match &args.get(0) {
Some((_, ErrorType::Type(Symbol::INT_INTEGER, _))) => true,
Some((_, ErrorType::Alias(Symbol::INT_INTEGER, _, _))) => true,
_ => false,
},
_ => false,
};
let is_float = |t: &ErrorType| match t {
ErrorType::Type(Symbol::FLOAT_FLOAT, _) => true,
ErrorType::Alias(Symbol::FLOAT_FLOAT, _, _) => true,
_ => false,
};
let problems = match pair {
(RigidVar(x), other) | (other, RigidVar(x)) => vec![Problem::BadRigidVar(x, other)],
(a, b) if (is_int(&a) && is_float(&b)) || (is_float(&a) && is_int(&b)) => {
vec![Problem::IntFloat]
}
_ => vec![],
};
@ -2148,7 +2143,58 @@ fn type_problem_to_pretty<'b>(
Boolean(_) => bad_rigid_var(x, alloc.reflow("a uniqueness attribute value")),
}
}
IntFloat => alloc.hint().append(alloc.concat(vec![
alloc.reflow("Convert between "),
alloc.type_str("Int"),
alloc.reflow(" and "),
alloc.type_str("Float"),
alloc.reflow(" with "),
alloc.symbol_qualified(Symbol::NUM_TO_FLOAT),
alloc.reflow(" and "),
alloc.symbol_qualified(Symbol::FLOAT_ROUND),
alloc.reflow("."),
])),
_ => todo!(),
TagsMissing(missing) => match missing.split_last() {
None => alloc.nil(),
Some((f1, [])) => {
let hint1 = alloc
.hint()
.append(alloc.reflow("Looks like a closed tag union does not have the "))
.append(alloc.tag_name(f1.clone()))
.append(alloc.reflow(" tag."));
let hint2 = alloc.hint().append(alloc.reflow(
"Closed tag unions can't grow, \
because that might change the size in memory. \
Can you use an open tag union?",
));
alloc.stack(vec![hint1, hint2])
}
Some((last, init)) => {
let separator = alloc.reflow(", ");
let hint1 = alloc
.hint()
.append(alloc.reflow("Looks like a closed tag union does not have the "))
.append(
alloc
.intersperse(init.iter().map(|v| alloc.tag_name(v.clone())), separator),
)
.append(alloc.reflow(" and "))
.append(alloc.tag_name(last.clone()))
.append(alloc.reflow(" tags."));
let hint2 = alloc.hint().append(alloc.reflow(
"Closed tag unions can't grow, \
because that might change the size in memory. \
Can you use an open tag union?",
));
alloc.stack(vec![hint1, hint2])
}
},
}
}

View File

@ -1067,6 +1067,8 @@ mod test_reporting {
But the type annotation on `x` says it should be:
Int
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`.
"#
),
)
@ -1101,6 +1103,8 @@ mod test_reporting {
But the type annotation on `x` says it should be:
Int
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`.
"#
),
)
@ -1133,6 +1137,8 @@ mod test_reporting {
But the type annotation on `x` says it should be:
Int -> Int
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`.
"#
),
)
@ -1461,6 +1467,8 @@ mod test_reporting {
But the type annotation says it should be:
{ x : Int }
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`.
"#
),
)
@ -1883,4 +1891,155 @@ mod test_reporting {
),
)
}
#[test]
fn plus_on_str() {
report_problem_as(
indoc!(
r#"
0x4 + "foo"
"#
),
// TODO also suggest fields with the correct type
indoc!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
The 2nd argument to `add` is not what I expect:
1 0x4 + "foo"
^^^^^
This argument is a string of type:
Str
But `add` needs the 2nd argument to be:
Num Integer
"#
),
)
}
#[test]
fn int_float() {
report_problem_as(
indoc!(
r#"
0x4 + 3.14
"#
),
indoc!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
The 2nd argument to `add` is not what I expect:
1 0x4 + 3.14
^^^^
This argument is a float of type:
Float
But `add` needs the 2nd argument to be:
Num Integer
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`.
"#
),
)
}
#[test]
fn tag_missing() {
report_problem_as(
indoc!(
r#"
f : [ A ] -> [ A, B ]
f = \a -> a
f
"#
),
indoc!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
Something is off with the body of the `f` definition:
2 f = \a -> a
^^^^^^^
The body is an anonymous function of type:
[ A ] -> [ A ]
But the type annotation on `f` says it should be:
[ A ] -> [ A, B ]
Hint: Looks like a closed tag union does not have the `B` tag.
Hint: Closed tag unions can't grow, because that might change the size
in memory. Can you use an open tag union?
"#
),
)
}
#[test]
fn tags_missing() {
report_problem_as(
indoc!(
r#"
f : [ A ] -> [ A, B, C ]
f = \a -> a
f
"#
),
indoc!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
Something is off with the body of the `f` definition:
2 f = \a -> a
^^^^^^^
The body is an anonymous function of type:
[ A ] -> [ A ]
But the type annotation on `f` says it should be:
[ A ] -> [ A, B, C ]
Hint: Looks like a closed tag union does not have the `C` and `B` tags.
Hint: Closed tag unions can't grow, because that might change the size
in memory. Can you use an open tag union?
"#
),
)
}
#[test]
fn open_tag_union_can_grow() {
report_problem_as(
indoc!(
r#"
f : [ A ]* -> [ A, B, C ]
f = \a -> a
f
"#
),
// should not give errors
indoc!(""),
)
}
}

View File

@ -2,10 +2,9 @@ use crate::boolean_algebra;
use crate::pretty_print::Parens;
use crate::subs::{Subs, VarStore, Variable};
use inlinable_string::InlinableString;
use roc_collections::all::{union, ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_collections::all::{union, ImMap, ImSet, Index, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_parse::operator::{ArgSide, BinOp};
use roc_region::all::{Located, Region};
use std::fmt;
@ -598,33 +597,46 @@ pub struct RecordStructure {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PReason {
WhenMatch { index: usize },
TagArg { tag_name: TagName, index: usize },
WhenMatch { index: Index },
TagArg { tag_name: TagName, index: Index },
PatternGuard,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnnotationSource {
TypedIfBranch(usize /* index */),
TypedWhenBranch(usize /* index */),
TypedIfBranch { index: Index, num_branches: usize },
TypedWhenBranch(Index),
TypedBody,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Reason {
FnArg { name: Option<Symbol>, arg_index: u8 },
FnCall { name: Option<Symbol>, arity: u8 },
BinOpArg(BinOp, ArgSide),
BinOpRet(BinOp),
FnArg {
name: Option<Symbol>,
arg_index: Index,
},
FnCall {
name: Option<Symbol>,
arity: u8,
},
// BinOpArg(BinOp, ArgSide),
// BinOpRet(BinOp),
FloatLiteral,
IntLiteral,
NumLiteral,
InterpolatedStringVar,
WhenBranch { index: usize },
WhenBranch {
index: Index,
},
WhenGuard,
IfCondition,
IfBranch { index: usize, total_branches: usize },
ElemInList { index: usize },
IfBranch {
index: Index,
total_branches: usize,
},
ElemInList {
index: Index,
},
RecordUpdateValue(Lowercase),
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),
}