Merge remote-tracking branch 'remote/misc-fixes' into refcount-tracking

This commit is contained in:
Luke Boswell 2025-11-30 13:50:30 +11:00
commit a7545cd872
No known key found for this signature in database
GPG key ID: 54A7324B1B975757
18 changed files with 974 additions and 2433 deletions

View file

@ -5070,13 +5070,171 @@ pub fn canonicalizeExpr(
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
},
.suffix_single_question => |_| {
const feature = try self.env.insertString("canonicalize suffix_single_question expression");
const expr_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{ .not_implemented = .{
.feature = feature,
.region = Region.zero(),
} });
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = null };
.suffix_single_question => |unary| {
// Desugar `expr?` into:
// match expr {
// Ok(#ok) => #ok,
// Err(#err) => return Err(#err),
// }
const region = self.parse_ir.tokenizedRegionToRegion(unary.region);
const free_vars_start = self.scratch_free_vars.top();
// Canonicalize the inner expression (the expression before `?`)
const can_cond = try self.canonicalizeExpr(unary.expr) orelse return null;
// Use pre-interned identifiers for the Ok/Err values and tag names
const ok_val_ident = self.env.idents.question_ok;
const err_val_ident = self.env.idents.question_err;
const ok_tag_ident = self.env.idents.ok;
const err_tag_ident = self.env.idents.err;
// Mark the start of scratch match branches
const scratch_top = self.env.store.scratchMatchBranchTop();
// === Branch 1: Ok(#ok) => #ok ===
{
// Enter a new scope for this branch
try self.scopeEnter(self.env.gpa, false);
defer self.scopeExit(self.env.gpa) catch {};
// Create the assign pattern for the Ok value
const ok_assign_pattern_idx = try self.env.addPattern(Pattern{
.assign = .{ .ident = ok_val_ident },
}, region);
// Introduce the pattern into scope
_ = try self.scopeIntroduceInternal(self.env.gpa, .ident, ok_val_ident, ok_assign_pattern_idx, false, true);
// Create pattern span for Ok tag argument
const ok_patterns_start = self.env.store.scratchPatternTop();
try self.env.store.addScratchPattern(ok_assign_pattern_idx);
const ok_args_span = try self.env.store.patternSpanFrom(ok_patterns_start);
// Create the Ok tag pattern: Ok(#ok)
const ok_tag_pattern_idx = try self.env.addPattern(Pattern{
.applied_tag = .{
.name = ok_tag_ident,
.args = ok_args_span,
},
}, region);
// Create branch pattern
const branch_pat_scratch_top = self.env.store.scratchMatchBranchPatternTop();
const ok_branch_pattern_idx = try self.env.addMatchBranchPattern(Expr.Match.BranchPattern{
.pattern = ok_tag_pattern_idx,
.degenerate = false,
}, region);
try self.env.store.addScratchMatchBranchPattern(ok_branch_pattern_idx);
const ok_branch_pat_span = try self.env.store.matchBranchPatternSpanFrom(branch_pat_scratch_top);
// Create the branch body: lookup #ok
const ok_lookup_idx = try self.env.addExpr(CIR.Expr{ .e_lookup_local = .{
.pattern_idx = ok_assign_pattern_idx,
} }, region);
// Mark the pattern as used
try self.used_patterns.put(self.env.gpa, ok_assign_pattern_idx, {});
// Create the Ok branch
const ok_branch_idx = try self.env.addMatchBranch(
Expr.Match.Branch{
.patterns = ok_branch_pat_span,
.value = ok_lookup_idx,
.guard = null,
.redundant = @enumFromInt(0),
},
region,
);
try self.env.store.addScratchMatchBranch(ok_branch_idx);
}
// === Branch 2: Err(#err) => return Err(#err) ===
{
// Enter a new scope for this branch
try self.scopeEnter(self.env.gpa, false);
defer self.scopeExit(self.env.gpa) catch {};
// Create the assign pattern for the Err value
const err_assign_pattern_idx = try self.env.addPattern(Pattern{
.assign = .{ .ident = err_val_ident },
}, region);
// Introduce the pattern into scope
_ = try self.scopeIntroduceInternal(self.env.gpa, .ident, err_val_ident, err_assign_pattern_idx, false, true);
// Create pattern span for Err tag argument
const err_patterns_start = self.env.store.scratchPatternTop();
try self.env.store.addScratchPattern(err_assign_pattern_idx);
const err_args_span = try self.env.store.patternSpanFrom(err_patterns_start);
// Create the Err tag pattern: Err(#err)
const err_tag_pattern_idx = try self.env.addPattern(Pattern{
.applied_tag = .{
.name = err_tag_ident,
.args = err_args_span,
},
}, region);
// Create branch pattern
const branch_pat_scratch_top = self.env.store.scratchMatchBranchPatternTop();
const err_branch_pattern_idx = try self.env.addMatchBranchPattern(Expr.Match.BranchPattern{
.pattern = err_tag_pattern_idx,
.degenerate = false,
}, region);
try self.env.store.addScratchMatchBranchPattern(err_branch_pattern_idx);
const err_branch_pat_span = try self.env.store.matchBranchPatternSpanFrom(branch_pat_scratch_top);
// Create the branch body: return Err(#err)
// First, create lookup for #err
const err_lookup_idx = try self.env.addExpr(CIR.Expr{ .e_lookup_local = .{
.pattern_idx = err_assign_pattern_idx,
} }, region);
// Mark the pattern as used
try self.used_patterns.put(self.env.gpa, err_assign_pattern_idx, {});
// Create Err(#err) tag expression
const err_tag_args_start = self.env.store.scratchExprTop();
try self.env.store.addScratchExpr(err_lookup_idx);
const err_tag_args_span = try self.env.store.exprSpanFrom(err_tag_args_start);
const err_tag_expr_idx = try self.env.addExpr(CIR.Expr{
.e_tag = .{
.name = err_tag_ident,
.args = err_tag_args_span,
},
}, region);
// Create return Err(#err) expression
const return_expr_idx = try self.env.addExpr(CIR.Expr{ .e_return = .{
.expr = err_tag_expr_idx,
} }, region);
// Create the Err branch
const err_branch_idx = try self.env.addMatchBranch(
Expr.Match.Branch{
.patterns = err_branch_pat_span,
.value = return_expr_idx,
.guard = null,
.redundant = @enumFromInt(0),
},
region,
);
try self.env.store.addScratchMatchBranch(err_branch_idx);
}
// Create span from scratch branches
const branches_span = try self.env.store.matchBranchSpanFrom(scratch_top);
// Create the match expression
const match_expr = Expr.Match{
.cond = can_cond.idx,
.branches = branches_span,
.exhaustive = @enumFromInt(0), // Will be set during type checking
};
const expr_idx = try self.env.addExpr(CIR.Expr{ .e_match = match_expr }, region);
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
},
.unary_op => |unary| {
const region = self.parse_ir.tokenizedRegionToRegion(unary.region);
@ -6348,21 +6506,63 @@ fn canonicalizePattern(
.string => |e| {
const region = self.parse_ir.tokenizedRegionToRegion(e.region);
// resolve to a string slice from the source
const token_text = self.parse_ir.resolve(e.string_tok);
// Get the string expression which contains the actual string parts
const str_expr = self.parse_ir.store.getExpr(e.expr);
// TODO: Handle escape sequences
// For now, just intern the raw string
const literal = try self.env.insertString(token_text);
switch (str_expr) {
.string => |se| {
// Get the parts of the string expression
const parts = self.parse_ir.store.exprSlice(se.parts);
const str_pattern = Pattern{
.str_literal = .{
.literal = literal,
// For simple string literals, there should be exactly one string_part
if (parts.len == 1) {
const part = self.parse_ir.store.getExpr(parts[0]);
switch (part) {
.string_part => |sp| {
// Get the actual string content from the string_part token
const part_text = self.parse_ir.resolve(sp.token);
// Process escape sequences
const processed_text = try processEscapeSequences(self.env.gpa, part_text);
defer if (processed_text.ptr != part_text.ptr) {
self.env.gpa.free(processed_text);
};
const literal = try self.env.insertString(processed_text);
const str_pattern = Pattern{
.str_literal = .{
.literal = literal,
},
};
const pattern_idx = try self.env.addPattern(str_pattern, region);
return pattern_idx;
},
else => {},
}
}
// For string patterns with interpolation or multiple parts,
// we need more complex handling (not yet supported)
const malformed = try self.env.pushMalformed(Pattern.Idx, Diagnostic{
.not_implemented = .{
.feature = try self.env.insertString("string patterns with interpolation"),
.region = region,
},
});
return malformed;
},
};
const pattern_idx = try self.env.addPattern(str_pattern, region);
return pattern_idx;
else => {
// Unexpected expression type in string pattern
const malformed = try self.env.pushMalformed(Pattern.Idx, Diagnostic{
.pattern_arg_invalid = .{
.region = region,
},
});
return malformed;
},
}
},
.single_quote => |e| {
return try self.canonicalizeSingleQuote(e.region, e.token, Pattern.Idx);

View file

@ -163,6 +163,9 @@ pub const CommonIdents = extern struct {
// from_utf8 error payload fields (BadUtf8 record)
problem: Ident.Idx,
index: Ident.Idx,
// Synthetic identifiers for ? operator desugaring
question_ok: Ident.Idx,
question_err: Ident.Idx,
/// Insert all well-known identifiers into a CommonEnv.
/// Use this when creating a fresh ModuleEnv from scratch.
@ -228,6 +231,9 @@ pub const CommonIdents = extern struct {
// from_utf8 error payload fields (BadUtf8 record)
.problem = try common.insertIdent(gpa, Ident.for_text("problem")),
.index = try common.insertIdent(gpa, Ident.for_text("index")),
// Synthetic identifiers for ? operator desugaring
.question_ok = try common.insertIdent(gpa, Ident.for_text("#ok")),
.question_err = try common.insertIdent(gpa, Ident.for_text("#err")),
};
}
@ -296,6 +302,9 @@ pub const CommonIdents = extern struct {
// from_utf8 error payload fields (BadUtf8 record)
.problem = common.findIdent("problem") orelse unreachable,
.index = common.findIdent("index") orelse unreachable,
// Synthetic identifiers for ? operator desugaring
.question_ok = common.findIdent("#ok") orelse unreachable,
.question_err = common.findIdent("#err") orelse unreachable,
};
}
};

View file

@ -681,3 +681,67 @@ test "fx platform run from different cwd" {
// Verify stdout contains expected messages
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Hello from stdout!") != null);
}
test "question mark operator" {
// Tests the `?` operator for error propagation.
const allocator = testing.allocator;
try ensureRocBinary(allocator);
const run_result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/question_mark_operator.roc",
},
});
defer allocator.free(run_result.stdout);
defer allocator.free(run_result.stderr);
// The ? operator should unwrap Ok values and return "hello"
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "hello") != null);
}
test "numeric fold" {
// Tests List.fold with numeric accumulators.
const allocator = testing.allocator;
try ensureRocBinary(allocator);
const run_result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/numeric_fold.roc",
},
});
defer allocator.free(run_result.stdout);
defer allocator.free(run_result.stderr);
// Verify we get the correct sum: 1+2+3+4+5 = 15
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Sum: 15") != null);
}
test "string literal pattern matching" {
// Tests pattern matching on string literals in match expressions.
const allocator = testing.allocator;
try ensureRocBinary(allocator);
const run_result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/string_pattern_matching.roc",
},
});
defer allocator.free(run_result.stdout);
defer allocator.free(run_result.stderr);
// Verify string patterns match correctly
const has_alice = std.mem.indexOf(u8, run_result.stdout, "Hello Alice!") != null;
const has_bob = std.mem.indexOf(u8, run_result.stdout, "Hey Bob!") != null;
try testing.expect(has_alice);
try testing.expect(has_bob);
}

