Implement ? (question mark) operator for error propagation

The ? operator now desugars expr? to a match expression that unwraps Ok
values and early returns Err values:

  match expr {
      Ok(#ok) => #ok,
      Err(#err) => return Err(#err),
  }

Implementation details:
- Use pre-interned identifiers (#ok, #err) for synthetic variables
- Use pre-interned Ok/Err tag names from CommonIdents
- Create applied_tag patterns for Ok and Err
- Build match branches with proper scope isolation
- Mark synthetic patterns as used to avoid unused variable warnings
- Create e_lookup_local, e_tag, and e_return expressions for branch bodies

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Richard Feldman 2025-11-29 19:25:42 -05:00
parent 2d8f2e3744
commit cc3423a709
No known key found for this signature in database
11 changed files with 176 additions and 444 deletions

View file

@ -1,385 +0,0 @@
# Bug Fix Plan for Roc Compiler Issues
This document provides a detailed plan for fixing the remaining bugs found during testing.
## Status Summary
| Bug # | Description | Status | Difficulty |
|-------|-------------|--------|------------|
| 1 | Type annotations on helper functions panic | FIXED | - |
| 2 | Tuple pattern matching panic | FIXED | - |
| 3 | Custom type aliases panic | FIXED | - |
| 4 | `?` operator not implemented | FIXED | Medium |
| 5-8 | Missing builtins (Num.to_str, List.range, etc.) | Feature Request | - |
| 9 | Numeric fold produces garbage values | FIXED | Hard |
| 10 | For loop on list literals segfault | FIXED | - |
| 11 | List.first error handling crash | FIXED | - |
| 12 | String literal matching in match broken | FIXED | Medium |
| 13-14 | Args and memory leaks | Platform-specific | - |
## Bugs Requiring Fixes
---
## Bug 4: `?` Operator Not Implemented
### Reproduction
```roc
main! = || {
first = List.first(["hello"])?
Stdout.line!(first)
}
```
**Error:** `NOT IMPLEMENTED: canonicalize suffix_single_question expression`
### Root Cause Location
- **File:** `/Users/rtfeldman/code/roc5/src/canonicalize/Can.zig`
- **Lines:** 5073-5079
```zig
.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 };
},
```
### How Similar Operators Work
The `!` (bang) operator is handled at lines 5097-5107:
```zig
.OpBang => {
const can_operand = (try self.canonicalizeExpr(unary.expr)) orelse return null;
const expr_idx = try self.env.addExpr(Expr{
.e_unary_not = Expr.UnaryNot.init(can_operand.idx),
}, region);
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = can_operand.free_vars };
},
```
### Fix Plan
1. **Desugar `expr?` to a match expression:**
```roc
match expr {
Ok(value) => value,
Err(e) => return Err(e),
}
```
2. **Implementation steps:**
- Canonicalize the inner expression
- Create a match expression with two branches:
- `Ok(value)` pattern → extract and return the value
- `Err(e)` pattern → early return with the error
- Handle free variables from the inner expression
- Use existing `e_match` and `e_return` expression types
3. **Files to modify:**
- `src/canonicalize/Can.zig` (main implementation)
- May need to add helper functions for generating the match pattern
### Test File
- `/Users/rtfeldman/code/roc5/test/fx/bug_04_question_mark_not_implemented.roc`
---
## Bug 9: Numeric Fold Produces Incorrect Values
### Reproduction
```roc
main! = || {
sum = [1, 2, 3, 4, 5].fold(0, |acc, n| acc + n)
Stdout.line!("Sum: ${I64.to_str(sum)}")
}
```
**Expected:** `Sum: 15`
**Actual:** `Sum: -3446744073709551616` (garbage value)
### Root Cause Location
- **File:** `/Users/rtfeldman/code/roc5/src/eval/interpreter.zig`
- **Lines:** 10780-10857 (`for_loop_body_done` handler)
### How fold Works (Builtin Definition)
From `/Users/rtfeldman/code/roc5/src/build/roc/Builtin.roc:97-106`:
```roc
fold : List(item), state, (state, item -> state) -> state
fold = |list, init, step| {
var $state = init
for item in list {
$state = step($state, item)
}
$state
}
```
### Root Cause Analysis
The `for_loop_body_done` continuation handler has a binding cleanup issue:
1. **Line 10786:** `body_result.decref(&self.runtime_layout_store, roc_ops);`
- Immediately discards the loop body result without preserving mutable variable updates
2. **Line 10789:** `self.trimBindingList(&self.bindings, fl.loop_bindings_start, roc_ops);`
- Trims bindings back to the loop scope start
- **BUG:** This removes/corrupts the mutable variable binding (`$state`) that tracks the accumulator
The execution flow:
1. Before loop: `$state` binding created with value 0
2. Iteration 1: `$state = step($state, 1)` → binding updated to 1
3. `for_loop_body_done` called, binding is trimmed away
4. Iteration 2: Tries to read `$state` but binding is corrupted/missing
5. Returns uninitialized memory (garbage)
### Fix Plan
1. **Track mutable variable bindings separately:**
- Mutable variables (`var $x`) should not be trimmed by iteration scope cleanup
- Either mark bindings as "mutable" or track their indices separately
2. **Implementation options:**
- **Option A:** Add a `is_mutable` flag to bindings, skip trimming mutable bindings
- **Option B:** Track `mutable_bindings_start` separately from `loop_bindings_start`
- **Option C:** Check if a binding was updated via reassignment before trimming
3. **Files to modify:**
- `src/eval/interpreter.zig`
- Line 171-178: Binding struct (add mutable flag)
- Line 5015-5027: `trimBindingList` function (skip mutable bindings)
- Line 10780-10857: `for_loop_body_done` handler (preserve mutable bindings)
- Line 10937-10963: Reassignment handling (mark binding as mutable)
4. **Key code locations:**
- Binding creation: lines 10825 (`new_loop_bindings_start`)
- Binding trimming: line 10789 (`trimBindingList`)
- Reassignment: lines 10947-10950 (where binding value is updated)
### Test File
- `/Users/rtfeldman/code/roc5/test/fx/bug_09_numeric_fold_wrong.roc`
---
## Bug 12: String Literal Matching Falls Through to Wildcard
### Reproduction
```roc
greet = |name| {
match name {
"Alice" => "Hello Alice!"
"Bob" => "Hey Bob!"
_ => "Hello stranger!"
}
}
```
**Expected:** Returns correct greeting for "Alice" and "Bob"
**Actual:** Always returns "Hello stranger!" (wildcard case)
### Root Cause Location
- **File:** `/Users/rtfeldman/code/roc5/src/eval/interpreter.zig`
- **Lines:** 5077-5082 (string pattern matching)
```zig
.str_literal => |sl| {
if (!(value.layout.tag == .scalar and value.layout.data.scalar.tag == .str)) return false;
const lit = self.env.getString(sl.literal);
const rs: *const RocStr = @ptrCast(@alignCast(value.ptr.?));
return rs.eqlSlice(lit);
},
```
### Potential Root Causes
1. **Layout type check failing:** The condition at line 5078 might incorrectly reject valid string values if the type system reports a different layout than expected
2. **String literal retrieval issue:** `self.env.getString(sl.literal)` might return an incorrect or empty string
3. **RocStr pointer dereferencing issue:** The `value.ptr` might not be correctly aligned or dereferenced
4. **eqlSlice comparison issue:** The string comparison function might have a subtle bug
### Fix Plan
1. **Debug and diagnose:**
- Add logging to verify which checks are failing
- Check if layout detection correctly identifies string types
- Verify string literal is correctly retrieved from environment
2. **Investigation steps:**
- Print `value.layout.tag` and `value.layout.data.scalar.tag` values
- Print the retrieved literal string
- Print the RocStr contents being compared
3. **Likely fix locations:**
- Layout type detection (line 5078)
- String retrieval (line 5079)
- Pointer alignment/casting (line 5080)
4. **Files to modify:**
- `src/eval/interpreter.zig` (lines 5077-5082)
- Potentially pattern canonicalization in `src/canonicalize/Can.zig`
### Test File
- `/Users/rtfeldman/code/roc5/test/fx/bug_12_string_match_broken.roc`
---
## Implementation Order
**Recommended order:**
1. **Bug 12 (String match)** - Likely a simple fix, good for understanding the codebase
2. **Bug 9 (Numeric fold)** - More complex, requires careful binding management
3. **Bug 4 (`?` operator)** - Feature implementation, requires understanding desugaring
---
## Test Commands
Run all bug reproduction tests:
```bash
zig build test -Dcli-tests=true --summary all 2>&1 | grep -A5 "fx_platform_test"
```
Run a single reproduction manually:
```bash
./zig-out/bin/roc test/fx/bug_04_question_mark_not_implemented.roc
./zig-out/bin/roc test/fx/bug_09_numeric_fold_wrong.roc
./zig-out/bin/roc test/fx/bug_12_string_match_broken.roc
```
---
## Learnings and Updates
*(This section will be updated as fixes are implemented)*
### Bug 12 Investigation Notes (FIXED)
**Root Cause:** The string pattern canonicalization was using the wrong token to extract the string literal content.
**Details:**
- String patterns in the AST have two fields: `string_tok` (the StringStart token position) and `expr` (the parsed string expression)
- The old code at `Can.zig:6348` tried to extract content from `string_tok` directly, but `StringStart` is just a delimiter token (the opening quote), not the actual string content
- String tokenization works as: `StringStart``StringPart` (actual content) → `StringEnd`
- The actual string content lives in the `expr` field as a parsed string expression with `string_part` tokens
**Fix Location:** `src/canonicalize/Can.zig` lines 6348-6407
**Fix Implementation:**
1. Get the string expression from `e.expr` instead of `e.string_tok`
2. Extract parts from the string expression's parts list
3. For simple string literals, get the `string_part` token from the first (and only) part
4. Resolve that token to get the actual string content
5. Process escape sequences and create the `str_literal` pattern
**Code Change:**
```zig
.string => |e| {
const str_expr = self.parse_ir.store.getExpr(e.expr);
switch (str_expr) {
.string => |se| {
const parts = self.parse_ir.store.exprSlice(se.parts);
if (parts.len == 1) {
const part = self.parse_ir.store.getExpr(parts[0]);
switch (part) {
.string_part => |sp| {
const part_text = self.parse_ir.resolve(sp.token);
// ... create str_literal pattern
},
else => {},
}
}
},
else => {},
}
},
```
**Key Insight:** The interpreter's `str_literal` pattern matching code was correct all along - the bug was that canonicalization was storing empty/wrong strings in the pattern, so the comparison always failed and fell through to the wildcard case
### Bug 9 Investigation Notes (FIXED)
**Root Cause:** Flex type variables were defaulting to Dec (128-bit decimal) instead of I64 (64-bit integer) at runtime.
**Details:**
- When a numeric literal like `42` appears without explicit type annotation, its type is a flex variable
- The `getRuntimeLayout` function defaulted flex vars to Dec (128-bit)
- When the value was later used in a context expecting I64 (e.g., `I64.to_str()`), only 8 bytes were read from 16 bytes of Dec data
- This produced garbage values because the memory layout didn't match
**Debugging Process:**
1. Created isolated test cases to narrow down the issue:
- Simple var reassignment in for loop: WORKED
- Var inside lambda without for loop: FAILED
- Lambda with explicit type annotation: WORKED
- Lambda with inferred type: FAILED
2. Determined the issue was with untyped lambdas returning numeric values
3. Found that U8, I32, U64 all worked with type annotations, but unannotated numeric returns failed
4. Root cause: `getRuntimeLayout` in interpreter.zig at line 5563-5571 defaulted flex vars to Dec
**Fix Location:** `src/eval/interpreter.zig` lines 5563-5571
**Fix Implementation:**
Changed the default from Dec (128-bit decimal) to I64 (64-bit signed integer):
```zig
// BEFORE:
if (resolved.desc.content == .flex) {
const dec_layout = layout.Layout.frac(types.Frac.Precision.dec);
...
}
// AFTER:
if (resolved.desc.content == .flex) {
const i64_layout = layout.Layout.int(types.Int.Precision.i64);
...
}
```
**Key Insight:** Integer literals should default to I64, not Dec. The original code was using Dec as the default for all unresolved numeric types, which is incorrect for integer literals. Decimal literals (with fractional parts) are handled separately via `e_dec`, `e_frac_f32`, `e_frac_f64` expression types
### Bug 4 Implementation Notes (FIXED)
**Root Cause:** The `?` operator was not implemented in canonicalization - it just returned a "NOT IMPLEMENTED" diagnostic.
**Fix Implementation:** Desugar `expr?` into a match expression:
```roc
match expr {
Ok($q_ok) => $q_ok,
Err($q_err) => return Err($q_err),
}
```
**Fix Location:** `src/canonicalize/Can.zig` lines 5073-5234
**Implementation Details:**
1. Canonicalize the inner expression (the expression before `?`)
2. Create synthetic identifiers for Ok value (`$q_ok`) and Err value (`$q_err`)
3. Create tag identifiers for `Ok` and `Err`
4. For each branch, enter a new scope to isolate pattern bindings
5. Create assign patterns for the inner values
6. Introduce patterns into scope via `scopeIntroduceInternal`
7. Create `applied_tag` patterns for `Ok($q_ok)` and `Err($q_err)`
8. Create match branch patterns and branches
9. For Ok branch: create `e_lookup_local` to return the unwrapped value
10. For Err branch: create `e_tag` wrapping the error and `e_return` for early return
11. Mark both patterns as used via `used_patterns.put` to avoid unused variable warnings
12. Create the final `e_match` expression
**Key APIs Used:**
- `self.env.store.scratchPatternTop()` / `addScratchPattern()` - for building pattern spans
- `self.env.store.scratchMatchBranchPatternTop()` / `addScratchMatchBranchPattern()` - for branch patterns
- `self.env.addPattern()` - create CIR patterns
- `self.env.addMatchBranchPattern()` - create match branch patterns
- `self.env.addMatchBranch()` - create match branches
- `self.env.addExpr()` - create CIR expressions
- `self.scopeIntroduceInternal()` - introduce patterns into scope
- `self.used_patterns.put()` - mark patterns as used
**Test File:** `test/fx/bug_04_question_mark_not_implemented.roc`

View file

@ -5073,8 +5073,8 @@ pub fn canonicalizeExpr(
.suffix_single_question => |unary| {
// Desugar `expr?` into:
// match expr {
// Ok($q_ok) => $q_ok,
// Err($q_err) => return Err($q_err),
// Ok(#ok) => #ok,
// Err(#err) => return Err(#err),
// }
const region = self.parse_ir.tokenizedRegionToRegion(unary.region);
@ -5083,16 +5083,16 @@ pub fn canonicalizeExpr(
// Canonicalize the inner expression (the expression before `?`)
const can_cond = try self.canonicalizeExpr(unary.expr) orelse return null;
// Create synthetic identifiers for the Ok value and Err value
const ok_val_ident = try self.env.insertIdent(base.Ident.for_text("$q_ok"));
const err_val_ident = try self.env.insertIdent(base.Ident.for_text("$q_err"));
const ok_tag_ident = try self.env.insertIdent(base.Ident.for_text("Ok"));
const err_tag_ident = try self.env.insertIdent(base.Ident.for_text("Err"));
// 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($q_ok) => $q_ok ===
// === Branch 1: Ok(#ok) => #ok ===
{
// Enter a new scope for this branch
try self.scopeEnter(self.env.gpa, false);
@ -5111,7 +5111,7 @@ pub fn canonicalizeExpr(
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($q_ok)
// Create the Ok tag pattern: Ok(#ok)
const ok_tag_pattern_idx = try self.env.addPattern(Pattern{
.applied_tag = .{
.name = ok_tag_ident,
@ -5128,7 +5128,7 @@ pub fn canonicalizeExpr(
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 $q_ok
// 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);
@ -5148,7 +5148,7 @@ pub fn canonicalizeExpr(
try self.env.store.addScratchMatchBranch(ok_branch_idx);
}
// === Branch 2: Err($q_err) => return Err($q_err) ===
// === Branch 2: Err(#err) => return Err(#err) ===
{
// Enter a new scope for this branch
try self.scopeEnter(self.env.gpa, false);
@ -5167,7 +5167,7 @@ pub fn canonicalizeExpr(
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($q_err)
// Create the Err tag pattern: Err(#err)
const err_tag_pattern_idx = try self.env.addPattern(Pattern{
.applied_tag = .{
.name = err_tag_ident,
@ -5184,15 +5184,15 @@ pub fn canonicalizeExpr(
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($q_err)
// First, create lookup for $q_err
// 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($q_err) tag expression
// 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);
@ -5204,7 +5204,7 @@ pub fn canonicalizeExpr(
},
}, region);
// Create return Err($q_err) expression
// Create return Err(#err) expression
const return_expr_idx = try self.env.addExpr(CIR.Expr{ .e_return = .{
.expr = err_tag_expr_idx,
} }, region);

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

@ -682,14 +682,8 @@ test "fx platform run from different cwd" {
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Hello from stdout!") != null);
}
// =============================================================================
// BUG REPRODUCTIONS - These tests document known bugs and will fail once fixed
// =============================================================================
test "question mark operator works" {
// Bug 4 (FIXED): The `?` postfix operator for error propagation now works.
// The operator desugars to a match expression that either unwraps Ok values
// or early returns Err values.
test "question mark operator" {
// Tests the `?` operator for error propagation.
const allocator = testing.allocator;
try ensureRocBinary(allocator);
@ -698,7 +692,7 @@ test "question mark operator works" {
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/bug_04_question_mark_not_implemented.roc",
"test/fx/question_mark_operator.roc",
},
});
defer allocator.free(run_result.stdout);
@ -708,9 +702,8 @@ test "question mark operator works" {
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "hello") != null);
}
test "numeric fold produces correct values" {
// Bug 9 (FIXED): List.fold with numeric accumulators now produces correct values.
// Regression test to ensure numeric fold operations work correctly.
test "numeric fold" {
// Tests List.fold with numeric accumulators.
const allocator = testing.allocator;
try ensureRocBinary(allocator);
@ -719,7 +712,7 @@ test "numeric fold produces correct values" {
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/bug_09_numeric_fold_wrong.roc",
"test/fx/numeric_fold.roc",
},
});
defer allocator.free(run_result.stdout);
@ -730,8 +723,7 @@ test "numeric fold produces correct values" {
}
test "string literal pattern matching" {
// Bug 12 (FIXED): Pattern matching on specific string literals now works correctly.
// Regression test to ensure string patterns match properly in match expressions.
// Tests pattern matching on string literals in match expressions.
const allocator = testing.allocator;
try ensureRocBinary(allocator);
@ -740,7 +732,7 @@ test "string literal pattern matching" {
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/bug_12_string_match_broken.roc",
"test/fx/string_pattern_matching.roc",
},
});
defer allocator.free(run_result.stdout);

View file

@ -426,7 +426,7 @@ pub fn runExpectListI64(src: []const u8, expected_elements: []const i64, should_
const builtin_types = BuiltinTypes.init(resources.builtin_indices, resources.builtin_module.env, resources.builtin_module.env, resources.builtin_module.env);
const imported_envs = [_]*const can.ModuleEnv{resources.builtin_module.env};
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping);
var interpreter = try Interpreter.init(test_allocator, resources.module_env, builtin_types, resources.builtin_module.env, &imported_envs, &resources.checker.import_mapping, null);
defer interpreter.deinit();
const enable_trace = should_trace == .trace;

View file

@ -2,10 +2,7 @@ app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
# Bug 9: Numeric fold produces incorrect values
# Expected: Sum: 15
# Actual: Sum: -3446744073709551616 (or similar garbage)
# Note: String fold works correctly
# Tests List.fold with numeric accumulators.
main! = || {
sum = [1, 2, 3, 4, 5].fold(0, |acc, n| acc + n)

View file

@ -2,11 +2,9 @@ app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
# Bug 4: The `?` operator
# Previously showed: "This feature is not yet implemented: canonicalize suffix_single_question expression"
# Now it works!
# Tests the `?` operator for error propagation.
# The operator unwraps Ok values or early-returns Err values.
# Helper function that can return an error
get_greeting : {} -> Try(Str, [ListWasEmpty])
get_greeting = |{}| {
first = List.first(["hello"])?

View file

@ -2,9 +2,7 @@ app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
# Bug 12: String literal matching in match doesn't work
# Expected: "Hello Alice!" then "Hey Bob!"
# Actual: "Hello stranger!" for both (always falls through to wildcard)
# Tests pattern matching on string literals in match expressions.
main! = || {
greet("Alice")

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