Skip to content

Commit 6a7f2a6

Browse files
cknittclaude
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. Its body was then checked as a switch branch, so a type error there said "But this switch is expected to return" and added the hint about switch branches, although the source has no switch. The synthesized match is now marked like the one for optional argument defaults and gets function-style diagnostics. Diagnostic modified (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>
1 parent 139986d commit 6a7f2a6

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)