View file

@ -2034,9 +2034,17 @@ pub const Interpreter = struct {
std.debug.assert(args.len == 2); // low-level .num_is_eq expects 2 arguments
const lhs = try self.extractNumericValue(args[0]);
const rhs = try self.extractNumericValue(args[1]);
const result = switch (lhs) {
.int => |l| l == rhs.int,
.dec => |l| l.num == rhs.dec.num,
const result: bool = switch (lhs) {
.int => |l| switch (rhs) {
.int => |r| l == r,
.dec => |r| l == @divTrunc(r.num, RocDec.one_point_zero_i128),
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| l.num == r.num,
.int => |r| l.num == @as(i128, r) * RocDec.one_point_zero_i128,
else => return error.TypeMismatch,
},
.f32, .f64 => {
self.triggerCrash("Equality comparison not supported for F32/F64 due to floating point imprecision", false, roc_ops);
return error.Crash;
@ -2049,11 +2057,27 @@ pub const Interpreter = struct {
std.debug.assert(args.len == 2); // low-level .num_is_gt expects 2 arguments
const lhs = try self.extractNumericValue(args[0]);
const rhs = try self.extractNumericValue(args[1]);
const result = switch (lhs) {
.int => |l| l > rhs.int,
.f32 => |l| l > rhs.f32,
.f64 => |l| l > rhs.f64,
.dec => |l| l.num > rhs.dec.num,
const result: bool = switch (lhs) {
.int => |l| switch (rhs) {
.int => |r| l > r,
// Int vs Dec: convert Dec to Int for comparison
.dec => |r| l > @divTrunc(r.num, RocDec.one_point_zero_i128),
else => return error.TypeMismatch,
},
.f32 => |l| switch (rhs) {
.f32 => |r| l > r,
else => return error.TypeMismatch,
},
.f64 => |l| switch (rhs) {
.f64 => |r| l > r,
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| l.num > r.num,
// Dec vs Int: convert Int to Dec for comparison
.int => |r| l.num > @as(i128, r) * RocDec.one_point_zero_i128,
else => return error.TypeMismatch,
},
};
return try self.makeBoolValue(result);
},
@ -2062,11 +2086,25 @@ pub const Interpreter = struct {
std.debug.assert(args.len == 2); // low-level .num_is_gte expects 2 arguments
const lhs = try self.extractNumericValue(args[0]);
const rhs = try self.extractNumericValue(args[1]);
const result = switch (lhs) {
.int => |l| l >= rhs.int,
.f32 => |l| l >= rhs.f32,
.f64 => |l| l >= rhs.f64,
.dec => |l| l.num >= rhs.dec.num,
const result: bool = switch (lhs) {
.int => |l| switch (rhs) {
.int => |r| l >= r,
.dec => |r| l >= @divTrunc(r.num, RocDec.one_point_zero_i128),
else => return error.TypeMismatch,
},
.f32 => |l| switch (rhs) {
.f32 => |r| l >= r,
else => return error.TypeMismatch,
},
.f64 => |l| switch (rhs) {
.f64 => |r| l >= r,
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| l.num >= r.num,
.int => |r| l.num >= @as(i128, r) * RocDec.one_point_zero_i128,
else => return error.TypeMismatch,
},
};
return try self.makeBoolValue(result);
},
@ -2075,11 +2113,25 @@ pub const Interpreter = struct {
std.debug.assert(args.len == 2); // low-level .num_is_lt expects 2 arguments
const lhs = try self.extractNumericValue(args[0]);
const rhs = try self.extractNumericValue(args[1]);
const result = switch (lhs) {
.int => |l| l < rhs.int,
.f32 => |l| l < rhs.f32,
.f64 => |l| l < rhs.f64,
.dec => |l| l.num < rhs.dec.num,
const result: bool = switch (lhs) {
.int => |l| switch (rhs) {
.int => |r| l < r,
.dec => |r| l < @divTrunc(r.num, RocDec.one_point_zero_i128),
else => return error.TypeMismatch,
},
.f32 => |l| switch (rhs) {
.f32 => |r| l < r,
else => return error.TypeMismatch,
},
.f64 => |l| switch (rhs) {
.f64 => |r| l < r,
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| l.num < r.num,
.int => |r| l.num < @as(i128, r) * RocDec.one_point_zero_i128,
else => return error.TypeMismatch,
},
};
return try self.makeBoolValue(result);
},
@ -2088,11 +2140,25 @@ pub const Interpreter = struct {
std.debug.assert(args.len == 2); // low-level .num_is_lte expects 2 arguments
const lhs = try self.extractNumericValue(args[0]);
const rhs = try self.extractNumericValue(args[1]);
const result = switch (lhs) {
.int => |l| l <= rhs.int,
.f32 => |l| l <= rhs.f32,
.f64 => |l| l <= rhs.f64,
.dec => |l| l.num <= rhs.dec.num,
const result: bool = switch (lhs) {
.int => |l| switch (rhs) {
.int => |r| l <= r,
.dec => |r| l <= @divTrunc(r.num, RocDec.one_point_zero_i128),
else => return error.TypeMismatch,
},
.f32 => |l| switch (rhs) {
.f32 => |r| l <= r,
else => return error.TypeMismatch,
},
.f64 => |l| switch (rhs) {
.f64 => |r| l <= r,
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| l.num <= r.num,
.int => |r| l.num <= @as(i128, r) * RocDec.one_point_zero_i128,
else => return error.TypeMismatch,
},
};
return try self.makeBoolValue(result);
},
@ -2126,10 +2192,24 @@ pub const Interpreter = struct {
out.is_initialized = false;
switch (lhs) {
.int => |l| try out.setInt(l + rhs.int),
.f32 => |l| out.setF32(l + rhs.f32),
.f64 => |l| out.setF64(l + rhs.f64),
.dec => |l| out.setDec(RocDec.add(l, rhs.dec, roc_ops)),
.int => |l| switch (rhs) {
.int => |r| try out.setInt(l + r),
.dec => |r| try out.setInt(l + @divTrunc(r.num, RocDec.one_point_zero_i128)),
else => return error.TypeMismatch,
},
.f32 => |l| switch (rhs) {
.f32 => |r| out.setF32(l + r),
else => return error.TypeMismatch,
},
.f64 => |l| switch (rhs) {
.f64 => |r| out.setF64(l + r),
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| out.setDec(RocDec.add(l, r, roc_ops)),
.int => |r| out.setDec(RocDec.add(l, RocDec{ .num = @as(i128, r) * RocDec.one_point_zero_i128 }, roc_ops)),
else => return error.TypeMismatch,
},
}
out.is_initialized = true;
return out;
@ -2144,10 +2224,24 @@ pub const Interpreter = struct {
out.is_initialized = false;
switch (lhs) {
.int => |l| try out.setInt(l - rhs.int),
.f32 => |l| out.setF32(l - rhs.f32),
.f64 => |l| out.setF64(l - rhs.f64),
.dec => |l| out.setDec(RocDec.sub(l, rhs.dec, roc_ops)),
.int => |l| switch (rhs) {
.int => |r| try out.setInt(l - r),
.dec => |r| try out.setInt(l - @divTrunc(r.num, RocDec.one_point_zero_i128)),
else => return error.TypeMismatch,
},
.f32 => |l| switch (rhs) {
.f32 => |r| out.setF32(l - r),
else => return error.TypeMismatch,
},
.f64 => |l| switch (rhs) {
.f64 => |r| out.setF64(l - r),
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| out.setDec(RocDec.sub(l, r, roc_ops)),
.int => |r| out.setDec(RocDec.sub(l, RocDec{ .num = @as(i128, r) * RocDec.one_point_zero_i128 }, roc_ops)),
else => return error.TypeMismatch,
},
}
out.is_initialized = true;
return out;
@ -2162,10 +2256,24 @@ pub const Interpreter = struct {
out.is_initialized = false;
switch (lhs) {
.int => |l| try out.setInt(l * rhs.int),
.f32 => |l| out.setF32(l * rhs.f32),
.f64 => |l| out.setF64(l * rhs.f64),
.dec => |l| out.setDec(RocDec.mul(l, rhs.dec, roc_ops)),
.int => |l| switch (rhs) {
.int => |r| try out.setInt(l * r),
.dec => |r| try out.setInt(l * @divTrunc(r.num, RocDec.one_point_zero_i128)),
else => return error.TypeMismatch,
},
.f32 => |l| switch (rhs) {
.f32 => |r| out.setF32(l * r),
else => return error.TypeMismatch,
},
.f64 => |l| switch (rhs) {
.f64 => |r| out.setF64(l * r),
else => return error.TypeMismatch,
},
.dec => |l| switch (rhs) {
.dec => |r| out.setDec(RocDec.mul(l, r, roc_ops)),
.int => |r| out.setDec(RocDec.mul(l, RocDec{ .num = @as(i128, r) * RocDec.one_point_zero_i128 }, roc_ops)),
else => return error.TypeMismatch,
},
}
out.is_initialized = true;
return out;
@ -2180,21 +2288,43 @@ pub const Interpreter = struct {
out.is_initialized = false;
switch (lhs) {
.int => |l| {
if (rhs.int == 0) return error.DivisionByZero;
try out.setInt(@divTrunc(l, rhs.int));
.int => |l| switch (rhs) {
.int => |r| {
if (r == 0) return error.DivisionByZero;
try out.setInt(@divTrunc(l, r));
},
.dec => |r| {
const r_int = @divTrunc(r.num, RocDec.one_point_zero_i128);
if (r_int == 0) return error.DivisionByZero;
try out.setInt(@divTrunc(l, r_int));
},
else => return error.TypeMismatch,
},
.f32 => |l| {
if (rhs.f32 == 0) return error.DivisionByZero;
out.setF32(l / rhs.f32);
.f32 => |l| switch (rhs) {
.f32 => |r| {
if (r == 0) return error.DivisionByZero;
out.setF32(l / r);
},
else => return error.TypeMismatch,
},
.f64 => |l| {
if (rhs.f64 == 0) return error.DivisionByZero;
out.setF64(l / rhs.f64);
.f64 => |l| switch (rhs) {
.f64 => |r| {
if (r == 0) return error.DivisionByZero;
out.setF64(l / r);
},
else => return error.TypeMismatch,
},
.dec => |l| {
if (rhs.dec.num == 0) return error.DivisionByZero;
out.setDec(RocDec.div(l, rhs.dec, roc_ops));
.dec => |l| switch (rhs) {
.dec => |r| {
if (r.num == 0) return error.DivisionByZero;
out.setDec(RocDec.div(l, r, roc_ops));
},
.int => |r| {
if (r == 0) return error.DivisionByZero;
const r_dec = RocDec{ .num = @as(i128, r) * RocDec.one_point_zero_i128 };
out.setDec(RocDec.div(l, r_dec, roc_ops));
},
else => return error.TypeMismatch,
},
}
out.is_initialized = true;
@ -2210,22 +2340,44 @@ pub const Interpreter = struct {
out.is_initialized = false;
switch (lhs) {
.int => |l| {
if (rhs.int == 0) return error.DivisionByZero;
try out.setInt(@divTrunc(l, rhs.int));
.int => |l| switch (rhs) {
.int => |r| {
if (r == 0) return error.DivisionByZero;
try out.setInt(@divTrunc(l, r));
},
.dec => |r| {
const r_int = @divTrunc(r.num, RocDec.one_point_zero_i128);
if (r_int == 0) return error.DivisionByZero;
try out.setInt(@divTrunc(l, r_int));
},
else => return error.TypeMismatch,
},
.f32 => |l| {
if (rhs.f32 == 0) return error.DivisionByZero;
out.setF32(@trunc(l / rhs.f32));
.f32 => |l| switch (rhs) {
.f32 => |r| {
if (r == 0) return error.DivisionByZero;
out.setF32(@trunc(l / r));
},
else => return error.TypeMismatch,
},
.f64 => |l| {
if (rhs.f64 == 0) return error.DivisionByZero;
out.setF64(@trunc(l / rhs.f64));
.f64 => |l| switch (rhs) {
.f64 => |r| {
if (r == 0) return error.DivisionByZero;
out.setF64(@trunc(l / r));
},
else => return error.TypeMismatch,
},
.dec => |l| {
// For Dec, div and div_trunc are the same since it's already integer-like
if (rhs.dec.num == 0) return error.DivisionByZero;
out.setDec(RocDec.div(l, rhs.dec, roc_ops));
.dec => |l| switch (rhs) {
.dec => |r| {
// For Dec, div and div_trunc are the same since it's already integer-like
if (r.num == 0) return error.DivisionByZero;
out.setDec(RocDec.div(l, r, roc_ops));
},
.int => |r| {
if (r == 0) return error.DivisionByZero;
const r_dec = RocDec{ .num = @as(i128, r) * RocDec.one_point_zero_i128 };
out.setDec(RocDec.div(l, r_dec, roc_ops));
},
else => return error.TypeMismatch,
},
}
out.is_initialized = true;
@ -2241,21 +2393,43 @@ pub const Interpreter = struct {
out.is_initialized = false;
switch (lhs) {
.int => |l| {
if (rhs.int == 0) return error.DivisionByZero;
try out.setInt(@rem(l, rhs.int));
.int => |l| switch (rhs) {
.int => |r| {
if (r == 0) return error.DivisionByZero;
try out.setInt(@rem(l, r));
},
.dec => |r| {
const r_int = @divTrunc(r.num, RocDec.one_point_zero_i128);
if (r_int == 0) return error.DivisionByZero;
try out.setInt(@rem(l, r_int));
},
else => return error.TypeMismatch,
},
.f32 => |l| {
if (rhs.f32 == 0) return error.DivisionByZero;
out.setF32(@rem(l, rhs.f32));
.f32 => |l| switch (rhs) {
.f32 => |r| {
if (r == 0) return error.DivisionByZero;
out.setF32(@rem(l, r));
},
else => return error.TypeMismatch,
},
.f64 => |l| {
if (rhs.f64 == 0) return error.DivisionByZero;
out.setF64(@rem(l, rhs.f64));
.f64 => |l| switch (rhs) {
.f64 => |r| {
if (r == 0) return error.DivisionByZero;
out.setF64(@rem(l, r));
},
else => return error.TypeMismatch,
},
.dec => |l| {
if (rhs.dec.num == 0) return error.DivisionByZero;
out.setDec(RocDec.rem(l, rhs.dec, roc_ops));
.dec => |l| switch (rhs) {
.dec => |r| {
if (r.num == 0) return error.DivisionByZero;
out.setDec(RocDec.rem(l, r, roc_ops));
},
.int => |r| {
if (r == 0) return error.DivisionByZero;
const r_dec = RocDec{ .num = @as(i128, r) * RocDec.one_point_zero_i128 };
out.setDec(RocDec.rem(l, r_dec, roc_ops));
},
else => return error.TypeMismatch,
},
}
out.is_initialized = true;
@ -4338,6 +4512,36 @@ pub const Interpreter = struct {
if (lhs.layout.tag == .scalar and rhs.layout.tag == .scalar) {
const lhs_scalar = lhs.layout.data.scalar;
const rhs_scalar = rhs.layout.data.scalar;
// Handle numeric type mismatches (Int vs Dec)
const lhs_is_numeric = lhs_scalar.tag == .int or lhs_scalar.tag == .frac;
const rhs_is_numeric = rhs_scalar.tag == .int or rhs_scalar.tag == .frac;
if (lhs_is_numeric and rhs_is_numeric) {
// Allow comparing Int with Dec by converting
const lhs_num = self.extractNumericValue(lhs) catch return error.TypeMismatch;
const rhs_num = self.extractNumericValue(rhs) catch return error.TypeMismatch;
return switch (lhs_num) {
.int => |l| switch (rhs_num) {
.int => |r| l == r,
.dec => |r| l == @divTrunc(r.num, RocDec.one_point_zero_i128),
else => false,
},
.dec => |l| switch (rhs_num) {
.dec => |r| l.num == r.num,
.int => |r| l.num == @as(i128, r) * RocDec.one_point_zero_i128,
else => false,
},
.f32 => |l| switch (rhs_num) {
.f32 => |r| l == r,
else => false,
},
.f64 => |l| switch (rhs_num) {
.f64 => |r| l == r,
else => false,
},
};
}
if (lhs_scalar.tag != rhs_scalar.tag) return error.TypeMismatch;
switch (lhs_scalar.tag) {
@ -5655,14 +5859,15 @@ pub const Interpreter = struct {
try self.ensureVarLayoutCapacity(idx + 1);
const slot_ptr = &self.var_to_layout_slot.items[idx];
// If we have a flex var, default it to Dec
// This is the interpreter-time defaulting for numeric literals
// If we have a flex var, default it to I64 (not Dec)
// This is the interpreter-time defaulting for unresolved numeric types.
// Integer literals should default to I64, not Dec.
if (resolved.desc.content == .flex) {
// Directly return Dec's scalar layout
const dec_layout = layout.Layout.frac(types.Frac.Precision.dec);
const dec_layout_idx = try self.runtime_layout_store.insertLayout(dec_layout);
slot_ptr.* = @intFromEnum(dec_layout_idx) + 1;
return dec_layout;
// Default to I64 for better compatibility with integer operations
const i64_layout = layout.Layout.int(types.Int.Precision.i64);
const i64_layout_idx = try self.runtime_layout_store.insertLayout(i64_layout);
slot_ptr.* = @intFromEnum(i64_layout_idx) + 1;
return i64_layout;
}
if (slot_ptr.* != 0) {
const layout_idx_plus_one = slot_ptr.*;
@ -7242,18 +7447,87 @@ pub const Interpreter = struct {
};
// Get LHS and RHS type info
// Note: Both operands should be unified to the same type by the type checker
const lhs_ct_var = can.ModuleEnv.varFrom(binop.lhs);
var lhs_rt_var = try self.translateTypeVar(self.env, lhs_ct_var);
const lhs_rt_var = try self.translateTypeVar(self.env, lhs_ct_var);
const rhs_ct_var = can.ModuleEnv.varFrom(binop.rhs);
const rhs_rt_var = try self.translateTypeVar(self.env, rhs_ct_var);
// Resolve the lhs type - if flex/rigid, default to Dec
// Ensure both operands have the same numeric type.
// Strategy:
// - If one operand is concrete (not flex/rigid), unify the other with it
// - If both are unresolved (flex/rigid), default both to Dec
const lhs_resolved = self.runtime_types.resolveVar(lhs_rt_var);
if (lhs_resolved.desc.content == .flex or lhs_resolved.desc.content == .rigid) {
const rhs_resolved = self.runtime_types.resolveVar(rhs_rt_var);
const lhs_is_flex = lhs_resolved.desc.content == .flex or lhs_resolved.desc.content == .rigid;
const rhs_is_flex = rhs_resolved.desc.content == .flex or rhs_resolved.desc.content == .rigid;
if (lhs_is_flex and rhs_is_flex) {
// Both unresolved - default both to Dec
const dec_content = try self.mkNumberTypeContentRuntime("Dec");
const dec_var = try self.runtime_types.freshFromContent(dec_content);
lhs_rt_var = dec_var;
_ = try unify.unify(
self.env,
self.runtime_types,
&self.problems,
&self.snapshots,
&self.unify_scratch,
&self.unify_scratch.occurs_scratch,
unify.ModuleEnvLookup{
.interpreter_lookup_ctx = @ptrCast(&self.module_envs),
.interpreter_lookup_fn = interpreterLookupModuleEnv,
},
lhs_rt_var,
dec_var,
);
_ = try unify.unify(
self.env,
self.runtime_types,
&self.problems,
&self.snapshots,
&self.unify_scratch,
&self.unify_scratch.occurs_scratch,
unify.ModuleEnvLookup{
.interpreter_lookup_ctx = @ptrCast(&self.module_envs),
.interpreter_lookup_fn = interpreterLookupModuleEnv,
},
rhs_rt_var,
dec_var,
);
} else if (lhs_is_flex and !rhs_is_flex) {
// LHS is flex, RHS is concrete - unify LHS with RHS
_ = try unify.unify(
self.env,
self.runtime_types,
&self.problems,
&self.snapshots,
&self.unify_scratch,
&self.unify_scratch.occurs_scratch,
unify.ModuleEnvLookup{
.interpreter_lookup_ctx = @ptrCast(&self.module_envs),
.interpreter_lookup_fn = interpreterLookupModuleEnv,
},
lhs_rt_var,
rhs_rt_var,
);
} else if (!lhs_is_flex and rhs_is_flex) {
// RHS is flex, LHS is concrete - unify RHS with LHS
_ = try unify.unify(
self.env,
self.runtime_types,
&self.problems,
&self.snapshots,
&self.unify_scratch,
&self.unify_scratch.occurs_scratch,
unify.ModuleEnvLookup{
.interpreter_lookup_ctx = @ptrCast(&self.module_envs),
.interpreter_lookup_fn = interpreterLookupModuleEnv,
},
rhs_rt_var,
lhs_rt_var,
);
}
// If both are concrete, they should already match (type checker ensures this)
// For != we need to negate the result of is_eq
const negate_result = binop.op == .ne;

10
test/fx/numeric_fold.roc Normal file
View file

@ -0,0 +1,10 @@
app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
# Tests List.fold with numeric accumulators.
main! = || {
sum = [1, 2, 3, 4, 5].fold(0, |acc, n| acc + n)
Stdout.line!("Sum: ${I64.to_str(sum)}")
}

View file

@ -0,0 +1,19 @@
app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
# Tests the `?` operator for error propagation.
# The operator unwraps Ok values or early-returns Err values.
get_greeting : {} -> Try(Str, [ListWasEmpty])
get_greeting = |{}| {
first = List.first(["hello"])?
Ok(first)
}
main! = || {
match get_greeting({}) {
Ok(greeting) => Stdout.line!(greeting)
Err(ListWasEmpty) => Stdout.line!("List was empty!")
}
}

View file

@ -0,0 +1,19 @@
app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
# Tests pattern matching on string literals in match expressions.
main! = || {
greet("Alice")
greet("Bob")
}
greet = |name| {
message = match name {
"Alice" => "Hello Alice!"
"Bob" => "Hey Bob!"
_ => "Hello stranger!"
}
Stdout.line!(message)
}

View file

@ -1,89 +0,0 @@
# META
~~~ini
description=fuzz crash
type=file
~~~
# SOURCE
~~~roc
ff8.8.d
~~~
# EXPECTED
PARSE ERROR - fuzz_crash_007.md:1:1:1:4
PARSE ERROR - fuzz_crash_007.md:1:4:1:6
PARSE ERROR - fuzz_crash_007.md:1:6:1:8
MISSING MAIN! FUNCTION - fuzz_crash_007.md:1:1:1:8
# PROBLEMS
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
**fuzz_crash_007.md:1:1:1:4:**
```roc
ff8.8.d
```
^^^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
**fuzz_crash_007.md:1:4:1:6:**
```roc
ff8.8.d
```
^^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
**fuzz_crash_007.md:1:6:1:8:**
```roc
ff8.8.d
```
^^
**MISSING MAIN! FUNCTION**
Default app modules must have a `main!` function.
No `main!` function was found.
Add a main! function like:
`main! = |arg| { ... }`
**fuzz_crash_007.md:1:1:1:8:**
```roc
ff8.8.d
```
^^^^^^^
# TOKENS
~~~zig
LowerIdent,NoSpaceDotInt,NoSpaceDotLowerIdent,
EndOfFile,
~~~
# PARSE
~~~clojure
(file
(type-module)
(statements
(s-malformed (tag "statement_unexpected_token"))
(s-malformed (tag "statement_unexpected_token"))
(s-malformed (tag "statement_unexpected_token"))))
~~~
# FORMATTED
~~~roc
~~~
# CANONICALIZE
~~~clojure
(can-ir (empty true))
~~~
# TYPES
~~~clojure
(inferred-types
(defs)
(expressions))
~~~

File diff suppressed because it is too large Load diff

View file

@ -750,10 +750,27 @@ This feature is not yet implemented: unsupported operator
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize suffix_single_question expression
**UNDEFINED VARIABLE**
Nothing is named `e_fn` in this scope.
Is there an `import` or `exposing` missing up-top?
**fuzz_crash_020.md:105:55:105:59:**
```roc
b?? 12 > 5 or 13 + 2 < 5 and 10 - 1 >= 16 or 12 <= 3 e_fn(arg1)?.od()?.ned()?.recd?
```
^^^^
**UNDEFINED VARIABLE**
Nothing is named `arg1` in this scope.
Is there an `import` or `exposing` missing up-top?
**fuzz_crash_020.md:105:60:105:64:**
```roc
b?? 12 > 5 or 13 + 2 < 5 and 10 - 1 >= 16 or 12 <= 3 e_fn(arg1)?.od()?.ned()?.recd?
```
^^^^
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**UNDEFINED VARIABLE**
Nothing is named `r` in this scope.
@ -1681,7 +1698,7 @@ expect {
(branch
(patterns
(pattern (degenerate false)
(p-str (text """))))
(p-str (text "for"))))
(value
(e-num (value "20"))))
(branch
@ -1914,7 +1931,30 @@ expect {
(receiver
(e-dot-access (field "unknown")
(receiver
(e-runtime-error (tag "not_implemented")))))))))
(e-match
(match
(cond
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-runtime-error (tag "ident_not_in_scope"))))
(branches
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-lookup-local
(p-assign (ident "#ok")))))
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-return
(e-tag (name "Err")
(args
(e-lookup-local
(p-assign (ident "#err"))))))))))))))))))
(e-tag (name "Stdo!")
(args
(e-string

View file

@ -1,119 +0,0 @@
# META
~~~ini
description=fuzz crash
type=file
~~~
# SOURCE
~~~roc
app[]{f:platform""}{
o:0}0
~~~
# EXPECTED
PARSE ERROR - fuzz_crash_043.md:1:20:1:21
UNEXPECTED TOKEN IN TYPE ANNOTATION - fuzz_crash_043.md:2:3:2:4
PARSE ERROR - fuzz_crash_043.md:2:4:2:5
PARSE ERROR - fuzz_crash_043.md:2:5:2:6
MALFORMED TYPE - fuzz_crash_043.md:2:3:2:4
# PROBLEMS
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
**fuzz_crash_043.md:1:20:1:21:**
```roc
app[]{f:platform""}{
```
^
**UNEXPECTED TOKEN IN TYPE ANNOTATION**
The token **0** is not expected in a type annotation.
Type annotations should contain types like _Str_, _Num a_, or _List U64_.
**fuzz_crash_043.md:2:3:2:4:**
```roc
o:0}0
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
**fuzz_crash_043.md:2:4:2:5:**
```roc
o:0}0
```
^
**PARSE ERROR**
A parsing error occurred: `statement_unexpected_token`
This is an unexpected parsing error. Please check your syntax.
**fuzz_crash_043.md:2:5:2:6:**
```roc
o:0}0
```
^
**MALFORMED TYPE**
This type annotation is malformed or contains invalid syntax.
**fuzz_crash_043.md:2:3:2:4:**
```roc
o:0}0
```
^
# TOKENS
~~~zig
KwApp,OpenSquare,CloseSquare,OpenCurly,LowerIdent,OpColon,KwPlatform,StringStart,StringPart,StringEnd,CloseCurly,OpenCurly,
LowerIdent,OpColon,Int,CloseCurly,Int,
EndOfFile,
~~~
# PARSE
~~~clojure
(file
(app
(provides)
(record-field (name "f")
(e-string
(e-string-part (raw ""))))
(packages
(record-field (name "f")
(e-string
(e-string-part (raw ""))))))
(statements
(s-malformed (tag "statement_unexpected_token"))
(s-type-anno (name "o")
(ty-malformed (tag "ty_anno_unexpected_token")))
(s-malformed (tag "statement_unexpected_token"))
(s-malformed (tag "statement_unexpected_token"))))
~~~
# FORMATTED
~~~roc
app [] { f: platform "" }
o :
~~~
# CANONICALIZE
~~~clojure
(can-ir
(d-let
(p-assign (ident "o"))
(e-anno-only)
(annotation
(ty-malformed))))
~~~
# TYPES
~~~clojure
(inferred-types
(defs
(patt (type "Error")))
(expressions
(expr (type "Error"))))
~~~

View file

@ -142,9 +142,9 @@ NO CHANGE
(branch
(patterns
(pattern (degenerate false)
(p-str (text """)))
(p-str (text "hello")))
(pattern (degenerate false)
(p-str (text """))))
(p-str (text "world"))))
(value
(e-string
(e-literal (string "greetings")))))

View file

@ -11,12 +11,30 @@ some_fn(arg1)?
.record_field?
~~~
# EXPECTED
NOT IMPLEMENTED - :0:0:0:0
UNDEFINED VARIABLE - record_access_multiline_formatting_1.md:1:1:1:8
UNDEFINED VARIABLE - record_access_multiline_formatting_1.md:1:9:1:13
# PROBLEMS
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize suffix_single_question expression
**UNDEFINED VARIABLE**
Nothing is named `some_fn` in this scope.
Is there an `import` or `exposing` missing up-top?
**record_access_multiline_formatting_1.md:1:1:1:8:**
```roc
some_fn(arg1)?
```
^^^^^^^
**UNDEFINED VARIABLE**
Nothing is named `arg1` in this scope.
Is there an `import` or `exposing` missing up-top?
**record_access_multiline_formatting_1.md:1:9:1:13:**
```roc
some_fn(arg1)?
```
^^^^
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
# TOKENS
~~~zig
@ -56,7 +74,30 @@ NO CHANGE
(receiver
(e-dot-access (field "unknown")
(receiver
(e-runtime-error (tag "not_implemented"))))))))
(e-match
(match
(cond
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-runtime-error (tag "ident_not_in_scope"))))
(branches
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-lookup-local
(p-assign (ident "#ok")))))
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-return
(e-tag (name "Err")
(args
(e-lookup-local
(p-assign (ident "#err")))))))))))))))))
~~~
# TYPES
~~~clojure

View file

@ -11,12 +11,30 @@ some_fn(arg1)? # Comment 1
.record_field?
~~~
# EXPECTED
NOT IMPLEMENTED - :0:0:0:0
UNDEFINED VARIABLE - record_access_multiline_formatting_4.md:1:1:1:8
UNDEFINED VARIABLE - record_access_multiline_formatting_4.md:1:9:1:13
# PROBLEMS
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize suffix_single_question expression
**UNDEFINED VARIABLE**
Nothing is named `some_fn` in this scope.
Is there an `import` or `exposing` missing up-top?
**record_access_multiline_formatting_4.md:1:1:1:8:**
```roc
some_fn(arg1)? # Comment 1
```
^^^^^^^
**UNDEFINED VARIABLE**
Nothing is named `arg1` in this scope.
Is there an `import` or `exposing` missing up-top?
**record_access_multiline_formatting_4.md:1:9:1:13:**
```roc
some_fn(arg1)? # Comment 1
```
^^^^
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
# TOKENS
~~~zig
@ -56,7 +74,30 @@ NO CHANGE
(receiver
(e-dot-access (field "unknown")
(receiver
(e-runtime-error (tag "not_implemented"))))))))
(e-match
(match
(cond
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-runtime-error (tag "ident_not_in_scope"))))
(branches
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-lookup-local
(p-assign (ident "#ok")))))
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-return
(e-tag (name "Err")
(args
(e-lookup-local
(p-assign (ident "#err")))))))))))))))))
~~~
# TYPES
~~~clojure

View file

@ -8,12 +8,30 @@ type=expr
some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?
~~~
# EXPECTED
NOT IMPLEMENTED - :0:0:0:0
UNDEFINED VARIABLE - static_dispatch_super_test.md:1:1:1:8
UNDEFINED VARIABLE - static_dispatch_super_test.md:1:9:1:13
# PROBLEMS
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize suffix_single_question expression
**UNDEFINED VARIABLE**
Nothing is named `some_fn` in this scope.
Is there an `import` or `exposing` missing up-top?
**static_dispatch_super_test.md:1:1:1:8:**
```roc
some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?
```
^^^^^^^
**UNDEFINED VARIABLE**
Nothing is named `arg1` in this scope.
Is there an `import` or `exposing` missing up-top?
**static_dispatch_super_test.md:1:9:1:13:**
```roc
some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?
```
^^^^
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
# TOKENS
~~~zig
@ -50,7 +68,30 @@ NO CHANGE
(receiver
(e-dot-access (field "unknown")
(receiver
(e-runtime-error (tag "not_implemented"))))))))
(e-match
(match
(cond
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-runtime-error (tag "ident_not_in_scope"))))
(branches
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-lookup-local
(p-assign (ident "#ok")))))
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-return
(e-tag (name "Err")
(args
(e-lookup-local
(p-assign (ident "#err")))))))))))))))))
~~~
# TYPES
~~~clojure

View file

@ -697,15 +697,38 @@ This feature is not yet implemented: unsupported operator
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize suffix_single_question expression
**UNDEFINED VARIABLE**
Nothing is named `some_fn` in this scope.
Is there an `import` or `exposing` missing up-top?
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**syntax_grab_bag.md:189:26:189:33:**
```roc
static_dispatch_style = some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?
```
^^^^^^^
**NOT IMPLEMENTED**
This feature is not yet implemented: canonicalize suffix_single_question expression
This error doesn't have a proper diagnostic report yet. Let us know if you want to help improve Roc's error messages!
**UNDEFINED VARIABLE**
Nothing is named `arg1` in this scope.
Is there an `import` or `exposing` missing up-top?
**syntax_grab_bag.md:189:34:189:38:**
```roc
static_dispatch_style = some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?
```
^^^^
**UNDEFINED VARIABLE**
Nothing is named `line!` in this scope.
Is there an `import` or `exposing` missing up-top?
**syntax_grab_bag.md:190:2:190:14:**
```roc
Stdout.line!(interpolated)?
```
^^^^^^^^^^^^
**UNDEFINED VARIABLE**
Nothing is named `line!` in this scope.
@ -740,18 +763,6 @@ The unused variable is declared here:
^^^^^^^^^^^^^^^^
**UNUSED VARIABLE**
Variable `interpolated` is not used anywhere in your code.
If you don't need this variable, prefix it with an underscore like `_interpolated` to suppress this warning.
The unused variable is declared here:
**syntax_grab_bag.md:165:2:165:14:**
```roc
interpolated = "Hello, ${world}"
```
^^^^^^^^^^^^
**UNUSED VARIABLE**
Variable `record` is not used anywhere in your code.
@ -948,6 +959,17 @@ It has the type:
But I expected it to be:
_Str_
**UNUSED VALUE**
This expression produces a value, but it's not being used:
**syntax_grab_bag.md:190:2:190:29:**
```roc
Stdout.line!(interpolated)?
```
^^^^^^^^^^^^^^^^^^^^^^^^^^^
It has the type:
_d_
**TYPE MISMATCH**
This expression is used in an unexpected way:
**syntax_grab_bag.md:144:9:196:2:**
@ -2043,15 +2065,15 @@ expect {
(branch
(patterns
(pattern (degenerate false)
(p-str (text """))))
(p-str (text "foo"))))
(value
(e-num (value "100"))))
(branch
(patterns
(pattern (degenerate false)
(p-str (text """)))
(p-str (text "foo")))
(pattern (degenerate false)
(p-str (text """))))
(p-str (text "bar"))))
(value
(e-num (value "200"))))
(branch
@ -2390,9 +2412,56 @@ expect {
(receiver
(e-dot-access (field "unknown")
(receiver
(e-runtime-error (tag "not_implemented")))))))))
(e-match
(match
(cond
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-runtime-error (tag "ident_not_in_scope"))))
(branches
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-lookup-local
(p-assign (ident "#ok")))))
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-return
(e-tag (name "Err")
(args
(e-lookup-local
(p-assign (ident "#err"))))))))))))))))))
(s-expr
(e-runtime-error (tag "not_implemented")))
(e-match
(match
(cond
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-lookup-local
(p-assign (ident "interpolated")))))
(branches
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-lookup-local
(p-assign (ident "#ok")))))
(branch
(patterns
(pattern (degenerate false)
(p-applied-tag)))
(value
(e-return
(e-tag (name "Err")
(args
(e-lookup-local
(p-assign (ident "#err"))))))))))))
(e-call
(e-runtime-error (tag "ident_not_in_scope"))
(e-string