From 9f143f79ecfcc901af8a80062f58d0b19ea8472e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 16 Dec 2025 07:53:38 -0500 Subject: [PATCH] Fix nominal pattern bound variable collection (issue #8689) collectBoundVarsToScratch was not recursing into the backing pattern for nominal and nominal_external patterns. This caused variables bound in nominal patterns (e.g., Wrapper.Simple(s)) to not be tracked as bound variables, leading to them incorrectly being included in the free variables of match expression bodies. Co-Authored-By: Claude Opus 4.5 --- src/canonicalize/Can.zig | 10 ++++++++-- src/cli/test/fx_test_specs.zig | 5 +++++ test/fx/issue8689.roc | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 test/fx/issue8689.roc diff --git a/src/canonicalize/Can.zig b/src/canonicalize/Can.zig index 631f9fd297..fcc8376880 100644 --- a/src/canonicalize/Can.zig +++ b/src/canonicalize/Can.zig @@ -2534,6 +2534,14 @@ fn collectBoundVarsToScratch(self: *Self, pattern_idx: Pattern.Idx) !void { } } }, + .nominal => |n| { + // Recurse into the backing pattern to collect bound variables + try self.collectBoundVarsToScratch(n.backing_pattern); + }, + .nominal_external => |n| { + // Recurse into the backing pattern to collect bound variables + try self.collectBoundVarsToScratch(n.backing_pattern); + }, .num_literal, .small_dec_literal, .dec_literal, @@ -2541,8 +2549,6 @@ fn collectBoundVarsToScratch(self: *Self, pattern_idx: Pattern.Idx) !void { .frac_f64_literal, .str_literal, .underscore, - .nominal, - .nominal_external, .runtime_error, => {}, } diff --git a/src/cli/test/fx_test_specs.zig b/src/cli/test/fx_test_specs.zig index b15013697c..079cca245b 100644 --- a/src/cli/test/fx_test_specs.zig +++ b/src/cli/test/fx_test_specs.zig @@ -243,6 +243,11 @@ pub const io_spec_tests = [_]TestSpec{ .io_spec = "1>is ok", .description = "Regression test: List.get with method syntax (issue #8662)", }, + .{ + .roc_file = "test/fx/issue8689.roc", + .io_spec = "1>hello", + .description = "Regression test: Field access on record payload in tag union wrapped by opaque type (issue #8689)", + }, }; /// Get the total number of IO spec tests diff --git a/test/fx/issue8689.roc b/test/fx/issue8689.roc new file mode 100644 index 0000000000..5dc3275f8c --- /dev/null +++ b/test/fx/issue8689.roc @@ -0,0 +1,19 @@ +app [main!] { pf: platform "./platform/main.roc" } + +import pf.Stdout + +Wrapper := [ + WithRecord({ name : Str, value : U64 }), + Simple(Str), +] + +getName : Wrapper -> Str +getName = |w| match w { + Wrapper.Simple(s) => s + Wrapper.WithRecord(payload) => payload.name +} + +main! = || { + wrapper = Wrapper.WithRecord({ name: "hello", value: 42 }) + Stdout.line!(getName(wrapper)) +}