diff --git a/examples/212-generic-struct-protocol-erase.sx b/examples/212-generic-struct-protocol-erase.sx new file mode 100644 index 0000000..9e7e09a --- /dev/null +++ b/examples/212-generic-struct-protocol-erase.sx @@ -0,0 +1,33 @@ +// Phase 6 / issue 0054 — generic-struct → parameterized-protocol erasure. +// - A generic-struct impl method `self: *Box` now resolves `self.field` to the +// concrete INSTANCE (the template name binds to the instance type), so +// `self.x` works (was "field not found on type 'Box'"). +// - `xx c` erases a generic-struct instance (`Combined__s64_s64`) to a +// parameterized protocol (`VL(s64)`) via the generic +// `impl VL($R) for Combined($R, ..$Ts)`: the thunk monomorphizes the +// template method for the instance and dispatch works (was a trap). + +#import "modules/std.sx"; + +VL :: protocol(T: Type) { get :: () -> T; } +IntCell :: struct { v: s64; } +impl VL(s64) for IntCell { get :: (self: *IntCell) -> s64 => self.v; } + +Combined :: struct($R: Type, ..$Ts: []Type) { + sources: (..VL(Ts)); + value: $R; +} +impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; } + +make :: (..sources: VL) -> VL(s64) { + c : Combined(s64, ..sources.T) = ---; + c.value = 99; + c.sources = (..sources); + return xx c; // Combined__s64_s64 -> VL(s64) +} + +main :: () -> s32 { + r := make(IntCell.{ v = 1 }); + print("{}\n", r.get()); // 99 (dispatch through the erased Combined) + 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 167d435..0809aab 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11604,6 +11604,11 @@ pub const Lowering = struct { const id = if (table.findByName(name_id)) |existing| existing else table.intern(info); table.update(id, info); + // Bind the template name to this concrete instance so a method's + // `self: *Combined` (the template name) resolves to `*Combined__s64_s64` + // — otherwise `self.field` hits the 0-field generic stub. + tb.put(tmpl.name, id) catch {}; + // Store the type bindings and template name for method resolution const owned_mangled = self.alloc.dupe(u8, mangled_name) catch return id; self.struct_instance_bindings.put(owned_mangled, tb) catch {}; @@ -12879,8 +12884,21 @@ pub const Lowering = struct { // Ensure the concrete method is lowered const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ concrete_type_name, method.name }) catch method.name; - if (self.fn_ast_map.contains(qualified) and !self.lowered_functions.contains(qualified)) { - self.lazyLowerFunction(qualified); + if (!self.lowered_functions.contains(qualified)) { + if (self.fn_ast_map.contains(qualified)) { + self.lazyLowerFunction(qualified); + } else if (self.struct_instance_template.get(concrete_type_name)) |tmpl_name| { + // Generic-struct instance (`Combined__s64_s64`): the impl method + // is registered under the template name (`Combined.get`). + // Monomorphize it for this instance's bindings so the thunk has a + // concrete `Combined__s64_s64.get` to call. + const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, method.name }) catch method.name; + if (self.fn_ast_map.get(tmpl_qualified)) |fd| { + if (self.struct_instance_bindings.getPtr(concrete_type_name)) |bindings| { + self.monomorphizeFunction(fd, qualified, bindings); + } + } + } } // Call the concrete method: ConcreteType.method(__sx_ctx?, ctx, args...). diff --git a/tests/expected/212-generic-struct-protocol-erase.exit b/tests/expected/212-generic-struct-protocol-erase.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/212-generic-struct-protocol-erase.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/212-generic-struct-protocol-erase.txt b/tests/expected/212-generic-struct-protocol-erase.txt new file mode 100644 index 0000000..3ad5abd --- /dev/null +++ b/tests/expected/212-generic-struct-protocol-erase.txt @@ -0,0 +1 @@ +99