Skip to content

Commit 80627b9

Browse files
committed
Improve expected Try type error message
1 parent 7e46a23 commit 80627b9

3 files changed

Lines changed: 43 additions & 48 deletions

File tree

src/check/Check.zig

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4447,13 +4447,37 @@ fn checkMatchExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, match: CIR.Exp
44474447
// If so, we'll skip exhaustiveness checking since the types may be invalid
44484448
var had_type_error = false;
44494449

4450+
// For matches desugared from `?` operator, verify the condition unifies with Try type FIRST.
4451+
// If it doesn't, report the specific error and skip pattern checking to avoid confusing errors.
4452+
var has_invalid_try = false;
4453+
if (match.is_try_suffix) {
4454+
// Get the actual Try type from builtins and instantiate it with fresh type vars
4455+
const try_type_var = ModuleEnv.varFrom(self.builtin_ctx.try_stmt);
4456+
const copied_try_var = if (self.builtin_ctx.builtin_module) |builtin_env|
4457+
try self.copyVar(try_type_var, builtin_env, Region.zero())
4458+
else
4459+
try_type_var;
4460+
const try_var = try self.instantiateVar(copied_try_var, env, .use_root_instantiated);
4461+
4462+
// Unify the condition with Try type
4463+
const try_result = try self.unify(try_var, cond_var, env);
4464+
if (!try_result.isOk()) {
4465+
has_invalid_try = true;
4466+
self.setDetailIfTypeMismatch(try_result, problem.TypeMismatchDetail{ .invalid_try_operator = .{
4467+
.expr = match.cond,
4468+
} });
4469+
}
4470+
}
4471+
44504472
// Manually check the 1st branch
44514473
// The type of the branch's body becomes the var other branch bodies must unify
44524474
// against.
44534475
const first_branch_idx = branch_idxs[0];
44544476
const first_branch = self.cir.store.getMatchBranch(first_branch_idx);
44554477
const first_branch_ptrn_idxs = self.cir.store.sliceMatchBranchPatterns(first_branch.patterns);
44564478

