mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-07 14:44:59 +00:00
Use ordinals in list error message
This commit is contained in:
parent
1a7043f407
commit
c0700465b4
10 changed files with 225 additions and 28 deletions
|
@ -276,9 +276,10 @@ pub fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx) void {
|
|||
// anything because we already pre-unified the list's elem var with it.
|
||||
const first_elem_idx = elems[0];
|
||||
var last_unified_idx: CIR.Expr.Idx = first_elem_idx;
|
||||
var last_unified_index: usize = 0; // Track the index for error messages
|
||||
self.checkExpr(first_elem_idx);
|
||||
|
||||
for (elems[1..]) |elem_expr_id| {
|
||||
for (elems[1..], 1..) |elem_expr_id, i| {
|
||||
self.checkExpr(elem_expr_id);
|
||||
|
||||
// Unify each element's var with the list's elem var
|
||||
|
@ -298,19 +299,17 @@ pub fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx) void {
|
|||
// Extract snapshots from the type mismatch problem
|
||||
switch (self.problems.problems.get(problem_idx)) {
|
||||
.type_mismatch => |mismatch| {
|
||||
// The expected type is elem_var, actual is the incompatible element
|
||||
elem_var_snapshot = mismatch.expected;
|
||||
incompatible_snapshot = mismatch.actual;
|
||||
},
|
||||
else => {
|
||||
// Shouldn't happen, but handle gracefully
|
||||
elem_var_snapshot = self.snapshots.createSnapshot(
|
||||
self.types,
|
||||
elem_var,
|
||||
);
|
||||
incompatible_snapshot = self.snapshots.createSnapshot(
|
||||
self.types,
|
||||
@enumFromInt(@intFromEnum(elem_expr_id)),
|
||||
);
|
||||
// For other problem types (e.g., number_does_not_fit), the original
|
||||
// problem is already more specific than our generic "incompatible list
|
||||
// elements" message, so we should keep it as-is and not replace it.
|
||||
// Note: if an element has an error type (e.g., from a nested heterogeneous
|
||||
// list), unification would succeed, not fail, so we wouldn't reach this branch.
|
||||
break;
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -328,9 +327,11 @@ pub fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx) void {
|
|||
.first_elem_region = prev_region orelse list.region,
|
||||
.first_elem_var = @enumFromInt(@intFromEnum(last_unified_idx)),
|
||||
.first_elem_snapshot = elem_var_snapshot,
|
||||
.first_elem_index = last_unified_index,
|
||||
.incompatible_elem_region = incomp_region orelse list.region,
|
||||
.incompatible_elem_var = @enumFromInt(@intFromEnum(elem_expr_id)),
|
||||
.incompatible_elem_snapshot = incompatible_snapshot,
|
||||
.incompatible_elem_index = i,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -341,6 +342,7 @@ pub fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx) void {
|
|||
|
||||
// Track the last successfully unified element
|
||||
last_unified_idx = elem_expr_id;
|
||||
last_unified_index = i;
|
||||
}
|
||||
},
|
||||
.e_empty_list => |_| {},
|
||||
|
|
|
@ -221,7 +221,15 @@ pub const Problem = union(enum) {
|
|||
const owned_incompatible_type = try report.addOwnedString(buf.items);
|
||||
|
||||
// Add description
|
||||
try report.document.addText("These two elements in this list have incompatible types:");
|
||||
const first_ordinal = try ordinalString(gpa, data.first_elem_index + 1);
|
||||
defer gpa.free(first_ordinal);
|
||||
const second_ordinal = try ordinalString(gpa, data.incompatible_elem_index + 1);
|
||||
defer gpa.free(second_ordinal);
|
||||
|
||||
const description = try std.fmt.allocPrint(gpa, "The {s} and {s} elements in this list have incompatible types:", .{ first_ordinal, second_ordinal });
|
||||
defer gpa.free(description);
|
||||
const owned_description = try report.addOwnedString(description);
|
||||
try report.document.addText(owned_description);
|
||||
try report.document.addLineBreak();
|
||||
|
||||
// Show a single region spanning both mismatched elements
|
||||
|
@ -253,7 +261,10 @@ pub const Problem = union(enum) {
|
|||
try report.document.addLineBreak();
|
||||
|
||||
// Show the type of the first element
|
||||
try report.document.addText("The first element has this type:");
|
||||
const first_type_desc = try std.fmt.allocPrint(gpa, "The {s} element has this type:", .{first_ordinal});
|
||||
defer gpa.free(first_type_desc);
|
||||
const owned_first_type_desc = try report.addOwnedString(first_type_desc);
|
||||
try report.document.addText(owned_first_type_desc);
|
||||
try report.document.addLineBreak();
|
||||
try report.document.addText(" ");
|
||||
try report.document.addAnnotated(owned_first_type, .type_variable);
|
||||
|
@ -261,7 +272,10 @@ pub const Problem = union(enum) {
|
|||
try report.document.addLineBreak();
|
||||
|
||||
// Show the type of the second element
|
||||
try report.document.addText("The second element has this type:");
|
||||
const second_type_desc = try std.fmt.allocPrint(gpa, "However, the {s} element has this type:", .{second_ordinal});
|
||||
defer gpa.free(second_type_desc);
|
||||
const owned_second_type_desc = try report.addOwnedString(second_type_desc);
|
||||
try report.document.addText(owned_second_type_desc);
|
||||
try report.document.addLineBreak();
|
||||
try report.document.addText(" ");
|
||||
try report.document.addAnnotated(owned_incompatible_type, .type_variable);
|
||||
|
@ -394,6 +408,31 @@ pub const Problem = union(enum) {
|
|||
const report = Report.init(allocator, "UNIMPLEMENTED", .runtime_error);
|
||||
return report;
|
||||
}
|
||||
|
||||
fn ordinalString(allocator: Allocator, n: usize) ![]u8 {
|
||||
const suffix = switch (n) {
|
||||
1 => "st",
|
||||
2 => "nd",
|
||||
3 => "rd",
|
||||
else => blk: {
|
||||
// For numbers ending in 1, 2, or 3 (except 11, 12, 13), use st, nd, rd
|
||||
const last_digit = n % 10;
|
||||
const last_two_digits = n % 100;
|
||||
if (last_two_digits >= 11 and last_two_digits <= 13) {
|
||||
break :blk "th"; // 11th, 12th, 13th
|
||||
} else if (last_digit == 1) {
|
||||
break :blk "st"; // 21st, 31st, etc.
|
||||
} else if (last_digit == 2) {
|
||||
break :blk "nd"; // 22nd, 32nd, etc.
|
||||
} else if (last_digit == 3) {
|
||||
break :blk "rd"; // 23rd, 33rd, etc.
|
||||
} else {
|
||||
break :blk "th"; // 4th, 5th, ... 20th, 24th, etc.
|
||||
}
|
||||
},
|
||||
};
|
||||
return std.fmt.allocPrint(allocator, "{d}{s}", .{ n, suffix });
|
||||
}
|
||||
};
|
||||
|
||||
/// A single var problem
|
||||
|
@ -408,9 +447,11 @@ pub const IncompatibleListElements = struct {
|
|||
first_elem_region: base.Region,
|
||||
first_elem_var: Var,
|
||||
first_elem_snapshot: SnapshotContentIdx,
|
||||
first_elem_index: usize, // 0-based index of the first element
|
||||
incompatible_elem_region: base.Region,
|
||||
incompatible_elem_var: Var,
|
||||
incompatible_elem_snapshot: SnapshotContentIdx,
|
||||
incompatible_elem_index: usize, // 0-based index of the incompatible element
|
||||
};
|
||||
|
||||
/// A two var problem
|
||||
|
|
6
src/snapshots/can_list_first_concrete.md
generated
6
src/snapshots/can_list_first_concrete.md
generated
|
@ -9,17 +9,17 @@ type=expr
|
|||
~~~
|
||||
# PROBLEMS
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
These two elements in this list have incompatible types:
|
||||
The 1st and 2nd elements in this list have incompatible types:
|
||||
**can_list_first_concrete.md:1:2:1:13:**
|
||||
```roc
|
||||
[42, "world", 3.14]
|
||||
```
|
||||
^^^^^^^^^^^
|
||||
|
||||
The first element has this type:
|
||||
The 1st element has this type:
|
||||
_Num(*)_
|
||||
|
||||
The second element has this type:
|
||||
However, the 2nd element has this type:
|
||||
_Str_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
|
6
src/snapshots/can_list_heterogeneous.md
generated
6
src/snapshots/can_list_heterogeneous.md
generated
|
@ -9,17 +9,17 @@ type=expr
|
|||
~~~
|
||||
# PROBLEMS
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
These two elements in this list have incompatible types:
|
||||
The 1st and 2nd elements in this list have incompatible types:
|
||||
**can_list_heterogeneous.md:1:2:1:12:**
|
||||
```roc
|
||||
[1, "hello", 3.14]
|
||||
```
|
||||
^^^^^^^^^^
|
||||
|
||||
The first element has this type:
|
||||
The 1st element has this type:
|
||||
_Num(*)_
|
||||
|
||||
The second element has this type:
|
||||
However, the 2nd element has this type:
|
||||
_Str_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
|
6
src/snapshots/can_list_nested_heterogeneous.md
generated
6
src/snapshots/can_list_nested_heterogeneous.md
generated
|
@ -9,17 +9,17 @@ type=expr
|
|||
~~~
|
||||
# PROBLEMS
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
These two elements in this list have incompatible types:
|
||||
The 2nd and 3rd elements in this list have incompatible types:
|
||||
**can_list_nested_heterogeneous.md:1:6:1:20:**
|
||||
```roc
|
||||
[[], [1], ["hello"]]
|
||||
```
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The first element has this type:
|
||||
The 2nd element has this type:
|
||||
_List(Num(*))_
|
||||
|
||||
The second element has this type:
|
||||
However, the 3rd element has this type:
|
||||
_List(Str)_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
|
59
src/snapshots/can_list_number_doesnt_fit.md
generated
Normal file
59
src/snapshots/can_list_number_doesnt_fit.md
generated
Normal file
|
@ -0,0 +1,59 @@
|
|||
# META
|
||||
~~~ini
|
||||
description=List with number literal that doesn't fit in inferred type
|
||||
type=expr
|
||||
~~~
|
||||
# SOURCE
|
||||
~~~roc
|
||||
[1u8, 2u8, 300]
|
||||
~~~
|
||||
# PROBLEMS
|
||||
**INVALID NUMBER**
|
||||
This number literal is not valid: 1u8
|
||||
|
||||
**INVALID NUMBER**
|
||||
This number literal is not valid: 2u8
|
||||
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
The 1st and 2nd elements in this list have incompatible types:
|
||||
**can_list_number_doesnt_fit.md:1:2:1:10:**
|
||||
```roc
|
||||
[1u8, 2u8, 300]
|
||||
```
|
||||
^^^^^^^^
|
||||
|
||||
The 1st element has this type:
|
||||
_Error_
|
||||
|
||||
However, the 2nd element has this type:
|
||||
_Error_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
||||
# TOKENS
|
||||
~~~zig
|
||||
OpenSquare(1:1-1:2),Int(1:2-1:5),Comma(1:5-1:6),Int(1:7-1:10),Comma(1:10-1:11),Int(1:12-1:15),CloseSquare(1:15-1:16),EndOfFile(1:16-1:16),
|
||||
~~~
|
||||
# PARSE
|
||||
~~~clojure
|
||||
(e-list @1-1-1-16
|
||||
(e-int @1-2-1-5 (raw "1u8"))
|
||||
(e-int @1-7-1-10 (raw "2u8"))
|
||||
(e-int @1-12-1-15 (raw "300")))
|
||||
~~~
|
||||
# FORMATTED
|
||||
~~~roc
|
||||
NO CHANGE
|
||||
~~~
|
||||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(e-list @1-1-1-16 (elem-var 73) (id 77)
|
||||
(elems
|
||||
(e-runtime-error (tag "invalid_num_literal"))
|
||||
(e-runtime-error (tag "invalid_num_literal"))
|
||||
(e-int @1-12-1-15 (value "300"))))
|
||||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(expr (id 77) (type "List(Error)"))
|
||||
~~~
|
|
@ -9,17 +9,17 @@ type=expr
|
|||
~~~
|
||||
# PROBLEMS
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
These two elements in this list have incompatible types:
|
||||
The 2nd and 3rd elements in this list have incompatible types:
|
||||
**can_list_triple_nested_heterogeneous.md:1:6:1:32:**
|
||||
```roc
|
||||
[[], [[], [1]], [[], ["hello"]]]
|
||||
```
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The first element has this type:
|
||||
The 2nd element has this type:
|
||||
_List(List(Num(*)))_
|
||||
|
||||
The second element has this type:
|
||||
However, the 3rd element has this type:
|
||||
_List(List(Str))_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
|
79
src/snapshots/can_nested_heterogeneous_lists.md
generated
Normal file
79
src/snapshots/can_nested_heterogeneous_lists.md
generated
Normal file
|
@ -0,0 +1,79 @@
|
|||
# META
|
||||
~~~ini
|
||||
description=Nested heterogeneous lists
|
||||
type=expr
|
||||
~~~
|
||||
# SOURCE
|
||||
~~~roc
|
||||
[[1, "hello"], [2, 3]]
|
||||
~~~
|
||||
# PROBLEMS
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
The 1st and 2nd elements in this list have incompatible types:
|
||||
**can_nested_heterogeneous_lists.md:1:3:1:13:**
|
||||
```roc
|
||||
[[1, "hello"], [2, 3]]
|
||||
```
|
||||
^^^^^^^^^^
|
||||
|
||||
The 1st element has this type:
|
||||
_Num(*)_
|
||||
|
||||
However, the 2nd element has this type:
|
||||
_Str_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
The 1st and 2nd elements in this list have incompatible types:
|
||||
**can_nested_heterogeneous_lists.md:1:2:1:22:**
|
||||
```roc
|
||||
[[1, "hello"], [2, 3]]
|
||||
```
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The 1st element has this type:
|
||||
_List(Error)_
|
||||
|
||||
However, the 2nd element has this type:
|
||||
_List(Num(*))_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
||||
# TOKENS
|
||||
~~~zig
|
||||
OpenSquare(1:1-1:2),OpenSquare(1:2-1:3),Int(1:3-1:4),Comma(1:4-1:5),StringStart(1:6-1:7),StringPart(1:7-1:12),StringEnd(1:12-1:13),CloseSquare(1:13-1:14),Comma(1:14-1:15),OpenSquare(1:16-1:17),Int(1:17-1:18),Comma(1:18-1:19),Int(1:20-1:21),CloseSquare(1:21-1:22),CloseSquare(1:22-1:23),EndOfFile(1:23-1:23),
|
||||
~~~
|
||||
# PARSE
|
||||
~~~clojure
|
||||
(e-list @1-1-1-23
|
||||
(e-list @1-2-1-14
|
||||
(e-int @1-3-1-4 (raw "1"))
|
||||
(e-string @1-6-1-13
|
||||
(e-string-part @1-7-1-12 (raw "hello"))))
|
||||
(e-list @1-16-1-22
|
||||
(e-int @1-17-1-18 (raw "2"))
|
||||
(e-int @1-20-1-21 (raw "3"))))
|
||||
~~~
|
||||
# FORMATTED
|
||||
~~~roc
|
||||
NO CHANGE
|
||||
~~~
|
||||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(e-list @1-1-1-23 (elem-var 75) (id 79)
|
||||
(elems
|
||||
(e-list @1-2-1-14 (elem-var 72)
|
||||
(elems
|
||||
(e-int @1-3-1-4 (value "1"))
|
||||
(e-string @1-6-1-13
|
||||
(e-literal @1-7-1-12 (string "hello")))))
|
||||
(e-list @1-16-1-22 (elem-var 76)
|
||||
(elems
|
||||
(e-int @1-17-1-18 (value "2"))
|
||||
(e-int @1-20-1-21 (value "3"))))))
|
||||
~~~
|
||||
# TYPES
|
||||
~~~clojure
|
||||
(expr (id 79) (type "List(Error)"))
|
||||
~~~
|
|
@ -9,17 +9,17 @@ type=expr
|
|||
~~~
|
||||
# PROBLEMS
|
||||
**INCOMPATIBLE LIST ELEMENTS**
|
||||
These two elements in this list have incompatible types:
|
||||
The 2nd and 3rd elements in this list have incompatible types:
|
||||
**list_type_err.md:1:5:1:15:**
|
||||
```roc
|
||||
[1, 2, "hello"]
|
||||
```
|
||||
^^^^^^^^^^
|
||||
|
||||
The first element has this type:
|
||||
The 2nd element has this type:
|
||||
_Num(*)_
|
||||
|
||||
The second element has this type:
|
||||
However, the 3rd element has this type:
|
||||
_Str_
|
||||
|
||||
All elements in a list must have compatible types.
|
||||
|
|
16
test_nested_heterogeneous.roc
Normal file
16
test_nested_heterogeneous.roc
Normal file
|
@ -0,0 +1,16 @@
|
|||
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
|
||||
|
||||
main =
|
||||
# Test case 1: Simple heterogeneous list
|
||||
list1 = [1, "hello", 3.14]
|
||||
|
||||
# Test case 2: Nested heterogeneous list - inner list has type mismatch
|
||||
list2 = [[1, "hello"], [2, 3]]
|
||||
|
||||
# Test case 3: Deeply nested heterogeneous list
|
||||
list3 = [[[1, 2], [3, 4]], [["a", "b"], ["c", "d"]]]
|
||||
|
||||
# Test case 4: Mixed heterogeneous issues
|
||||
list4 = [[1, 2], ["hello", "world"], [3.14, 2.71]]
|
||||
|
||||
Stdout.line! "If this compiles, something is wrong!"
|
Loading…
Add table
Add a link
Reference in a new issue