diff --git a/examples/0544-packs-imported-pack-fn-fixed-param-source-pin.sx b/examples/0544-packs-imported-pack-fn-fixed-param-source-pin.sx new file mode 100644 index 0000000..ec2385f --- /dev/null +++ b/examples/0544-packs-imported-pack-fn-fixed-param-source-pin.sx @@ -0,0 +1,22 @@ +// Regression (stdlib E4): an imported pack function whose fixed-prefix param +// type is visible only in its defining module must resolve during pack +// monomorphization. `lib.sx` imports `dep.sx` (which defines `Needs`) and +// exposes `make() -> Needs` plus `use_pack(n: Needs, ..$args)`. `main` imports +// ONLY `lib.sx`, so `Needs` is two flat hops away and not bare-visible here — +// main never names it. +// +// Before the fix, `monomorphizePackFn` restored the caller's source before +// re-binding the fixed-prefix params, so `n: Needs` was resolved in main's +// context and rejected with "type 'Needs' is not visible" — even though the +// control plain fn `use_plain(n: Needs)` (typed via the source-pinned call-arg +// path) ran fine. The fixed-prefix param is now resolved under the pack fn's own +// source (`fd.body.source_file`), matching the rest of the pack signature/body. +#import "modules/std.sx"; +#import "0544-packs-imported-pack-fn-fixed-param-source-pin/lib.sx"; + +main :: () -> s32 { + x := make(); + print("plain {}\n", use_plain(x)); + print("pack {}\n", use_pack(x, 1, 2)); + return 0; +} diff --git a/examples/0544-packs-imported-pack-fn-fixed-param-source-pin/dep.sx b/examples/0544-packs-imported-pack-fn-fixed-param-source-pin/dep.sx new file mode 100644 index 0000000..8cefe6c --- /dev/null +++ b/examples/0544-packs-imported-pack-fn-fixed-param-source-pin/dep.sx @@ -0,0 +1,3 @@ +// `Needs` lives two flat-import hops away from `main` (main -> lib -> dep), so +// it is NOT bare-visible at the call site under non-transitive visibility. +Needs :: struct { v: s64; } diff --git a/examples/0544-packs-imported-pack-fn-fixed-param-source-pin/lib.sx b/examples/0544-packs-imported-pack-fn-fixed-param-source-pin/lib.sx new file mode 100644 index 0000000..5519bed --- /dev/null +++ b/examples/0544-packs-imported-pack-fn-fixed-param-source-pin/lib.sx @@ -0,0 +1,16 @@ +// `lib.sx` imports `dep.sx`, so `Needs` is bare-visible HERE. A module that +// imports only `lib.sx` cannot see `Needs` (non-transitive). The pack fn's +// fixed-prefix param `n: Needs` must therefore resolve in this module's +// context, not the caller's. +#import "modules/std.sx"; +#import "dep.sx"; + +make :: () -> Needs => Needs.{ v = 7 }; + +// Control: a plain (non-pack) fn with the same fixed param already resolves in +// its defining module — the cross-module call-arg typing path is source-pinned. +use_plain :: (n: Needs) -> s64 => n.v; + +// Pack fn: the fixed-prefix param `n: Needs` is bound during pack +// monomorphization. Its type must resolve under this module's source. +use_pack :: (n: Needs, ..$args) -> s64 => n.v + args[0]; diff --git a/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.exit b/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.stderr b/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.stdout b/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.stdout new file mode 100644 index 0000000..07a0be7 --- /dev/null +++ b/examples/expected/0544-packs-imported-pack-fn-fixed-param-source-pin.stdout @@ -0,0 +1,2 @@ +plain 7 +pack 8 diff --git a/src/ir/lower.zig b/src/ir/lower.zig index b4c9890..0e17847 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11324,8 +11324,12 @@ pub const Lowering = struct { if (!p.is_comptime) { // Contextually type the arg from the param (so a lambda arg // `(x) => …` takes its param types from a `Closure(...)` param). + // The param type is resolved under the pack fn's OWN source + // (E4): a fixed-prefix type bare-visible only in the defining + // module must resolve there, not the caller's. The arg itself + // is lowered AFTER, in the caller's context. const saved_tt = self.target_type; - const pty = self.resolveParamType(&p); + const pty = self.resolveParamTypeInSource(fd.body.source_file, &p); if (pty != .unresolved) self.target_type = pty; args.append(self.alloc, self.lowerExpr(call_node.args[ri])) catch return self.builder.constInt(0, .void); self.target_type = saved_tt; @@ -11634,7 +11638,12 @@ pub const Lowering = struct { ct_arg_idx += 1; continue; } - const pty = self.resolveParamType(&p); + // Pin to the pack fn's OWN module (E4): a fixed-prefix param whose + // type is bare-visible only in the defining module must resolve + // there, not in the caller's restored context. Mirrors the + // signature build above and `resolveParamTypeInSource` at the + // cross-module call-arg typing sites. + const pty = self.resolveParamTypeInSource(fd.body.source_file, &p); const slot = self.builder.alloca(pty); self.builder.store(slot, Ref.fromIndex(param_idx)); scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true });