4479+
// Skip pattern checking if we already know the condition isn't a Try type
4480+
// This prevents confusing cascading errors about pattern incompatibility
44574481
for (first_branch_ptrn_idxs) |branch_ptrn_idx| {
44584482
const branch_ptrn = self.cir.store.getMatchBranchPattern(branch_ptrn_idx);
44594483
try self.checkPattern(branch_ptrn.pattern, env);
@@ -4474,7 +4498,7 @@ fn checkMatchExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, match: CIR.Exp
44744498
for (branch_idxs[1..], 1..) |branch_idx, branch_cur_index| {
44754499
const branch = self.cir.store.getMatchBranch(branch_idx);
44764500

4477-
// First, check the patterns of this branch
4501+
// First, check the patterns of this branch (skip if invalid try to avoid confusing errors)
44784502
const branch_ptrn_idxs = self.cir.store.sliceMatchBranchPatterns(branch.patterns);
44794503
for (branch_ptrn_idxs, 0..) |branch_ptrn_idx, cur_ptrn_index| {
44804504
// Check the pattern's sub types
@@ -4509,7 +4533,7 @@ fn checkMatchExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, match: CIR.Exp
45094533
for (branch_idxs[branch_cur_index + 1 ..], branch_cur_index + 1..) |other_branch_idx, other_branch_cur_index| {
45104534
const other_branch = self.cir.store.getMatchBranch(other_branch_idx);
45114535

4512-
// Still check the other patterns
4536+
// Still check the other patterns (skip if invalid try to avoid confusing errors)
45134537
const other_branch_ptrn_idxs = self.cir.store.sliceMatchBranchPatterns(other_branch.patterns);
45144538
for (other_branch_ptrn_idxs, 0..) |other_branch_ptrn_idx, other_cur_ptrn_index| {
45154539
// Check the pattern's sub types
@@ -4538,29 +4562,6 @@ fn checkMatchExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, match: CIR.Exp
45384562
}
45394563
}
45404564

4541-
// For matches desugared from `?` operator, verify the condition unifies with Try type.
4542-
// If it doesn't, report an error. Skip exhaustiveness checking for invalid try since
4543-
// the desugared match only handles Ok/Err branches and would report confusing errors.
4544-
var has_invalid_try = false;
4545-
if (match.is_try_suffix) {
4546-
// Get the actual Try type from builtins and instantiate it with fresh type vars
4547-
const try_type_var = ModuleEnv.varFrom(self.builtin_ctx.try_stmt);
4548-
const copied_try_var = if (self.builtin_ctx.builtin_module) |builtin_env|
4549-
try self.copyVar(try_type_var, builtin_env, Region.zero())
4550-
else
4551-
try_type_var;
4552-
const try_var = try self.instantiateVar(copied_try_var, env, .use_root_instantiated);
4553-
4554-
// Unify the condition with Try type
4555-
const try_result = try self.unify(try_var, cond_var, env);
4556-
if (!try_result.isOk()) {
4557-
has_invalid_try = true;
4558-
self.setDetailIfTypeMismatch(try_result, problem.TypeMismatchDetail{ .invalid_try_operator = .{
4559-
.expr = match.cond,
4560-
} });
4561-
}
4562-
}
4563-
45644565
// Unify the root expr with the match value
45654566
_ = try self.unify(ModuleEnv.varFrom(expr_idx), val_var, env);
45664567

test/snapshots/eval/issue8738_question_on_non_try.md

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,23 @@ do_something = || {
2020
result = do_something()
2121
~~~
2222
# EXPECTED
23-
INCOMPATIBLE MATCH PATTERNS - issue8738_question_on_non_try.md:9:7:9:7
23+
EXPECTED TRY TYPE - issue8738_question_on_non_try.md:9:7:9:7
2424
# PROBLEMS
25-
**INCOMPATIBLE MATCH PATTERNS**
26-
The first pattern in this `match` is incompatible:
25+
**EXPECTED TRY TYPE**
26+
The `?` operator expects a _Try_ type (a tag union containing ONLY _Ok_ and _Err_ tags),
27+
but I found:
2728
**issue8738_question_on_non_try.md:9:7:**
2829
```roc
2930
_x = ok_or(Err(""), Exit(5))?
3031
```
31-
^^^^^^^^^^^^^^^^^^^^^^^^
32+
^^^^^^^^^^^^^^^^^^^^^^^
3233

33-
The first pattern has the type:
34+
This expression has type:
3435

35-
Try(ok, err)
36+
_[Exit(a), .._others]
37+
where [a.from_numeral : Numeral -> Try(a, [InvalidNumeral(Str)])]_
3638

37-
But the expression between the `match` parenthesis has the type:
38-
39-
[Exit(a), .._others]
40-
where [a.from_numeral : Numeral -> Try(a, [InvalidNumeral(Str)])]
41-
42-
These two types can never match!
39+
Tip: Maybe wrap a value using _Ok(value)_ or _Err(value)_.
4340

4441
# TOKENS
4542
~~~zig

test/snapshots/try_undefined_tag.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ A?
99
~~~
1010
# EXPECTED
1111
TRY OPERATOR OUTSIDE FUNCTION - try_undefined_tag.md:1:1:1:3
12-
INCOMPATIBLE MATCH PATTERNS - try_undefined_tag.md:1:1:1:1
12+
EXPECTED TRY TYPE - try_undefined_tag.md:1:1:1:1
1313
# PROBLEMS
1414
**TRY OPERATOR OUTSIDE FUNCTION**
1515
The `?` operator can only be used inside function bodies because it can cause an early return.
@@ -21,23 +21,20 @@ A?
2121
^^
2222

2323

24-
**INCOMPATIBLE MATCH PATTERNS**
25-
The first pattern in this `match` is incompatible:
24+
**EXPECTED TRY TYPE**
25+
The `?` operator expects a _Try_ type (a tag union containing ONLY _Ok_ and _Err_ tags),
26+
but I found:
2627
**try_undefined_tag.md:1:1:**
2728
```roc
2829
A?
2930
```
30-
^^
31-
32-
The first pattern has the type:
33-
34-
Try(ok, err)
31+
^
3532

36-
But the expression between the `match` parenthesis has the type:
33+
This expression has type:
3734

38-
[A, .._others]
35+
_[A, .._others]_
3936

40-
These two types can never match!
37+
Tip: Maybe wrap a value using _Ok(value)_ or _Err(value)_.
4138

4239
# TOKENS
4340
~~~zig

0 commit comments

Comments
 (0)