mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
Merge pull request #2054 from rtfeldman/i/1804-inference-var
Implement type checking in the presence of `_` inference variables
This commit is contained in:
commit
22adbe0bce
5 changed files with 335 additions and 2 deletions
|
@ -2492,4 +2492,121 @@ pub mod test_constrain {
|
||||||
"custom",
|
"custom",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_inside_arrow() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
id : _ -> _
|
||||||
|
id = \x -> x
|
||||||
|
id
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"a -> a",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "TODO: Type2::substitute"]
|
||||||
|
fn inference_var_inside_ctor() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
canIGo : _ -> Result _ _
|
||||||
|
canIGo = \color ->
|
||||||
|
when color is
|
||||||
|
"green" -> Ok "go!"
|
||||||
|
"yellow" -> Err (SlowIt "whoa, let's slow down!")
|
||||||
|
"red" -> Err (StopIt "absolutely not")
|
||||||
|
_ -> Err (UnknownColor "this is a weird stoplight")
|
||||||
|
canIGo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Str -> Result Str [ SlowIt Str, StopIt Str, UnknownColor Str ]*",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "TODO: Gives { x : *, y : * } -> { x : *, y : * }. This is a bug in typechecking defs with annotations."]
|
||||||
|
fn inference_var_inside_ctor_linked() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
swapRcd: {x: _, y: _} -> {x: _, y: _}
|
||||||
|
swapRcd = \{x, y} -> {x: y, y: x}
|
||||||
|
swapRcd
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x : a, y : b } -> { x : b, y : a }",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_link_with_rigid() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
swapRcd: {x: tx, y: ty} -> {x: _, y: _}
|
||||||
|
swapRcd = \{x, y} -> {x: y, y: x}
|
||||||
|
swapRcd
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x : tx, y : ty } -> { x : ty, y : tx }",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "TODO: Type2::substitute"]
|
||||||
|
fn inference_var_inside_tag_ctor() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
badComics: Bool -> [ CowTools _, Thagomizer _ ]
|
||||||
|
badComics = \c ->
|
||||||
|
when c is
|
||||||
|
True -> CowTools "The Far Side"
|
||||||
|
False -> Thagomizer "The Far Side"
|
||||||
|
badComics
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Bool -> [ CowTools Str, Thagomizer Str ]",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_tag_union_ext() {
|
||||||
|
// TODO: we should really be inferring [ Blue, Orange ]a -> [ Lavender, Peach ]a here.
|
||||||
|
// See https://github.com/rtfeldman/roc/issues/2053
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
pastelize: _ -> [ Lavender, Peach ]_
|
||||||
|
pastelize = \color ->
|
||||||
|
when color is
|
||||||
|
Blue -> Lavender
|
||||||
|
Orange -> Peach
|
||||||
|
col -> col
|
||||||
|
pastelize
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"[ Blue, Lavender, Orange, Peach ]a -> [ Blue, Lavender, Orange, Peach ]a",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "TODO: gives { email : a, name : b }c -> { email : a, name : b }c. This is a bug in typechecking defs with annotations."]
|
||||||
|
fn inference_var_rcd_union_ext() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
setRocEmail : _ -> { name: Str, email: Str }_
|
||||||
|
setRocEmail = \person ->
|
||||||
|
{ person & email: "\(person.name)@roclang.com" }
|
||||||
|
setRocEmail
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ email : Str, name : Str }a -> { email : Str, name : Str }a",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -390,7 +390,9 @@ pub fn to_type2<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Inferred => {
|
Inferred => {
|
||||||
unimplemented!();
|
let var = env.var_store.fresh();
|
||||||
|
|
||||||
|
Type2::Variable(var)
|
||||||
}
|
}
|
||||||
Wildcard | Malformed(_) => {
|
Wildcard | Malformed(_) => {
|
||||||
let var = env.var_store.fresh();
|
let var = env.var_store.fresh();
|
||||||
|
|
|
@ -460,7 +460,10 @@ fn can_annotation_help(
|
||||||
Type::Variable(var)
|
Type::Variable(var)
|
||||||
}
|
}
|
||||||
Inferred => {
|
Inferred => {
|
||||||
unimplemented!();
|
// Inference variables aren't bound to a rigid or a wildcard, so all we have to do is
|
||||||
|
// make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
|
||||||
|
let var = var_store.fresh();
|
||||||
|
Type::Variable(var)
|
||||||
}
|
}
|
||||||
Malformed(string) => {
|
Malformed(string) => {
|
||||||
malformed(env, region, string);
|
malformed(env, region, string);
|
||||||
|
|
|
@ -4604,4 +4604,117 @@ mod solve_expr {
|
||||||
"RBTree {}",
|
"RBTree {}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_inside_arrow() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
id : _ -> _
|
||||||
|
id = \x -> x
|
||||||
|
id
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"a -> a",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_inside_ctor() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
canIGo : _ -> Result _ _
|
||||||
|
canIGo = \color ->
|
||||||
|
when color is
|
||||||
|
"green" -> Ok "go!"
|
||||||
|
"yellow" -> Err (SlowIt "whoa, let's slow down!")
|
||||||
|
"red" -> Err (StopIt "absolutely not")
|
||||||
|
_ -> Err (UnknownColor "this is a weird stoplight")
|
||||||
|
canIGo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Str -> Result Str [ SlowIt Str, StopIt Str, UnknownColor Str ]*",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_inside_ctor_linked() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
swapRcd: {x: _, y: _} -> {x: _, y: _}
|
||||||
|
swapRcd = \{x, y} -> {x: y, y: x}
|
||||||
|
swapRcd
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x : a, y : b } -> { x : b, y : a }",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_link_with_rigid() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
swapRcd: {x: tx, y: ty} -> {x: _, y: _}
|
||||||
|
swapRcd = \{x, y} -> {x: y, y: x}
|
||||||
|
swapRcd
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x : tx, y : ty } -> { x : ty, y : tx }",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_inside_tag_ctor() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
badComics: Bool -> [ CowTools _, Thagomizer _ ]
|
||||||
|
badComics = \c ->
|
||||||
|
when c is
|
||||||
|
True -> CowTools "The Far Side"
|
||||||
|
False -> Thagomizer "The Far Side"
|
||||||
|
badComics
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Bool -> [ CowTools Str, Thagomizer Str ]",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_tag_union_ext() {
|
||||||
|
// TODO: we should really be inferring [ Blue, Orange ]a -> [ Lavender, Peach ]a here.
|
||||||
|
// See https://github.com/rtfeldman/roc/issues/2053
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
pastelize: _ -> [ Lavender, Peach ]_
|
||||||
|
pastelize = \color ->
|
||||||
|
when color is
|
||||||
|
Blue -> Lavender
|
||||||
|
Orange -> Peach
|
||||||
|
col -> col
|
||||||
|
pastelize
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"[ Blue, Lavender, Orange, Peach ]a -> [ Blue, Lavender, Orange, Peach ]a",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_rcd_union_ext() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
setRocEmail : _ -> { name: Str, email: Str }_
|
||||||
|
setRocEmail = \person ->
|
||||||
|
{ person & email: "\(person.name)@roclang.com" }
|
||||||
|
setRocEmail
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ email : Str, name : Str }a -> { email : Str, name : Str }a",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6667,4 +6667,102 @@ I need all branches in an `if` to have the same type!
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_not_enough_in_alias() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
canIGo : _ -> Result _
|
||||||
|
canIGo = \color ->
|
||||||
|
when color is
|
||||||
|
"green" -> Ok "go!"
|
||||||
|
"yellow" -> Err (SlowIt "whoa, let's slow down!")
|
||||||
|
"red" -> Err (StopIt "absolutely not")
|
||||||
|
_ -> Err (UnknownColor "this is a weird stoplight")
|
||||||
|
canIGo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── TOO FEW TYPE ARGUMENTS ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The `Result` alias expects 2 type arguments, but it got 1 instead:
|
||||||
|
|
||||||
|
1│ canIGo : _ -> Result _
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
Are there missing parentheses?
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_too_many_in_alias() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
canIGo : _ -> Result _ _ _
|
||||||
|
canIGo = \color ->
|
||||||
|
when color is
|
||||||
|
"green" -> Ok "go!"
|
||||||
|
"yellow" -> Err (SlowIt "whoa, let's slow down!")
|
||||||
|
"red" -> Err (StopIt "absolutely not")
|
||||||
|
_ -> Err (UnknownColor "this is a weird stoplight")
|
||||||
|
canIGo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── TOO MANY TYPE ARGUMENTS ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The `Result` alias expects 2 type arguments, but it got 3 instead:
|
||||||
|
|
||||||
|
1│ canIGo : _ -> Result _ _ _
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Are there missing parentheses?
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inference_var_conflict_in_rigid_links() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
f : a -> (_ -> b)
|
||||||
|
f = \x -> \y -> if x == y then x else y
|
||||||
|
f
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
// TODO: We should tell the user that we inferred `_` as `a`
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Something is off with the body of the `f` definition:
|
||||||
|
|
||||||
|
1│ f : a -> (_ -> b)
|
||||||
|
2│ f = \x -> \y -> if x == y then x else y
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The body is an anonymous function of type:
|
||||||
|
|
||||||
|
a -> a
|
||||||
|
|
||||||
|
But the type annotation on `f` says it should be:
|
||||||
|
|
||||||
|
a -> b
|
||||||
|
|
||||||
|
Tip: Your type annotation uses `a` and `b` as separate type variables.
|
||||||
|
Your code seems to be saying they are the same though. Maybe they
|
||||||
|
should be the same your type annotation? Maybe your code uses them in
|
||||||
|
a weird way?
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue