Skip to content

Commit 6612b69

Browse files
cknittclaude
authored andcommitted
Type a let destructuring a GADT without switch diagnostics
Why: A `let` whose pattern contains a GADT constructor is typed as a one-case match, and that match took the diagnostic context of a `switch` in the source: a type error in the rest of the block said "But this switch is expected to return" and added the hint about switch branches, although the source has no switch. The match now carries `#let_gadt_pattern`, and its cases, like those of the match for an optional argument default, get function-style diagnostics. Typedtree modified (let destructuring a GADT constructor): Example: type rec any = Any('a, 'a => string): any let get = value => { let Any(x, f) = value f(x) } Before (typed): expression Texp_match expression Texp_ident "value/1004" [ <case> pattern Tpat_construct "Any" … expression Texp_apply … ] After (typed): expression attribute "#let_gadt_pattern" [] Texp_match expression Texp_ident "value/1004" [ <case> pattern Tpat_construct "Any" … expression Texp_apply … ] Decision modified (diagnostic context of a match's cases): Before (text): `call_context` in `Typecore.type_expect_` match with @let.unwrap ↦ LetUnwrap match with #optional_arg_default ↦ Function match typed for a let with a GADT pattern ↦ Switch any other match ↦ Switch After (text): match with @let.unwrap ↦ LetUnwrap match with #optional_arg_default ↦ Function match with #let_gadt_pattern ↦ Function any other match ↦ Switch Diagnostic modified (type error in the body of a let destructuring a GADT): Example: type rec any = Any('a, 'a => string): any let get = value => { let Any(x, _) = value x } Before (diagnostic): This has type: $a But this switch is expected to return: 'a $a is an existential type bound by the constructor Any. It is only known in the scope of the pattern that matches Any. All branches in a switch must return the same type. To fix this, change your branch to return the expected type. After (diagnostic): This has type: $a But it's expected to have type: 'a $a is an existential type bound by the constructor Any. It is only known in the scope of the pattern that matches Any. Fixes: #9069 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3 Claude-Session: https://claude.ai/code/session_01Kr4cjoUi2f5VpMvE6wT9bo Signed-off-by: Christoph Knittel <ck@cca.io> Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com>
1 parent 139986d commit 6612b69

4 files changed

Lines changed: 29 additions & 3 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- A type error in the body after a `let` that destructures a GADT constructor no longer talks about switch branches.

‎compiler/ml/typecore.ml‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2644,6 +2644,8 @@ and type_expect_ ?deprecated_context ~context ?(recarg = Rejected) env sexp
26442644
{
26452645
sexp with
26462646
pexp_desc = Pexp_match (sval, [Ast_helper.Exp.case spat sbody]);
2647+
pexp_attributes =
2648+
(mknoloc "#let_gadt_pattern", PStr []) :: sexp.pexp_attributes;
26472649
}
26482650
ty_expected
26492651
| Pexp_let (rec_flag, spat_sexp_list, sbody) ->
@@ -2832,10 +2834,13 @@ and type_expect_ ?deprecated_context ~context ?(recarg = Rejected) env sexp
28322834
in
28332835
let call_context =
28342836
(* Optional-default lowering synthesizes a match expression, but from the
2835-
user's point of view this is still part of a function definition. Use
2836-
function-style diagnostics instead of reporting a phantom switch. *)
2837+
user's point of view this is still part of a function definition. A
2838+
[let] destructuring a GADT constructor is typed as a one-case match,
2839+
whose body is the rest of the block. Use function-style diagnostics
2840+
for both instead of reporting a phantom switch. *)
28372841
if has_attr "let.unwrap" then `LetUnwrap
2838-
else if has_attr "#optional_arg_default" then `Function
2842+
else if has_attr "#optional_arg_default" || has_attr "#let_gadt_pattern"
2843+
then `Function
28392844
else `Switch
28402845
in
28412846
let val_cases, partial =
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/gadt_existential_let.res:5:3
4+
5+
3 │ let get = value => {
6+
4 │ let Any(x, _) = value
7+
5 │ x
8+
6 │ }
9+
7 │
10+
11+
This has type: $a
12+
But it's expected to have type: 'a
13+
$a is an existential type bound by the constructor Any.
14+
It is only known in the scope of the pattern that matches Any.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
type rec any = Any('a, 'a => string): any
2+
3+
let get = value => {
4+
let Any(x, _) = value
5+
x
6+
}

0 commit comments

Comments
 (0)