issue 0153 RESOLVED: pin generic return-type resolution to the fn's defining module
inferGenericReturnType resolved a generic call's return-type AST ($R, !E) in the CALL-SITE module context. For a re-exported fn the error-set name (LE / IoErr, re-exported as LE :: lib.LE) resolved through the call-site alias to a TypeId NOT tagged .error_set, so the planned result was a tuple whose last field wasn't an error set — errorChannelOf saw a plain tuple and the value- failable's ! channel was lost (try/or rejected it / built a malformed i1 PHI). monomorphizeFunction already pins the source to the fn's defining module before resolving the return type; inferGenericReturnType did not, so the planned call-result type disagreed with the instance's real signature. Fix: pin the source to fd.body.source_file around the return-type resolution (binding-build stays in the call-site context — its args are typed there). Regression test examples/1058-errors-reexport-value-failable-channel.sx (+ companion lib.sx). Suite green 732/0.
This commit is contained in:
23
examples/1058-errors-reexport-value-failable-channel.sx
Normal file
23
examples/1058-errors-reexport-value-failable-channel.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
// A generic value-failable fn `($R, !E)` reached through a RE-EXPORT alias
|
||||
// keeps its `!` error channel at the call site — the result types as a
|
||||
// value-failable, so `or` / `try` accept it. Mirrors std.sx's
|
||||
// `await :: io_mod.await` (+ `IoErr :: io_mod.IoErr`) re-export.
|
||||
// Regression (issue 0153): the planned call-result type was resolved in the
|
||||
// CALL-SITE module (where `LE` is a re-export alias → a non-`.error_set`
|
||||
// TypeId), so `errorChannelOf` saw a plain tuple and `b.get() or {…}` built a
|
||||
// malformed i1 PHI. The fix pins return-type resolution to the fn's defining
|
||||
// module, matching `monomorphizeFunction`. Needs BOTH generic + re-export.
|
||||
#import "modules/std.sx";
|
||||
lib :: #import "examples/1058-errors-reexport-value-failable-channel/lib.sx";
|
||||
|
||||
// Re-export the generic fn AND its error set (the std.sx facade pattern).
|
||||
Box :: lib.Box;
|
||||
get :: lib.get;
|
||||
LE :: lib.LE;
|
||||
|
||||
main :: () -> i32 {
|
||||
b : Box(i64) = .{ v = 42 };
|
||||
r := b.get() or { -1 }; // value-failable channel preserved → r=42
|
||||
print("r={}\n", r);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
r=42
|
||||
@@ -1,5 +1,34 @@
|
||||
# 0153 — a re-exported generic value-failable `($R, !E)` loses its `!` error channel
|
||||
|
||||
## ✅ RESOLVED (2026-06-21)
|
||||
|
||||
**Root cause** — `GenericResolver.inferGenericReturnType`
|
||||
(`src/ir/generics.zig`) resolved the generic call's return-type AST
|
||||
(`($R, !E)`) in the CALL-SITE module context. For a re-exported fn the error
|
||||
set name (`LE` / `IoErr`, re-exported as `LE :: lib.LE`) resolved through the
|
||||
call-site alias to a TypeId that is NOT tagged `.error_set`, so the planned
|
||||
result was a tuple whose last field wasn't an error set — `errorChannelOf`
|
||||
(`lower/error.zig:148`) saw a plain tuple and the failable channel was lost.
|
||||
`monomorphizeFunction` already pins the source to the fn's defining module
|
||||
before resolving the return type; `inferGenericReturnType` did not, so the
|
||||
planned call-result type and the instance's real signature disagreed.
|
||||
|
||||
**Fix** — pin the source to the function's defining module
|
||||
(`fd.body.source_file`) around the return-type resolution in
|
||||
`inferGenericReturnType`, mirroring `monomorphizeFunction`. The binding-build
|
||||
stays in the call-site context (its args are typed there). Now the `!E`
|
||||
resolves to the same `.error_set` TypeId the instance's signature uses.
|
||||
|
||||
**Verified** — the repro prints `r=42`; regression test
|
||||
`examples/1058-errors-reexport-value-failable-channel.sx` (+ companion
|
||||
`lib.sx`). This also unblocked the B1.2 async surface end-to-end:
|
||||
`examples/1805-concurrency-io-blocking-async.sx` (`sum: 42` / `double: 42` /
|
||||
`clock ok`) + `examples/1806-concurrency-io-cancel.sx` (cancel → `await`
|
||||
raises `.Canceled`). Full suite green.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Symptom
|
||||
|
||||
A generic function returning a value-failable `($R, !E)` keeps its error
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Repro for issue 0153 — a generic value-failable fn `($R, !E)` reached
|
||||
// through a RE-EXPORT alias loses its `!` error channel at the call site:
|
||||
// the result is typed as a plain tuple, so `try`/`or` reject it / build a
|
||||
// malformed PHI. Needs BOTH generic + re-export: a non-generic re-export
|
||||
// works, and a directly-imported (non-re-exported) generic value-failable
|
||||
// works. Mirrors std.sx's `await :: io_mod.await` (+ `IoErr :: io_mod.IoErr`).
|
||||
#import "modules/std.sx";
|
||||
lib :: #import "issues/0153-reexport-generic-value-failable-loses-error-channel/lib.sx";
|
||||
|
||||
// Re-export the generic fn AND its error set (the std.sx facade pattern).
|
||||
Box :: lib.Box;
|
||||
get :: lib.get;
|
||||
LE :: lib.LE;
|
||||
|
||||
main :: () -> i32 {
|
||||
b : Box(i64) = .{ v = 42 };
|
||||
r := b.get() or { -1 }; // BUG: PHI i1/i64 mismatch (was: clean → r=42)
|
||||
print("r={}\n", r);
|
||||
return 0;
|
||||
}
|
||||
@@ -284,6 +284,18 @@ pub const GenericResolver = struct {
|
||||
// tag.
|
||||
var scope = TypeBindingScope.enter(self.l, tmp_bindings);
|
||||
defer scope.exit();
|
||||
// Resolve the return type in the function's DEFINING module, exactly
|
||||
// as `monomorphizeFunction` does — so a name in the return type (e.g.
|
||||
// the error set of a value-failable `(… , !E)`) resolves to the SAME
|
||||
// TypeId the instance's real signature uses, not whatever a re-export
|
||||
// alias at the call site resolves it to. Without this pin a re-exported
|
||||
// generic value-failable's `!E` resolved to a non-`.error_set` alias,
|
||||
// so the planned call result was a plain tuple and `errorChannelOf`
|
||||
// missed the failable channel (issue 0153). The binding-building above
|
||||
// stays in the call-site context (its args are typed there).
|
||||
const saved_src = self.l.current_source_file;
|
||||
defer self.l.setCurrentSourceFile(saved_src);
|
||||
if (fd.body.source_file) |src| self.l.setCurrentSourceFile(src);
|
||||
return self.l.resolveTypeWithBindings(fd.return_type.?);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user