mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-13 04:14:08 +00:00
Merge remote-tracking branch 'origin/main' into propagate_is_eq
This commit is contained in:
commit
b870851b51
27 changed files with 497 additions and 54 deletions
2
.github/workflows/ci_manager.yml
vendored
2
.github/workflows/ci_manager.yml
vendored
|
|
@ -35,6 +35,7 @@ jobs:
|
||||||
- '.github/actions/flaky-retry/action.yml'
|
- '.github/actions/flaky-retry/action.yml'
|
||||||
- 'ci/zig_lints.sh'
|
- 'ci/zig_lints.sh'
|
||||||
- 'ci/check_test_wiring.zig'
|
- 'ci/check_test_wiring.zig'
|
||||||
|
- 'ci/valgrind_clean.sh'
|
||||||
- uses: dorny/paths-filter@v3
|
- uses: dorny/paths-filter@v3
|
||||||
id: other_filter
|
id: other_filter
|
||||||
with:
|
with:
|
||||||
|
|
@ -53,6 +54,7 @@ jobs:
|
||||||
- '!.github/actions/flaky-retry/action.yml'
|
- '!.github/actions/flaky-retry/action.yml'
|
||||||
- '!ci/zig_lints.sh'
|
- '!ci/zig_lints.sh'
|
||||||
- '!ci/check_test_wiring.zig'
|
- '!ci/check_test_wiring.zig'
|
||||||
|
- '!ci/valgrind_clean.sh'
|
||||||
# Files that ci manager workflows should not run on.
|
# Files that ci manager workflows should not run on.
|
||||||
- '!.gitignore'
|
- '!.gitignore'
|
||||||
- '!.reuse'
|
- '!.reuse'
|
||||||
|
|
|
||||||
4
.github/workflows/ci_zig.yml
vendored
4
.github/workflows/ci_zig.yml
vendored
|
|
@ -194,9 +194,9 @@ jobs:
|
||||||
# We can re-evaluate as new version of zig/valgrind come out.
|
# We can re-evaluate as new version of zig/valgrind come out.
|
||||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||||
run: |
|
run: |
|
||||||
sudo apt install -y valgrind
|
sudo snap install valgrind --classic
|
||||||
valgrind --version
|
valgrind --version
|
||||||
valgrind --leak-check=full --error-exitcode=1 --errors-for-leak-kinds=definite,possible ./zig-out/bin/snapshot --debug
|
./ci/valgind_clean.sh --leak-check=full --error-exitcode=1 --errors-for-leak-kinds=definite,possible ./zig-out/bin/snapshot --debug
|
||||||
|
|
||||||
- name: check if statically linked (ubuntu)
|
- name: check if statically linked (ubuntu)
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
|
|
||||||
12
build.zig
12
build.zig
|
|
@ -242,7 +242,17 @@ const CheckTypeCheckerPatternsStep = struct {
|
||||||
std.mem.startsWith(u8, after_match, "order") or
|
std.mem.startsWith(u8, after_match, "order") or
|
||||||
std.mem.startsWith(u8, after_match, "copyForwards");
|
std.mem.startsWith(u8, after_match, "copyForwards");
|
||||||
|
|
||||||
if (!is_allowed) {
|
// Also allow module name comparisons - these are legitimately string-based
|
||||||
|
// (matching import names to loaded modules at runtime)
|
||||||
|
const is_module_name_comparison =
|
||||||
|
std.mem.indexOf(u8, line, "module_name") != null or
|
||||||
|
std.mem.indexOf(u8, line, "import_name") != null or
|
||||||
|
std.mem.indexOf(u8, line, "qualified_name") != null or
|
||||||
|
std.mem.indexOf(u8, line, "type_name") != null or
|
||||||
|
std.mem.indexOf(u8, line, "type_path") != null or
|
||||||
|
std.mem.indexOf(u8, line, "origin_env") != null;
|
||||||
|
|
||||||
|
if (!is_allowed and !is_module_name_comparison) {
|
||||||
try violations.append(allocator, .{
|
try violations.append(allocator, .{
|
||||||
.file_path = full_path,
|
.file_path = full_path,
|
||||||
.line_number = line_number,
|
.line_number = line_number,
|
||||||
|
|
|
||||||
4
ci/valgind_clean.sh
Executable file
4
ci/valgind_clean.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
valgrind "$@" 2>&1 | grep -v "Warning: DWARF2 reader: Badly formed extended line op encountered"
|
||||||
|
exit ${PIPESTATUS[0]}
|
||||||
|
|
@ -179,6 +179,8 @@ is_ne_ident: Ident.Idx,
|
||||||
// These match the nominal types created during type checking
|
// These match the nominal types created during type checking
|
||||||
builtin_try_ident: Ident.Idx,
|
builtin_try_ident: Ident.Idx,
|
||||||
builtin_numeral_ident: Ident.Idx,
|
builtin_numeral_ident: Ident.Idx,
|
||||||
|
/// Relative numeral ident "Num.Numeral" - used for creating nominal types where origin_module is Builtin
|
||||||
|
numeral_relative_ident: Ident.Idx,
|
||||||
builtin_str_ident: Ident.Idx,
|
builtin_str_ident: Ident.Idx,
|
||||||
list_type_ident: Ident.Idx,
|
list_type_ident: Ident.Idx,
|
||||||
box_type_ident: Ident.Idx,
|
box_type_ident: Ident.Idx,
|
||||||
|
|
@ -211,6 +213,9 @@ unbox_method_ident: Ident.Idx,
|
||||||
// Try tag idents
|
// Try tag idents
|
||||||
ok_ident: Ident.Idx,
|
ok_ident: Ident.Idx,
|
||||||
err_ident: Ident.Idx,
|
err_ident: Ident.Idx,
|
||||||
|
// Bool tag idents
|
||||||
|
true_tag_ident: Ident.Idx,
|
||||||
|
false_tag_ident: Ident.Idx,
|
||||||
|
|
||||||
/// Deferred numeric literals collected during type checking
|
/// Deferred numeric literals collected during type checking
|
||||||
/// These will be validated during comptime evaluation
|
/// These will be validated during comptime evaluation
|
||||||
|
|
@ -317,6 +322,7 @@ pub fn init(gpa: std.mem.Allocator, source: []const u8) std.mem.Allocator.Error!
|
||||||
// Pre-intern fully-qualified type identifiers for type checking and layout generation
|
// Pre-intern fully-qualified type identifiers for type checking and layout generation
|
||||||
const builtin_try_ident_val = try common.insertIdent(gpa, Ident.for_text("Builtin.Try"));
|
const builtin_try_ident_val = try common.insertIdent(gpa, Ident.for_text("Builtin.Try"));
|
||||||
const builtin_numeral_ident_val = try common.insertIdent(gpa, Ident.for_text("Builtin.Num.Numeral"));
|
const builtin_numeral_ident_val = try common.insertIdent(gpa, Ident.for_text("Builtin.Num.Numeral"));
|
||||||
|
const numeral_relative_ident = try common.insertIdent(gpa, Ident.for_text("Num.Numeral"));
|
||||||
const builtin_str_ident = try common.insertIdent(gpa, Ident.for_text("Builtin.Str"));
|
const builtin_str_ident = try common.insertIdent(gpa, Ident.for_text("Builtin.Str"));
|
||||||
const list_type_ident = try common.insertIdent(gpa, Ident.for_text("List"));
|
const list_type_ident = try common.insertIdent(gpa, Ident.for_text("List"));
|
||||||
const box_type_ident = try common.insertIdent(gpa, Ident.for_text("Box"));
|
const box_type_ident = try common.insertIdent(gpa, Ident.for_text("Box"));
|
||||||
|
|
@ -345,6 +351,8 @@ pub fn init(gpa: std.mem.Allocator, source: []const u8) std.mem.Allocator.Error!
|
||||||
const unbox_method_ident = try common.insertIdent(gpa, Ident.for_text("unbox"));
|
const unbox_method_ident = try common.insertIdent(gpa, Ident.for_text("unbox"));
|
||||||
const ok_ident = try common.insertIdent(gpa, Ident.for_text("Ok"));
|
const ok_ident = try common.insertIdent(gpa, Ident.for_text("Ok"));
|
||||||
const err_ident = try common.insertIdent(gpa, Ident.for_text("Err"));
|
const err_ident = try common.insertIdent(gpa, Ident.for_text("Err"));
|
||||||
|
const true_tag_ident = try common.insertIdent(gpa, Ident.for_text("True"));
|
||||||
|
const false_tag_ident = try common.insertIdent(gpa, Ident.for_text("False"));
|
||||||
|
|
||||||
return Self{
|
return Self{
|
||||||
.gpa = gpa,
|
.gpa = gpa,
|
||||||
|
|
@ -384,6 +392,7 @@ pub fn init(gpa: std.mem.Allocator, source: []const u8) std.mem.Allocator.Error!
|
||||||
.is_ne_ident = is_ne_ident,
|
.is_ne_ident = is_ne_ident,
|
||||||
.builtin_try_ident = builtin_try_ident_val,
|
.builtin_try_ident = builtin_try_ident_val,
|
||||||
.builtin_numeral_ident = builtin_numeral_ident_val,
|
.builtin_numeral_ident = builtin_numeral_ident_val,
|
||||||
|
.numeral_relative_ident = numeral_relative_ident,
|
||||||
.builtin_str_ident = builtin_str_ident,
|
.builtin_str_ident = builtin_str_ident,
|
||||||
.list_type_ident = list_type_ident,
|
.list_type_ident = list_type_ident,
|
||||||
.box_type_ident = box_type_ident,
|
.box_type_ident = box_type_ident,
|
||||||
|
|
@ -412,6 +421,8 @@ pub fn init(gpa: std.mem.Allocator, source: []const u8) std.mem.Allocator.Error!
|
||||||
.unbox_method_ident = unbox_method_ident,
|
.unbox_method_ident = unbox_method_ident,
|
||||||
.ok_ident = ok_ident,
|
.ok_ident = ok_ident,
|
||||||
.err_ident = err_ident,
|
.err_ident = err_ident,
|
||||||
|
.true_tag_ident = true_tag_ident,
|
||||||
|
.false_tag_ident = false_tag_ident,
|
||||||
.deferred_numeric_literals = try DeferredNumericLiteral.SafeList.initCapacity(gpa, 32),
|
.deferred_numeric_literals = try DeferredNumericLiteral.SafeList.initCapacity(gpa, 32),
|
||||||
.import_mapping = types_mod.import_mapping.ImportMapping.init(gpa),
|
.import_mapping = types_mod.import_mapping.ImportMapping.init(gpa),
|
||||||
};
|
};
|
||||||
|
|
@ -1858,6 +1869,7 @@ pub const Serialized = extern struct {
|
||||||
// Fully-qualified type identifiers for type checking and layout generation
|
// Fully-qualified type identifiers for type checking and layout generation
|
||||||
builtin_try_ident: Ident.Idx,
|
builtin_try_ident: Ident.Idx,
|
||||||
builtin_numeral_ident: Ident.Idx,
|
builtin_numeral_ident: Ident.Idx,
|
||||||
|
numeral_relative_ident: Ident.Idx,
|
||||||
builtin_str_ident: Ident.Idx,
|
builtin_str_ident: Ident.Idx,
|
||||||
list_type_ident: Ident.Idx,
|
list_type_ident: Ident.Idx,
|
||||||
box_type_ident: Ident.Idx,
|
box_type_ident: Ident.Idx,
|
||||||
|
|
@ -1886,6 +1898,8 @@ pub const Serialized = extern struct {
|
||||||
unbox_method_ident: Ident.Idx,
|
unbox_method_ident: Ident.Idx,
|
||||||
ok_ident: Ident.Idx,
|
ok_ident: Ident.Idx,
|
||||||
err_ident: Ident.Idx,
|
err_ident: Ident.Idx,
|
||||||
|
true_tag_ident: Ident.Idx,
|
||||||
|
false_tag_ident: Ident.Idx,
|
||||||
deferred_numeric_literals: DeferredNumericLiteral.SafeList.Serialized,
|
deferred_numeric_literals: DeferredNumericLiteral.SafeList.Serialized,
|
||||||
import_mapping_reserved: [6]u64, // Reserved space for import_mapping (AutoHashMap is ~40 bytes), initialized at runtime
|
import_mapping_reserved: [6]u64, // Reserved space for import_mapping (AutoHashMap is ~40 bytes), initialized at runtime
|
||||||
|
|
||||||
|
|
@ -1947,6 +1961,7 @@ pub const Serialized = extern struct {
|
||||||
self.is_ne_ident = env.is_ne_ident;
|
self.is_ne_ident = env.is_ne_ident;
|
||||||
self.builtin_try_ident = env.builtin_try_ident;
|
self.builtin_try_ident = env.builtin_try_ident;
|
||||||
self.builtin_numeral_ident = env.builtin_numeral_ident;
|
self.builtin_numeral_ident = env.builtin_numeral_ident;
|
||||||
|
self.numeral_relative_ident = env.numeral_relative_ident;
|
||||||
self.builtin_str_ident = env.builtin_str_ident;
|
self.builtin_str_ident = env.builtin_str_ident;
|
||||||
self.list_type_ident = env.list_type_ident;
|
self.list_type_ident = env.list_type_ident;
|
||||||
self.box_type_ident = env.box_type_ident;
|
self.box_type_ident = env.box_type_ident;
|
||||||
|
|
@ -1975,6 +1990,8 @@ pub const Serialized = extern struct {
|
||||||
self.unbox_method_ident = env.unbox_method_ident;
|
self.unbox_method_ident = env.unbox_method_ident;
|
||||||
self.ok_ident = env.ok_ident;
|
self.ok_ident = env.ok_ident;
|
||||||
self.err_ident = env.err_ident;
|
self.err_ident = env.err_ident;
|
||||||
|
self.true_tag_ident = env.true_tag_ident;
|
||||||
|
self.false_tag_ident = env.false_tag_ident;
|
||||||
// import_mapping is runtime-only and initialized fresh during deserialization
|
// import_mapping is runtime-only and initialized fresh during deserialization
|
||||||
self.import_mapping_reserved = .{ 0, 0, 0, 0, 0, 0 };
|
self.import_mapping_reserved = .{ 0, 0, 0, 0, 0, 0 };
|
||||||
}
|
}
|
||||||
|
|
@ -2043,6 +2060,7 @@ pub const Serialized = extern struct {
|
||||||
// Fully-qualified type identifiers for type checking and layout generation
|
// Fully-qualified type identifiers for type checking and layout generation
|
||||||
.builtin_try_ident = self.builtin_try_ident,
|
.builtin_try_ident = self.builtin_try_ident,
|
||||||
.builtin_numeral_ident = self.builtin_numeral_ident,
|
.builtin_numeral_ident = self.builtin_numeral_ident,
|
||||||
|
.numeral_relative_ident = self.numeral_relative_ident,
|
||||||
.builtin_str_ident = self.builtin_str_ident,
|
.builtin_str_ident = self.builtin_str_ident,
|
||||||
.list_type_ident = self.list_type_ident,
|
.list_type_ident = self.list_type_ident,
|
||||||
.box_type_ident = self.box_type_ident,
|
.box_type_ident = self.box_type_ident,
|
||||||
|
|
@ -2071,6 +2089,8 @@ pub const Serialized = extern struct {
|
||||||
.unbox_method_ident = self.unbox_method_ident,
|
.unbox_method_ident = self.unbox_method_ident,
|
||||||
.ok_ident = self.ok_ident,
|
.ok_ident = self.ok_ident,
|
||||||
.err_ident = self.err_ident,
|
.err_ident = self.err_ident,
|
||||||
|
.true_tag_ident = self.true_tag_ident,
|
||||||
|
.false_tag_ident = self.false_tag_ident,
|
||||||
.deferred_numeric_literals = self.deferred_numeric_literals.deserialize(offset).*,
|
.deferred_numeric_literals = self.deferred_numeric_literals.deserialize(offset).*,
|
||||||
.import_mapping = types_mod.import_mapping.ImportMapping.init(gpa),
|
.import_mapping = types_mod.import_mapping.ImportMapping.init(gpa),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -855,12 +855,13 @@ fn mkNumeralContent(self: *Self, env: *Env) Allocator.Error!Content {
|
||||||
else
|
else
|
||||||
self.common_idents.module_name; // We're compiling Builtin module itself
|
self.common_idents.module_name; // We're compiling Builtin module itself
|
||||||
|
|
||||||
// Use the relative name "Num.Numeral" (not "Builtin.Num.Numeral") to match the relative_name in TypeHeader
|
// Use the relative name "Num.Numeral" (not "Builtin.Num.Numeral") with origin_module Builtin
|
||||||
// The origin_module field already captures that this type is from Builtin
|
// Use the pre-interned ident from builtin_module to avoid string comparison
|
||||||
const numeral_ident_idx = self.cir.common.findIdent("Num.Numeral") orelse blk: {
|
const numeral_ident_idx = if (self.common_idents.builtin_module) |builtin_mod|
|
||||||
// If not found, create it (this handles tests and edge cases)
|
builtin_mod.numeral_relative_ident
|
||||||
break :blk try @constCast(self.cir).insertIdent(base.Ident.for_text("Num.Numeral"));
|
else
|
||||||
};
|
// When compiling Builtin module itself, use the pre-interned ident from self.cir
|
||||||
|
self.cir.numeral_relative_ident;
|
||||||
const numeral_ident = types_mod.TypeIdent{
|
const numeral_ident = types_mod.TypeIdent{
|
||||||
.ident_idx = numeral_ident_idx,
|
.ident_idx = numeral_ident_idx,
|
||||||
};
|
};
|
||||||
|
|
@ -3563,8 +3564,10 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, expected: Expected)
|
||||||
try self.unifyWith(expr_var, .{ .flex = Flex.init() }, env);
|
try self.unifyWith(expr_var, .{ .flex = Flex.init() }, env);
|
||||||
},
|
},
|
||||||
.e_dbg => |dbg| {
|
.e_dbg => |dbg| {
|
||||||
does_fx = try self.checkExpr(dbg.expr, env, expected) or does_fx;
|
// dbg evaluates its inner expression but returns {} (like expect)
|
||||||
_ = try self.unify(expr_var, ModuleEnv.varFrom(dbg.expr), env);
|
_ = try self.checkExpr(dbg.expr, env, .no_expectation);
|
||||||
|
does_fx = true;
|
||||||
|
try self.unifyWith(expr_var, .{ .structure = .empty_record }, env);
|
||||||
},
|
},
|
||||||
.e_expect => |expect| {
|
.e_expect => |expect| {
|
||||||
does_fx = try self.checkExpr(expect.body, env, expected) or does_fx;
|
does_fx = try self.checkExpr(expect.body, env, expected) or does_fx;
|
||||||
|
|
@ -5146,10 +5149,10 @@ fn checkDeferredStaticDispatchConstraints(self: *Self, env: *Env) std.mem.Alloca
|
||||||
const constraints = self.types.sliceStaticDispatchConstraints(deferred_constraint.constraints);
|
const constraints = self.types.sliceStaticDispatchConstraints(deferred_constraint.constraints);
|
||||||
if (constraints.len > 0) {
|
if (constraints.len > 0) {
|
||||||
const constraint = constraints[0];
|
const constraint = constraints[0];
|
||||||
const constraint_fn_name_bytes = self.cir.getIdent(constraint.fn_name);
|
|
||||||
|
|
||||||
// For is_eq constraints, use the specific equality error message
|
// For is_eq constraints, use the specific equality error message
|
||||||
if (std.mem.eql(u8, constraint_fn_name_bytes, "is_eq")) {
|
// Use ident index comparison instead of string comparison
|
||||||
|
if (constraint.fn_name == self.cir.is_eq_ident) {
|
||||||
try self.reportEqualityError(
|
try self.reportEqualityError(
|
||||||
deferred_constraint.var_,
|
deferred_constraint.var_,
|
||||||
constraint,
|
constraint,
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,9 @@ fn loadCompiledModule(gpa: std.mem.Allocator, bin_data: []const u8, module_name:
|
||||||
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
||||||
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
||||||
.err_ident = common.findIdent("Err") orelse unreachable,
|
.err_ident = common.findIdent("Err") orelse unreachable,
|
||||||
|
.numeral_relative_ident = common.findIdent("Num.Numeral") orelse unreachable,
|
||||||
|
.true_tag_ident = common.findIdent("True") orelse unreachable,
|
||||||
|
.false_tag_ident = common.findIdent("False") orelse unreachable,
|
||||||
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(gpa, 0),
|
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(gpa, 0),
|
||||||
.import_mapping = types.import_mapping.ImportMapping.init(gpa),
|
.import_mapping = types.import_mapping.ImportMapping.init(gpa),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1381,13 +1381,16 @@ test "check type - crash" {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug //
|
// dbg //
|
||||||
|
|
||||||
test "check type - debug" {
|
test "check type - dbg" {
|
||||||
|
// dbg returns {} (not the value it's debugging), so it can be used
|
||||||
|
// as a statement/side-effect without affecting the block's return type
|
||||||
const source =
|
const source =
|
||||||
\\y : U64
|
\\y : U64
|
||||||
\\y = {
|
\\y = {
|
||||||
\\ debug 2
|
\\ dbg 2
|
||||||
|
\\ 42
|
||||||
\\}
|
\\}
|
||||||
\\
|
\\
|
||||||
\\main = {
|
\\main = {
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,46 @@ test "fx platform match with wildcard" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "fx platform dbg missing return value" {
|
||||||
|
const allocator = testing.allocator;
|
||||||
|
|
||||||
|
try ensureRocBinary(allocator);
|
||||||
|
|
||||||
|
// Run an app that uses dbg as the last expression in main!.
|
||||||
|
// dbg is treated as a statement (side-effect only) when it's the final
|
||||||
|
// expression in a block, so the block returns {} as expected by main!.
|
||||||
|
const run_result = try std.process.Child.run(.{
|
||||||
|
.allocator = allocator,
|
||||||
|
.argv = &[_][]const u8{
|
||||||
|
"./zig-out/bin/roc",
|
||||||
|
"--no-cache",
|
||||||
|
"test/fx/dbg_missing_return.roc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
defer allocator.free(run_result.stdout);
|
||||||
|
defer allocator.free(run_result.stderr);
|
||||||
|
|
||||||
|
switch (run_result.term) {
|
||||||
|
.Exited => |code| {
|
||||||
|
if (code != 0) {
|
||||||
|
std.debug.print("Run failed with exit code {}\n", .{code});
|
||||||
|
std.debug.print("STDOUT: {s}\n", .{run_result.stdout});
|
||||||
|
std.debug.print("STDERR: {s}\n", .{run_result.stderr});
|
||||||
|
return error.RunFailed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
std.debug.print("Run terminated abnormally: {}\n", .{run_result.term});
|
||||||
|
std.debug.print("STDOUT: {s}\n", .{run_result.stdout});
|
||||||
|
std.debug.print("STDERR: {s}\n", .{run_result.stderr});
|
||||||
|
return error.RunFailed;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the dbg output was printed
|
||||||
|
try testing.expect(std.mem.indexOf(u8, run_result.stderr, "this should work now") != null);
|
||||||
|
}
|
||||||
|
|
||||||
test "fx platform check unused state var reports correct errors" {
|
test "fx platform check unused state var reports correct errors" {
|
||||||
const allocator = testing.allocator;
|
const allocator = testing.allocator;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,9 @@ test "ModuleEnv.Serialized roundtrip" {
|
||||||
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
||||||
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
||||||
.err_ident = common.findIdent("Err") orelse unreachable,
|
.err_ident = common.findIdent("Err") orelse unreachable,
|
||||||
|
.numeral_relative_ident = common.findIdent("Num.Numeral") orelse unreachable,
|
||||||
|
.true_tag_ident = common.findIdent("True") orelse unreachable,
|
||||||
|
.false_tag_ident = common.findIdent("False") orelse unreachable,
|
||||||
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(deser_alloc, 0),
|
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(deser_alloc, 0),
|
||||||
.import_mapping = types.import_mapping.ImportMapping.init(deser_alloc),
|
.import_mapping = types.import_mapping.ImportMapping.init(deser_alloc),
|
||||||
};
|
};
|
||||||
|
|
@ -167,7 +170,8 @@ test "ModuleEnv.Serialized roundtrip" {
|
||||||
// Plus 3 field/tag identifiers: before_dot, after_dot, ProvidedByCompiler
|
// Plus 3 field/tag identifiers: before_dot, after_dot, ProvidedByCompiler
|
||||||
// Plus 7 more identifiers: tag, payload, is_negative, digits_before_pt, digits_after_pt, box, unbox
|
// Plus 7 more identifiers: tag, payload, is_negative, digits_before_pt, digits_after_pt, box, unbox
|
||||||
// Plus 2 Try tag identifiers: Ok, Err
|
// Plus 2 Try tag identifiers: Ok, Err
|
||||||
try testing.expectEqual(@as(u32, 52), original.common.idents.interner.entry_count);
|
// Plus 3 more identifiers: Num.Numeral (relative), True, False
|
||||||
|
try testing.expectEqual(@as(u32, 55), original.common.idents.interner.entry_count);
|
||||||
try testing.expectEqualStrings("hello", original.getIdent(hello_idx));
|
try testing.expectEqualStrings("hello", original.getIdent(hello_idx));
|
||||||
try testing.expectEqualStrings("world", original.getIdent(world_idx));
|
try testing.expectEqualStrings("world", original.getIdent(world_idx));
|
||||||
|
|
||||||
|
|
@ -176,8 +180,8 @@ test "ModuleEnv.Serialized roundtrip" {
|
||||||
try testing.expectEqual(@as(usize, 2), original.imports.imports.len()); // Should have 2 unique imports
|
try testing.expectEqual(@as(usize, 2), original.imports.imports.len()); // Should have 2 unique imports
|
||||||
|
|
||||||
// First verify that the CommonEnv data was preserved after deserialization
|
// First verify that the CommonEnv data was preserved after deserialization
|
||||||
// Should have same 52 identifiers as original: hello, world, TestModule + 19 well-known identifiers + 18 type identifiers + 3 field/tag identifiers + 7 more identifiers + 2 Try tag identifiers from ModuleEnv.init()
|
// Should have same 55 identifiers as original: hello, world, TestModule + 19 well-known identifiers + 18 type identifiers + 3 field/tag identifiers + 7 more identifiers + 2 Try tag identifiers + 3 additional (Num.Numeral relative, True, False) from ModuleEnv.init()
|
||||||
try testing.expectEqual(@as(u32, 52), env.common.idents.interner.entry_count);
|
try testing.expectEqual(@as(u32, 55), env.common.idents.interner.entry_count);
|
||||||
|
|
||||||
try testing.expectEqual(@as(usize, 1), env.common.exposed_items.count());
|
try testing.expectEqual(@as(usize, 1), env.common.exposed_items.count());
|
||||||
try testing.expectEqual(@as(?u16, 42), env.common.exposed_items.getNodeIndexById(gpa, @as(u32, @bitCast(hello_idx))));
|
try testing.expectEqual(@as(?u16, 42), env.common.exposed_items.getNodeIndexById(gpa, @as(u32, @bitCast(hello_idx))));
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ pub const Interpreter = struct {
|
||||||
defer result_value.decref(&self.runtime_layout_store, roc_ops);
|
defer result_value.decref(&self.runtime_layout_store, roc_ops);
|
||||||
|
|
||||||
// Only copy result if the result type is compatible with ret_ptr
|
// Only copy result if the result type is compatible with ret_ptr
|
||||||
if (try self.shouldCopyResult(result_value, ret_ptr)) {
|
if (try self.shouldCopyResult(result_value, ret_ptr, roc_ops)) {
|
||||||
try result_value.copyToPtr(&self.runtime_layout_store, ret_ptr, roc_ops);
|
try result_value.copyToPtr(&self.runtime_layout_store, ret_ptr, roc_ops);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -470,7 +470,7 @@ pub const Interpreter = struct {
|
||||||
defer result.decref(&self.runtime_layout_store, roc_ops);
|
defer result.decref(&self.runtime_layout_store, roc_ops);
|
||||||
|
|
||||||
// Only copy result if the result type is compatible with ret_ptr
|
// Only copy result if the result type is compatible with ret_ptr
|
||||||
if (try self.shouldCopyResult(result, ret_ptr)) {
|
if (try self.shouldCopyResult(result, ret_ptr, roc_ops)) {
|
||||||
try result.copyToPtr(&self.runtime_layout_store, ret_ptr, roc_ops);
|
try result.copyToPtr(&self.runtime_layout_store, ret_ptr, roc_ops);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -478,7 +478,7 @@ pub const Interpreter = struct {
|
||||||
/// Check if the result should be copied to ret_ptr based on the result's layout.
|
/// Check if the result should be copied to ret_ptr based on the result's layout.
|
||||||
/// Returns false for zero-sized types (nothing to copy).
|
/// Returns false for zero-sized types (nothing to copy).
|
||||||
/// Validates that ret_ptr is properly aligned for the result type.
|
/// Validates that ret_ptr is properly aligned for the result type.
|
||||||
fn shouldCopyResult(self: *Interpreter, result: StackValue, ret_ptr: *anyopaque) !bool {
|
fn shouldCopyResult(self: *Interpreter, result: StackValue, ret_ptr: *anyopaque, _: *RocOps) !bool {
|
||||||
const result_size = self.runtime_layout_store.layoutSize(result.layout);
|
const result_size = self.runtime_layout_store.layoutSize(result.layout);
|
||||||
if (result_size == 0) {
|
if (result_size == 0) {
|
||||||
// Zero-sized types don't need copying
|
// Zero-sized types don't need copying
|
||||||
|
|
@ -493,7 +493,6 @@ pub const Interpreter = struct {
|
||||||
const required_alignment = result.layout.alignment(self.runtime_layout_store.targetUsize());
|
const required_alignment = result.layout.alignment(self.runtime_layout_store.targetUsize());
|
||||||
const ret_addr = @intFromPtr(ret_ptr);
|
const ret_addr = @intFromPtr(ret_ptr);
|
||||||
if (ret_addr % required_alignment.toByteUnits() != 0) {
|
if (ret_addr % required_alignment.toByteUnits() != 0) {
|
||||||
// Type mismatch detected at runtime
|
|
||||||
return error.TypeMismatch;
|
return error.TypeMismatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1363,9 +1362,9 @@ pub const Interpreter = struct {
|
||||||
};
|
};
|
||||||
var resolved = self.runtime_types.resolveVar(rt_var);
|
var resolved = self.runtime_types.resolveVar(rt_var);
|
||||||
// If the type is still flex and this is a True/False tag, use Bool
|
// If the type is still flex and this is a True/False tag, use Bool
|
||||||
|
// Use ident index comparison instead of string comparison
|
||||||
if (resolved.desc.content == .flex) {
|
if (resolved.desc.content == .flex) {
|
||||||
const name_text = self.env.getIdent(zero.name);
|
if (zero.name == self.env.true_tag_ident or zero.name == self.env.false_tag_ident) {
|
||||||
if (std.mem.eql(u8, name_text, "True") or std.mem.eql(u8, name_text, "False")) {
|
|
||||||
rt_var = try self.getCanonicalBoolRuntimeVar();
|
rt_var = try self.getCanonicalBoolRuntimeVar();
|
||||||
resolved = self.runtime_types.resolveVar(rt_var);
|
resolved = self.runtime_types.resolveVar(rt_var);
|
||||||
}
|
}
|
||||||
|
|
@ -1447,8 +1446,7 @@ pub const Interpreter = struct {
|
||||||
var resolved = self.resolveBaseVar(rt_var);
|
var resolved = self.resolveBaseVar(rt_var);
|
||||||
// If the type is still flex and this is a True/False tag, use Bool
|
// If the type is still flex and this is a True/False tag, use Bool
|
||||||
if (resolved.desc.content == .flex) {
|
if (resolved.desc.content == .flex) {
|
||||||
const name_text = self.env.getIdent(tag.name);
|
if (tag.name == self.env.true_tag_ident or tag.name == self.env.false_tag_ident) {
|
||||||
if (std.mem.eql(u8, name_text, "True") or std.mem.eql(u8, name_text, "False")) {
|
|
||||||
rt_var = try self.getCanonicalBoolRuntimeVar();
|
rt_var = try self.getCanonicalBoolRuntimeVar();
|
||||||
resolved = self.resolveBaseVar(rt_var);
|
resolved = self.resolveBaseVar(rt_var);
|
||||||
}
|
}
|
||||||
|
|
@ -1802,13 +1800,19 @@ pub const Interpreter = struct {
|
||||||
return error.Crash;
|
return error.Crash;
|
||||||
},
|
},
|
||||||
.e_dbg => |dbg_expr| {
|
.e_dbg => |dbg_expr| {
|
||||||
|
// Evaluate and print the inner expression
|
||||||
const inner_ct_var = can.ModuleEnv.varFrom(dbg_expr.expr);
|
const inner_ct_var = can.ModuleEnv.varFrom(dbg_expr.expr);
|
||||||
const inner_rt_var = try self.translateTypeVar(self.env, inner_ct_var);
|
const inner_rt_var = try self.translateTypeVar(self.env, inner_ct_var);
|
||||||
const value = try self.evalExprMinimal(dbg_expr.expr, roc_ops, inner_rt_var);
|
const value = try self.evalExprMinimal(dbg_expr.expr, roc_ops, inner_rt_var);
|
||||||
|
defer value.decref(&self.runtime_layout_store, roc_ops);
|
||||||
const rendered = try self.renderValueRocWithType(value, inner_rt_var);
|
const rendered = try self.renderValueRocWithType(value, inner_rt_var);
|
||||||
defer self.allocator.free(rendered);
|
defer self.allocator.free(rendered);
|
||||||
roc_ops.dbg(rendered);
|
roc_ops.dbg(rendered);
|
||||||
return value;
|
// dbg returns {} (empty record) - use same pattern as e_expect
|
||||||
|
const ct_var = can.ModuleEnv.varFrom(expr_idx);
|
||||||
|
const rt_var = try self.translateTypeVar(self.env, ct_var);
|
||||||
|
const layout_val = try self.getRuntimeLayout(rt_var);
|
||||||
|
return try self.pushRaw(layout_val, 0);
|
||||||
},
|
},
|
||||||
// no tag handling in minimal evaluator
|
// no tag handling in minimal evaluator
|
||||||
.e_lambda => |lam| {
|
.e_lambda => |lam| {
|
||||||
|
|
|
||||||
|
|
@ -1111,7 +1111,7 @@ fn parseStmtByType(self: *Parser, statementType: StatementType) Error!AST.Statem
|
||||||
} });
|
} });
|
||||||
return statement_idx;
|
return statement_idx;
|
||||||
},
|
},
|
||||||
.KwDbg, .KwDebug => {
|
.KwDbg => {
|
||||||
const start = self.pos;
|
const start = self.pos;
|
||||||
self.advance();
|
self.advance();
|
||||||
const expr = try self.parseExpr();
|
const expr = try self.parseExpr();
|
||||||
|
|
@ -2145,7 +2145,7 @@ pub fn parseExprWithBp(self: *Parser, min_bp: u8) Error!AST.Expr.Idx {
|
||||||
.branches = branches,
|
.branches = branches,
|
||||||
} });
|
} });
|
||||||
},
|
},
|
||||||
.KwDbg, .KwDebug => {
|
.KwDbg => {
|
||||||
self.advance();
|
self.advance();
|
||||||
const e = try self.parseExpr();
|
const e = try self.parseExpr();
|
||||||
expr = try self.store.addExpr(.{ .dbg = .{
|
expr = try self.store.addExpr(.{ .dbg = .{
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,6 @@ pub const Token = struct {
|
||||||
KwAs,
|
KwAs,
|
||||||
KwCrash,
|
KwCrash,
|
||||||
KwDbg,
|
KwDbg,
|
||||||
KwDebug,
|
|
||||||
KwElse,
|
KwElse,
|
||||||
KwExpect,
|
KwExpect,
|
||||||
KwExposes,
|
KwExposes,
|
||||||
|
|
@ -275,7 +274,6 @@ pub const Token = struct {
|
||||||
.KwAs,
|
.KwAs,
|
||||||
.KwCrash,
|
.KwCrash,
|
||||||
.KwDbg,
|
.KwDbg,
|
||||||
.KwDebug,
|
|
||||||
.KwElse,
|
.KwElse,
|
||||||
.KwExpect,
|
.KwExpect,
|
||||||
.KwExposes,
|
.KwExposes,
|
||||||
|
|
@ -369,7 +367,6 @@ pub const Token = struct {
|
||||||
.{ "as", .KwAs },
|
.{ "as", .KwAs },
|
||||||
.{ "crash", .KwCrash },
|
.{ "crash", .KwCrash },
|
||||||
.{ "dbg", .KwDbg },
|
.{ "dbg", .KwDbg },
|
||||||
.{ "debug", .KwDebug },
|
|
||||||
.{ "else", .KwElse },
|
.{ "else", .KwElse },
|
||||||
.{ "expect", .KwExpect },
|
.{ "expect", .KwExpect },
|
||||||
.{ "exposes", .KwExposes },
|
.{ "exposes", .KwExposes },
|
||||||
|
|
@ -2210,9 +2207,6 @@ fn rebuildBufferForTesting(buf: []const u8, tokens: *TokenizedBuffer, alloc: std
|
||||||
.KwDbg => {
|
.KwDbg => {
|
||||||
try buf2.appendSlice("dbg");
|
try buf2.appendSlice("dbg");
|
||||||
},
|
},
|
||||||
.KwDebug => {
|
|
||||||
try buf2.appendSlice("debug");
|
|
||||||
},
|
|
||||||
.KwElse => {
|
.KwElse => {
|
||||||
try buf2.appendSlice("else");
|
try buf2.appendSlice("else");
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1040,6 +1040,9 @@ fn compileSource(source: []const u8) !CompilerStageData {
|
||||||
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
||||||
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
||||||
.err_ident = common.findIdent("Err") orelse unreachable,
|
.err_ident = common.findIdent("Err") orelse unreachable,
|
||||||
|
.numeral_relative_ident = common.findIdent("Num.Numeral") orelse unreachable,
|
||||||
|
.true_tag_ident = common.findIdent("True") orelse unreachable,
|
||||||
|
.false_tag_ident = common.findIdent("False") orelse unreachable,
|
||||||
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(gpa, 0),
|
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(gpa, 0),
|
||||||
.import_mapping = types.import_mapping.ImportMapping.init(gpa),
|
.import_mapping = types.import_mapping.ImportMapping.init(gpa),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,9 @@ fn loadCompiledModule(gpa: std.mem.Allocator, bin_data: []const u8, module_name:
|
||||||
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
.unbox_method_ident = common.findIdent("unbox") orelse unreachable,
|
||||||
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
.ok_ident = common.findIdent("Ok") orelse unreachable,
|
||||||
.err_ident = common.findIdent("Err") orelse unreachable,
|
.err_ident = common.findIdent("Err") orelse unreachable,
|
||||||
|
.numeral_relative_ident = common.findIdent("Num.Numeral") orelse unreachable,
|
||||||
|
.true_tag_ident = common.findIdent("True") orelse unreachable,
|
||||||
|
.false_tag_ident = common.findIdent("False") orelse unreachable,
|
||||||
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(gpa, 0),
|
.deferred_numeric_literals = try ModuleEnv.DeferredNumericLiteral.SafeList.initCapacity(gpa, 0),
|
||||||
.import_mapping = types.import_mapping.ImportMapping.init(gpa),
|
.import_mapping = types.import_mapping.ImportMapping.init(gpa),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ pub const CommonMisspellings = struct {
|
||||||
.{ "case", "`case` is not a keyword in Roc. Use `match` for pattern matching." },
|
.{ "case", "`case` is not a keyword in Roc. Use `match` for pattern matching." },
|
||||||
.{ "switch", "`switch` is not a keyword in Roc. Use `match` for pattern matching." },
|
.{ "switch", "`switch` is not a keyword in Roc. Use `match` for pattern matching." },
|
||||||
.{ "when", "`when` is not a keyword in Roc. Use `match` for pattern matching." },
|
.{ "when", "`when` is not a keyword in Roc. Use `match` for pattern matching." },
|
||||||
|
.{ "debug", "`debug` is not a keyword in Roc. Use `dbg` for debug printing." },
|
||||||
.{ "then", "`then` is not a keyword in Roc. You can put the first branch of an `if` immediately after the condition, e.g. `if (condition) then_branch else else_branch`" },
|
.{ "then", "`then` is not a keyword in Roc. You can put the first branch of an `if` immediately after the condition, e.g. `if (condition) then_branch else else_branch`" },
|
||||||
.{ "elif", "Roc uses `else if` for chaining conditions, not `elif`." },
|
.{ "elif", "Roc uses `else if` for chaining conditions, not `elif`." },
|
||||||
.{ "elseif", "Roc uses `else if` (two words) for chaining conditions." },
|
.{ "elseif", "Roc uses `else if` (two words) for chaining conditions." },
|
||||||
|
|
@ -107,7 +108,7 @@ test "identifier misspellings lookup" {
|
||||||
const tip = CommonMisspellings.getIdentifierTip("case");
|
const tip = CommonMisspellings.getIdentifierTip("case");
|
||||||
try std.testing.expect(tip != null);
|
try std.testing.expect(tip != null);
|
||||||
try std.testing.expectEqualStrings(
|
try std.testing.expectEqualStrings(
|
||||||
"`case` is not a keyword in Roc. Use `when` for pattern matching.",
|
"`case` is not a keyword in Roc. Use `match` for pattern matching.",
|
||||||
tip.?,
|
tip.?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
test/fx/dbg_missing_return.roc
Normal file
7
test/fx/dbg_missing_return.roc
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
app [main!] { pf: platform "./platform/main.roc" }
|
||||||
|
|
||||||
|
import pf.Stdout
|
||||||
|
|
||||||
|
main! = || {
|
||||||
|
dbg "this should work now"
|
||||||
|
}
|
||||||
|
|
@ -433,7 +433,8 @@ fn setupWasm(gpa: std.mem.Allocator, arena: std.mem.Allocator, wasm_path: []cons
|
||||||
// Create and instantiate the module instance using the gpa allocator for the VM
|
// Create and instantiate the module instance using the gpa allocator for the VM
|
||||||
var module_instance = try bytebox.createModuleInstance(.Stack, module_def, gpa);
|
var module_instance = try bytebox.createModuleInstance(.Stack, module_def, gpa);
|
||||||
errdefer module_instance.destroy();
|
errdefer module_instance.destroy();
|
||||||
try module_instance.instantiate(.{});
|
// Use a larger stack size (256 KB instead of default 128 KB) to accommodate complex interpreter code
|
||||||
|
try module_instance.instantiate(.{ .stack_size = 1024 * 256 });
|
||||||
|
|
||||||
logDebug("[INFO] WASM module instantiated successfully.\n", .{});
|
logDebug("[INFO] WASM module instantiated successfully.\n", .{});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,7 @@ UNUSED VALUE - fuzz_crash_023.md:1:1:1:1
|
||||||
TYPE MISMATCH - fuzz_crash_023.md:155:2:157:3
|
TYPE MISMATCH - fuzz_crash_023.md:155:2:157:3
|
||||||
UNUSED VALUE - fuzz_crash_023.md:155:2:157:3
|
UNUSED VALUE - fuzz_crash_023.md:155:2:157:3
|
||||||
UNUSED VALUE - fuzz_crash_023.md:178:42:178:45
|
UNUSED VALUE - fuzz_crash_023.md:178:42:178:45
|
||||||
|
TYPE MISMATCH - fuzz_crash_023.md:144:9:196:2
|
||||||
# PROBLEMS
|
# PROBLEMS
|
||||||
**PARSE ERROR**
|
**PARSE ERROR**
|
||||||
A parsing error occurred: `expected_expr_record_field_name`
|
A parsing error occurred: `expected_expr_record_field_name`
|
||||||
|
|
@ -1046,6 +1047,71 @@ This expression produces a value, but it's not being used:
|
||||||
It has the type:
|
It has the type:
|
||||||
_[Blue]_others_
|
_[Blue]_others_
|
||||||
|
|
||||||
|
**TYPE MISMATCH**
|
||||||
|
This expression is used in an unexpected way:
|
||||||
|
**fuzz_crash_023.md:144:9:196:2:**
|
||||||
|
```roc
|
||||||
|
main! = |_| { # Yeah I can leave a comment here
|
||||||
|
world = "World"
|
||||||
|
var number = 123
|
||||||
|
expect blah == 1
|
||||||
|
tag = Blue
|
||||||
|
return # Comment after return keyword
|
||||||
|
tag # Comment after return statement
|
||||||
|
|
||||||
|
# Just a random comment!
|
||||||
|
|
||||||
|
...
|
||||||
|
match_time(
|
||||||
|
..., # Single args with comment
|
||||||
|
)
|
||||||
|
some_func(
|
||||||
|
dbg # After debug
|
||||||
|
42, # After debug expr
|
||||||
|
)
|
||||||
|
crash # Comment after crash keyword
|
||||||
|
"Unreachable!" # Comment after crash statement
|
||||||
|
tag_with_payload = Ok(number)
|
||||||
|
interpolated = "Hello, ${world}"
|
||||||
|
list = [
|
||||||
|
add_one(
|
||||||
|
dbg # After dbg in list
|
||||||
|
number, # after dbg expr as arg
|
||||||
|
), # Comment one
|
||||||
|
456, # Comment two
|
||||||
|
789, # Comment three
|
||||||
|
]
|
||||||
|
for n in list {
|
||||||
|
Stdout.line!("Adding ${n} to ${number}")
|
||||||
|
number = number + n
|
||||||
|
}
|
||||||
|
record = { foo: 123, bar: "Hello", ;az: tag, qux: Ok(world), punned }
|
||||||
|
tuple = (123, "World", tag, Ok(world), (nested, tuple), [1, 2, 3])
|
||||||
|
multiline_tuple = (
|
||||||
|
123,
|
||||||
|
"World",
|
||||||
|
tag1,
|
||||||
|
Ok(world), # This one has a comment
|
||||||
|
(nested, tuple),
|
||||||
|
[1, 2, 3],
|
||||||
|
)
|
||||||
|
bin_op_result = Err(foo) ?? 12 > 5 * 5 or 13 + 2 < 5 and 10 - 1 >= 16 or 12 <= 3 / 5
|
||||||
|
static_dispatch_style = some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?
|
||||||
|
Stdout.line!(interpolated)?
|
||||||
|
Stdout.line!(
|
||||||
|
"How about ${ # Comment after string interpolation open
|
||||||
|
Num.toStr(number) # Comment after string interpolation expr
|
||||||
|
} as a string?",
|
||||||
|
)
|
||||||
|
} # Comment after top-level decl
|
||||||
|
```
|
||||||
|
|
||||||
|
It has the type:
|
||||||
|
_List(Error) => Error_
|
||||||
|
|
||||||
|
But the type annotation says it should have the type:
|
||||||
|
_List(Error) -> Error_
|
||||||
|
|
||||||
# TOKENS
|
# TOKENS
|
||||||
~~~zig
|
~~~zig
|
||||||
KwApp,OpenSquare,LowerIdent,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,
|
KwApp,OpenSquare,LowerIdent,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,
|
||||||
|
|
@ -2577,7 +2643,7 @@ expect {
|
||||||
(patt (type "Error -> U64"))
|
(patt (type "Error -> U64"))
|
||||||
(patt (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
(patt (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
||||||
(patt (type "Error"))
|
(patt (type "Error"))
|
||||||
(patt (type "List(Error) -> Error"))
|
(patt (type "Error"))
|
||||||
(patt (type "{}"))
|
(patt (type "{}"))
|
||||||
(patt (type "Error")))
|
(patt (type "Error")))
|
||||||
(type_decls
|
(type_decls
|
||||||
|
|
@ -2624,7 +2690,7 @@ expect {
|
||||||
(expr (type "Error -> U64"))
|
(expr (type "Error -> U64"))
|
||||||
(expr (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
(expr (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
||||||
(expr (type "Error"))
|
(expr (type "Error"))
|
||||||
(expr (type "List(Error) -> Error"))
|
(expr (type "Error"))
|
||||||
(expr (type "{}"))
|
(expr (type "{}"))
|
||||||
(expr (type "Error"))))
|
(expr (type "Error"))))
|
||||||
~~~
|
~~~
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,7 @@ UNUSED VALUE - fuzz_crash_027.md:1:1:1:1
|
||||||
TYPE MISMATCH - fuzz_crash_027.md:111:2:113:3
|
TYPE MISMATCH - fuzz_crash_027.md:111:2:113:3
|
||||||
UNUSED VALUE - fuzz_crash_027.md:111:2:113:3
|
UNUSED VALUE - fuzz_crash_027.md:111:2:113:3
|
||||||
TYPE MISMATCH - fuzz_crash_027.md:143:2:147:3
|
TYPE MISMATCH - fuzz_crash_027.md:143:2:147:3
|
||||||
|
TYPE MISMATCH - fuzz_crash_027.md:100:9:148:2
|
||||||
# PROBLEMS
|
# PROBLEMS
|
||||||
**LEADING ZERO**
|
**LEADING ZERO**
|
||||||
Numbers cannot have leading zeros.
|
Numbers cannot have leading zeros.
|
||||||
|
|
@ -972,6 +973,67 @@ It has the type:
|
||||||
But the type annotation says it should have the type:
|
But the type annotation says it should have the type:
|
||||||
_Try(d)_
|
_Try(d)_
|
||||||
|
|
||||||
|
**TYPE MISMATCH**
|
||||||
|
This expression is used in an unexpected way:
|
||||||
|
**fuzz_crash_027.md:100:9:148:2:**
|
||||||
|
```roc
|
||||||
|
main! = |_| { # Yeah Ie
|
||||||
|
world = "World"
|
||||||
|
var number = 123
|
||||||
|
expect blah == 1
|
||||||
|
tag = Blue
|
||||||
|
return # Comd
|
||||||
|
tag
|
||||||
|
|
||||||
|
# Jusnt!
|
||||||
|
|
||||||
|
...
|
||||||
|
match_time(
|
||||||
|
..., #
|
||||||
|
)
|
||||||
|
some_func(
|
||||||
|
dbg # bug
|
||||||
|
42, # Aft expr
|
||||||
|
)
|
||||||
|
crash "Unreachtement
|
||||||
|
tag_with = Ok(number)
|
||||||
|
ited = "Hello, ${world}"
|
||||||
|
list = [
|
||||||
|
add_one(
|
||||||
|
dbg # Afin list
|
||||||
|
e[, # afarg
|
||||||
|
), 456, # ee
|
||||||
|
]
|
||||||
|
for n in list {
|
||||||
|
line!("Adding ${n} to ${number}")
|
||||||
|
number = number + n
|
||||||
|
}
|
||||||
|
record = { foo: 123, bar: "Hello", baz: tag, qux: Ok(world), punned }
|
||||||
|
tuple = (123, "World", tag, Ok(world), (nested, tuple), [1, 2, 3])
|
||||||
|
m_tuple = (
|
||||||
|
123,
|
||||||
|
"World",
|
||||||
|
tag1,
|
||||||
|
Ok(world), # Thisnt
|
||||||
|
(nested, tuple),
|
||||||
|
[1, 2, 3],
|
||||||
|
)
|
||||||
|
bsult = Err(foo) ?? 12 > 5 * 5 or 13 + 2 < 5 and 10 - 1 >= 16 or 12 <= 3 / 5
|
||||||
|
stale = some_fn(arg1)?.statod()?.ned()?.recd?
|
||||||
|
Stdoline!(
|
||||||
|
"How about ${ #
|
||||||
|
Num.toStr(number) # on expr
|
||||||
|
} as a",
|
||||||
|
)
|
||||||
|
} # Commenl decl
|
||||||
|
```
|
||||||
|
|
||||||
|
It has the type:
|
||||||
|
_List(Error) => Error_
|
||||||
|
|
||||||
|
But the type annotation says it should have the type:
|
||||||
|
_List(Error) -> Error_
|
||||||
|
|
||||||
# TOKENS
|
# TOKENS
|
||||||
~~~zig
|
~~~zig
|
||||||
KwApp,OpenSquare,LowerIdent,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,
|
KwApp,OpenSquare,LowerIdent,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,
|
||||||
|
|
@ -2255,7 +2317,7 @@ expect {
|
||||||
(patt (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
(patt (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
||||||
(patt (type "Error -> U64"))
|
(patt (type "Error -> U64"))
|
||||||
(patt (type "[Red, Blue][ProvidedByCompiler], _arg -> Error"))
|
(patt (type "[Red, Blue][ProvidedByCompiler], _arg -> Error"))
|
||||||
(patt (type "List(Error) -> Error"))
|
(patt (type "Error"))
|
||||||
(patt (type "{}"))
|
(patt (type "{}"))
|
||||||
(patt (type "Error")))
|
(patt (type "Error")))
|
||||||
(type_decls
|
(type_decls
|
||||||
|
|
@ -2292,7 +2354,7 @@ expect {
|
||||||
(expr (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
(expr (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
||||||
(expr (type "Error -> U64"))
|
(expr (type "Error -> U64"))
|
||||||
(expr (type "[Red, Blue][ProvidedByCompiler], _arg -> Error"))
|
(expr (type "[Red, Blue][ProvidedByCompiler], _arg -> Error"))
|
||||||
(expr (type "List(Error) -> Error"))
|
(expr (type "Error"))
|
||||||
(expr (type "{}"))
|
(expr (type "{}"))
|
||||||
(expr (type "Error"))))
|
(expr (type "Error"))))
|
||||||
~~~
|
~~~
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -13,16 +13,16 @@ type=repl
|
||||||
» Try.Ok(1) != Try.Ok(1)
|
» Try.Ok(1) != Try.Ok(1)
|
||||||
~~~
|
~~~
|
||||||
# OUTPUT
|
# OUTPUT
|
||||||
Evaluation error: error.BugUnboxedRigidVar
|
Crash: e_closure: failed to resolve capture value
|
||||||
---
|
---
|
||||||
Evaluation error: error.BugUnboxedRigidVar
|
Crash: e_closure: failed to resolve capture value
|
||||||
---
|
---
|
||||||
Evaluation error: error.BugUnboxedRigidVar
|
Crash: e_closure: failed to resolve capture value
|
||||||
---
|
---
|
||||||
Evaluation error: error.BugUnboxedRigidVar
|
Crash: e_closure: failed to resolve capture value
|
||||||
---
|
---
|
||||||
Evaluation error: error.BugUnboxedRigidVar
|
Crash: e_closure: failed to resolve capture value
|
||||||
---
|
---
|
||||||
Evaluation error: error.BugUnboxedRigidVar
|
Crash: e_closure: failed to resolve capture value
|
||||||
# PROBLEMS
|
# PROBLEMS
|
||||||
NIL
|
NIL
|
||||||
|
|
|
||||||
84
test/snapshots/statement/dbg_as_arg.md
Normal file
84
test/snapshots/statement/dbg_as_arg.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# META
|
||||||
|
~~~ini
|
||||||
|
description=Debug as function argument
|
||||||
|
type=snippet
|
||||||
|
~~~
|
||||||
|
# SOURCE
|
||||||
|
~~~roc
|
||||||
|
foo = |f| f(dbg 42)
|
||||||
|
bar = |f| f(dbg(42))
|
||||||
|
~~~
|
||||||
|
# EXPECTED
|
||||||
|
NIL
|
||||||
|
# PROBLEMS
|
||||||
|
NIL
|
||||||
|
# TOKENS
|
||||||
|
~~~zig
|
||||||
|
LowerIdent,OpAssign,OpBar,LowerIdent,OpBar,LowerIdent,NoSpaceOpenRound,KwDbg,Int,CloseRound,
|
||||||
|
LowerIdent,OpAssign,OpBar,LowerIdent,OpBar,LowerIdent,NoSpaceOpenRound,KwDbg,NoSpaceOpenRound,Int,CloseRound,CloseRound,
|
||||||
|
EndOfFile,
|
||||||
|
~~~
|
||||||
|
# PARSE
|
||||||
|
~~~clojure
|
||||||
|
(file
|
||||||
|
(type-module)
|
||||||
|
(statements
|
||||||
|
(s-decl
|
||||||
|
(p-ident (raw "foo"))
|
||||||
|
(e-lambda
|
||||||
|
(args
|
||||||
|
(p-ident (raw "f")))
|
||||||
|
(e-apply
|
||||||
|
(e-ident (raw "f"))
|
||||||
|
(e-dbg
|
||||||
|
(e-int (raw "42"))))))
|
||||||
|
(s-decl
|
||||||
|
(p-ident (raw "bar"))
|
||||||
|
(e-lambda
|
||||||
|
(args
|
||||||
|
(p-ident (raw "f")))
|
||||||
|
(e-apply
|
||||||
|
(e-ident (raw "f"))
|
||||||
|
(e-dbg
|
||||||
|
(e-tuple
|
||||||
|
(e-int (raw "42")))))))))
|
||||||
|
~~~
|
||||||
|
# FORMATTED
|
||||||
|
~~~roc
|
||||||
|
foo = |f| f(dbg 42)
|
||||||
|
bar = |f| f(dbg (42))
|
||||||
|
~~~
|
||||||
|
# CANONICALIZE
|
||||||
|
~~~clojure
|
||||||
|
(can-ir
|
||||||
|
(d-let
|
||||||
|
(p-assign (ident "foo"))
|
||||||
|
(e-lambda
|
||||||
|
(args
|
||||||
|
(p-assign (ident "f")))
|
||||||
|
(e-call
|
||||||
|
(e-lookup-local
|
||||||
|
(p-assign (ident "f")))
|
||||||
|
(e-dbg
|
||||||
|
(e-num (value "42"))))))
|
||||||
|
(d-let
|
||||||
|
(p-assign (ident "bar"))
|
||||||
|
(e-lambda
|
||||||
|
(args
|
||||||
|
(p-assign (ident "f")))
|
||||||
|
(e-call
|
||||||
|
(e-lookup-local
|
||||||
|
(p-assign (ident "f")))
|
||||||
|
(e-dbg
|
||||||
|
(e-num (value "42")))))))
|
||||||
|
~~~
|
||||||
|
# TYPES
|
||||||
|
~~~clojure
|
||||||
|
(inferred-types
|
||||||
|
(defs
|
||||||
|
(patt (type "({} -> a) => a"))
|
||||||
|
(patt (type "({} -> a) => a")))
|
||||||
|
(expressions
|
||||||
|
(expr (type "({} -> a) => a"))
|
||||||
|
(expr (type "({} -> a) => a"))))
|
||||||
|
~~~
|
||||||
63
test/snapshots/statement/dbg_last_in_block.md
Normal file
63
test/snapshots/statement/dbg_last_in_block.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# META
|
||||||
|
~~~ini
|
||||||
|
description=Debug as last expression in block should return {}
|
||||||
|
type=snippet
|
||||||
|
~~~
|
||||||
|
# SOURCE
|
||||||
|
~~~roc
|
||||||
|
main = || {
|
||||||
|
dbg "hello"
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
# EXPECTED
|
||||||
|
NIL
|
||||||
|
# PROBLEMS
|
||||||
|
NIL
|
||||||
|
# TOKENS
|
||||||
|
~~~zig
|
||||||
|
LowerIdent,OpAssign,OpBar,OpBar,OpenCurly,
|
||||||
|
KwDbg,StringStart,StringPart,StringEnd,
|
||||||
|
CloseCurly,
|
||||||
|
EndOfFile,
|
||||||
|
~~~
|
||||||
|
# PARSE
|
||||||
|
~~~clojure
|
||||||
|
(file
|
||||||
|
(type-module)
|
||||||
|
(statements
|
||||||
|
(s-decl
|
||||||
|
(p-ident (raw "main"))
|
||||||
|
(e-lambda
|
||||||
|
(args)
|
||||||
|
(e-block
|
||||||
|
(statements
|
||||||
|
(s-dbg
|
||||||
|
(e-string
|
||||||
|
(e-string-part (raw "hello"))))))))))
|
||||||
|
~~~
|
||||||
|
# FORMATTED
|
||||||
|
~~~roc
|
||||||
|
main = || {
|
||||||
|
dbg "hello"
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
# CANONICALIZE
|
||||||
|
~~~clojure
|
||||||
|
(can-ir
|
||||||
|
(d-let
|
||||||
|
(p-assign (ident "main"))
|
||||||
|
(e-lambda
|
||||||
|
(args)
|
||||||
|
(e-block
|
||||||
|
(e-dbg
|
||||||
|
(e-string
|
||||||
|
(e-literal (string "hello"))))))))
|
||||||
|
~~~
|
||||||
|
# TYPES
|
||||||
|
~~~clojure
|
||||||
|
(inferred-types
|
||||||
|
(defs
|
||||||
|
(patt (type "({}) => {}")))
|
||||||
|
(expressions
|
||||||
|
(expr (type "({}) => {}"))))
|
||||||
|
~~~
|
||||||
|
|
@ -62,7 +62,7 @@ test = {
|
||||||
~~~clojure
|
~~~clojure
|
||||||
(inferred-types
|
(inferred-types
|
||||||
(defs
|
(defs
|
||||||
(patt (type "a where [a.from_numeral : Numeral -> Try(a, [InvalidNumeral(Str)])]")))
|
(patt (type "{}")))
|
||||||
(expressions
|
(expressions
|
||||||
(expr (type "a where [a.from_numeral : Numeral -> Try(a, [InvalidNumeral(Str)])]"))))
|
(expr (type "{}"))))
|
||||||
~~~
|
~~~
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ foo = |num| {
|
||||||
~~~clojure
|
~~~clojure
|
||||||
(inferred-types
|
(inferred-types
|
||||||
(defs
|
(defs
|
||||||
(patt (type "a -> a where [a.to_str : a -> b]")))
|
(patt (type "a => {} where [a.to_str : a -> _ret]")))
|
||||||
(expressions
|
(expressions
|
||||||
(expr (type "a -> a where [a.to_str : a -> b]"))))
|
(expr (type "a => {} where [a.to_str : a -> _ret]"))))
|
||||||
~~~
|
~~~
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,7 @@ INCOMPATIBLE MATCH PATTERNS - syntax_grab_bag.md:84:2:84:2
|
||||||
UNUSED VALUE - syntax_grab_bag.md:1:1:1:1
|
UNUSED VALUE - syntax_grab_bag.md:1:1:1:1
|
||||||
TYPE MISMATCH - syntax_grab_bag.md:155:2:157:3
|
TYPE MISMATCH - syntax_grab_bag.md:155:2:157:3
|
||||||
UNUSED VALUE - syntax_grab_bag.md:155:2:157:3
|
UNUSED VALUE - syntax_grab_bag.md:155:2:157:3
|
||||||
|
TYPE MISMATCH - syntax_grab_bag.md:144:9:196:2
|
||||||
# PROBLEMS
|
# PROBLEMS
|
||||||
**UNDECLARED TYPE**
|
**UNDECLARED TYPE**
|
||||||
The type _Bar_ is not declared in this scope.
|
The type _Bar_ is not declared in this scope.
|
||||||
|
|
@ -926,6 +927,71 @@ This expression produces a value, but it's not being used:
|
||||||
It has the type:
|
It has the type:
|
||||||
_d_
|
_d_
|
||||||
|
|
||||||
|
**TYPE MISMATCH**
|
||||||
|
This expression is used in an unexpected way:
|
||||||
|
**syntax_grab_bag.md:144:9:196:2:**
|
||||||
|
```roc
|
||||||
|
main! = |_| { # Yeah I can leave a comment here
|
||||||
|
world = "World"
|
||||||
|
var number = 123
|
||||||
|
expect blah == 1
|
||||||
|
tag = Blue
|
||||||
|
return # Comment after return keyword
|
||||||
|
tag # Comment after return statement
|
||||||
|
|
||||||
|
# Just a random comment!
|
||||||
|
|
||||||
|
...
|
||||||
|
match_time(
|
||||||
|
..., # Single args with comment
|
||||||
|
)
|
||||||
|
some_func(
|
||||||
|
dbg # After debug
|
||||||
|
42, # After debug expr
|
||||||
|
)
|
||||||
|
crash # Comment after crash keyword
|
||||||
|
"Unreachable!" # Comment after crash statement
|
||||||
|
tag_with_payload = Ok(number)
|
||||||
|
interpolated = "Hello, ${world}"
|
||||||
|
list = [
|
||||||
|
add_one(
|
||||||
|
dbg # After dbg in list
|
||||||
|
number, # after dbg expr as arg
|
||||||
|
), # Comment one
|
||||||
|
456, # Comment two
|
||||||
|
789, # Comment three
|
||||||
|
]
|
||||||
|
for n in list {
|
||||||
|
Stdout.line!("Adding ${n} to ${number}")
|
||||||
|
number = number + n
|
||||||
|
}
|
||||||
|
record = { foo: 123, bar: "Hello", baz: tag, qux: Ok(world), punned }
|
||||||
|
tuple = (123, "World", tag, Ok(world), (nested, tuple), [1, 2, 3])
|
||||||
|
multiline_tuple = (
|
||||||
|
123,
|
||||||
|
"World",
|
||||||
|
tag1,
|
||||||
|
Ok(world), # This one has a comment
|
||||||
|
(nested, tuple),
|
||||||
|
[1, 2, 3],
|
||||||
|
)
|
||||||
|
bin_op_result = Err(foo) ?? 12 > 5 * 5 or 13 + 2 < 5 and 10 - 1 >= 16 or 12 <= 3 / 5
|
||||||
|
static_dispatch_style = some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?
|
||||||
|
Stdout.line!(interpolated)?
|
||||||
|
Stdout.line!(
|
||||||
|
"How about ${ # Comment after string interpolation open
|
||||||
|
Num.toStr(number) # Comment after string interpolation expr
|
||||||
|
} as a string?",
|
||||||
|
)
|
||||||
|
} # Comment after top-level decl
|
||||||
|
```
|
||||||
|
|
||||||
|
It has the type:
|
||||||
|
_List(Error) => Error_
|
||||||
|
|
||||||
|
But the type annotation says it should have the type:
|
||||||
|
_List(Error) -> Error_
|
||||||
|
|
||||||
# TOKENS
|
# TOKENS
|
||||||
~~~zig
|
~~~zig
|
||||||
KwApp,OpenSquare,LowerIdent,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,
|
KwApp,OpenSquare,LowerIdent,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,
|
||||||
|
|
@ -2462,7 +2528,7 @@ expect {
|
||||||
(patt (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
(patt (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
||||||
(patt (type "Error -> U64"))
|
(patt (type "Error -> U64"))
|
||||||
(patt (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
(patt (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
||||||
(patt (type "List(Error) -> Error"))
|
(patt (type "Error"))
|
||||||
(patt (type "{}"))
|
(patt (type "{}"))
|
||||||
(patt (type "Error")))
|
(patt (type "Error")))
|
||||||
(type_decls
|
(type_decls
|
||||||
|
|
@ -2508,7 +2574,7 @@ expect {
|
||||||
(expr (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
(expr (type "Bool -> d where [d.from_numeral : Numeral -> Try(d, [InvalidNumeral(Str)])]"))
|
||||||
(expr (type "Error -> U64"))
|
(expr (type "Error -> U64"))
|
||||||
(expr (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
(expr (type "[Red][Blue, Green][ProvidedByCompiler], _arg -> Error"))
|
||||||
(expr (type "List(Error) -> Error"))
|
(expr (type "Error"))
|
||||||
(expr (type "{}"))
|
(expr (type "{}"))
|
||||||
(expr (type "Error"))))
|
(expr (type "Error"))))
|
||||||
~~~
|
~~~
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue