From fc1617bf17d8443a09d81c7d307d492b01bf0abd Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 5 Jul 2022 19:07:41 -0400 Subject: [PATCH] Phantom types Closes #3314 --- crates/compiler/can/src/def.rs | 29 +++++++--- crates/compiler/solve/tests/solve_expr.rs | 51 +++++++++++++++++ crates/reporting/tests/test_reporting.rs | 67 +++++++++++++++++++++++ 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 6463029cc8..91d78f824f 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -351,15 +351,28 @@ fn canonicalize_alias<'a>( region: loc_lowercase.region, }); } - None => { - is_phantom = true; + None => match kind { + AliasKind::Structural => { + is_phantom = true; - env.problems.push(Problem::PhantomTypeArgument { - typ: symbol, - variable_region: loc_lowercase.region, - variable_name: loc_lowercase.value.clone(), - }); - } + env.problems.push(Problem::PhantomTypeArgument { + typ: symbol, + variable_region: loc_lowercase.region, + variable_name: loc_lowercase.value.clone(), + }); + } + AliasKind::Opaque => { + // Opaques can have phantom types. + can_vars.push(Loc { + value: AliasVar { + name: loc_lowercase.value.clone(), + var: var_store.fresh(), + opt_bound_ability: None, + }, + region: loc_lowercase.region, + }); + } + }, } } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 0f00ca6f47..cfbd38859e 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -7050,4 +7050,55 @@ mod solve_expr { &["fun : {} -[[thunk(5) [A Str]*, thunk(5) { a : Str }]]-> Str",] ); } + + #[test] + fn check_phantom_type() { + infer_eq_without_problem( + indoc!( + r#" + F a b := b + + foo : F Str Str -> F U8 Str + + x : F Str Str + + foo x + "# + ), + "F U8 Str", + ); + } + + #[test] + fn infer_phantom_type_flow() { + infer_eq_without_problem( + indoc!( + r#" + F a b := b + + foo : _ -> F U8 Str + foo = \it -> it + + foo + "# + ), + "F U8 Str -> F U8 Str", + ); + } + + #[test] + fn infer_unbound_phantom_type_star() { + infer_eq_without_problem( + indoc!( + r#" + F a b := b + + foo = \@F {} -> @F "" + + foo + "# + ), + "F * {}* -> F * Str", + ); + } } diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index af5c751023..048ad7aae4 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -9369,4 +9369,71 @@ All branches in an `if` must have the same type! a -> Str "### ); + + test_report!( + same_phantom_types_unify, + indoc!( + r#" + F a b := b + + foo : F Str Str -> {} + + x : F Str Str + + foo x + "# + ), + @r"" // okay + ); + + test_report!( + different_phantom_types, + indoc!( + r#" + F a b := b + + foo : F Str Str -> {} + + x : F U8 Str + + foo x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `foo` is not what I expect: + + 10│ foo x + ^ + + This `x` value is a: + + F U8 Str + + But `foo` needs the 1st argument to be: + + F Str Str + "### + ); + + test_report!( + #[ignore = "TODO This should be a type error"] + phantom_type_bound_to_ability_not_implementing, + indoc!( + r#" + app "test" provides [x] to "./platform" + + Foo has foo : a -> a | a has Foo + + F a b := b | a has Foo + + Hash := {} + + x : F Hash {} + "# + ), + @r###" + "### + ); }