ERR/E5.1: program-wide inferred-! union per closure/fn shape
All occurrences of Closure(<sig>) -> (T, !) with a structurally identical value-signature now share one inferred error-set node; every bare-! closure literal of that shape unions its escape tags in, and a `try slot(x)` against any matching-shape slot widens the caller's named set against that union. This closes the gap where a slot call (no static function name) skipped the widening check entirely. - shape_inferred_sets keyed by closureShapeKey (params + value-return via mangleTypeName, error slot excluded) so bare-!, non-failable, .function and .closure of one value-sig collapse to a single key. - convergeClosureShapeSets pre-pass (lowerRoot Pass 1d', after the name-keyed convergeInferredErrorSets): collectClosureShapes walks fn bodies through lambda boundaries; recordClosureShape resolves each concrete bare-! literal's shape and unions its raises (+ try named_fn() edges via calleeEscapeTags) into the shape node. - checkEscapeWidening falls back to shapeKeyOfCallee for bare-! slot calls (computed from the callee expr's .function/.closure type). Empty union is silently allowed (sub-feature 6). Scope: concrete shapes only (generic lambdas skipped); closure-to-closure try edges are not fix-pointed (under-approximation = a missed diagnostic, never a miscompile). Tests: 1041 (positive — union composes, runs), 1042 (reject — two widening diagnostics, exit 1).
This commit is contained in:
36
examples/1041-errors-failable-closure-shape-union.sx
Normal file
36
examples/1041-errors-failable-closure-shape-union.sx
Normal file
@@ -0,0 +1,36 @@
|
||||
// Program-wide inferred-`!` union per closure shape (ERR E5.1 sub-feature 2).
|
||||
// All occurrences of `Closure(s32) -> (s32, !)` share ONE inferred error set;
|
||||
// every bare-`!` closure literal of that shape unions its raised tags in. A
|
||||
// `try slot(x)` against any matching-shape slot widens against that union — so
|
||||
// a caller whose named set covers { Negative, Other } type-checks, and the
|
||||
// error channel actually carries each closure's own tag at runtime.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
All :: error { Negative, Other }
|
||||
|
||||
// `h` is a bare-`!` Closure slot; the caller declares the union as `!All`.
|
||||
dispatch :: (h: Closure(s32) -> (s32, !), x: s32) -> (s32, !All) {
|
||||
return try h(x);
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
gpa := GPA.init();
|
||||
push Context.{ allocator = xx gpa } {
|
||||
// Two literals of the SAME shape raising DIFFERENT tags both feed the
|
||||
// one shared `Closure(s32)->(s32,!)` union node.
|
||||
handlers : List(Closure(s32) -> (s32, !)) = .{};
|
||||
handlers.append(closure((x: s32) -> (s32, !) { if x < 0 { raise error.Negative; } return x * 2; }));
|
||||
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x + 100; }));
|
||||
|
||||
// success paths
|
||||
print("ok0={}\n", dispatch(handlers.items[0], 5) catch e 0); // 10
|
||||
print("ok1={}\n", dispatch(handlers.items[1], 7) catch e 0); // 107
|
||||
|
||||
// failure paths: each closure raises its own tag, which propagates
|
||||
// through `try` and is absorbed by the call-site `catch` fallback
|
||||
print("err0={}\n", dispatch(handlers.items[0], -1) catch e -1); // raised Negative → -1
|
||||
print("err1={}\n", dispatch(handlers.items[1], 0) catch e -2); // raised Other → -2
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
25
examples/1042-errors-failable-closure-shape-union-reject.sx
Normal file
25
examples/1042-errors-failable-closure-shape-union-reject.sx
Normal file
@@ -0,0 +1,25 @@
|
||||
// Program-wide closure-shape union — widening REJECTION (ERR E5.1 sub-feature 2).
|
||||
// Two closure literals of shape `Closure(s32)->(s32,!)` raise `Negative` /
|
||||
// `Other`; the shared inferred-`!` node for that shape is { Negative, Other }.
|
||||
// A caller that `try`s a slot of this shape but declares only `!Small` (which
|
||||
// omits both tags) is rejected — the union is checked against the caller's set
|
||||
// even though the call goes through a slot with no static function name.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Small :: error { Unrelated }
|
||||
|
||||
reject :: (h: Closure(s32) -> (s32, !), x: s32) -> (s32, !Small) {
|
||||
return try h(x); // Negative, Other ∉ Small → two diagnostics
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
gpa := GPA.init();
|
||||
push Context.{ allocator = xx gpa } {
|
||||
handlers : List(Closure(s32) -> (s32, !)) = .{};
|
||||
handlers.append(closure((x: s32) -> (s32, !) { if x < 0 { raise error.Negative; } return x; }));
|
||||
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x; }));
|
||||
print("r={}\n", reject(handlers.items[0], 5) catch e 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
ok0=10
|
||||
ok1=107
|
||||
err0=-1
|
||||
err1=-2
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,11 @@
|
||||
error: error tag 'error.Negative' is not in caller's error set 'Small'
|
||||
--> /Users/agra/projects/sx/examples/1042-errors-failable-closure-shape-union-reject.sx:13:12
|
||||
|
|
||||
13 | return try h(x); // Negative, Other ∉ Small → two diagnostics
|
||||
| ^^^^^^^^
|
||||
|
||||
error: error tag 'error.Other' is not in caller's error set 'Small'
|
||||
--> /Users/agra/projects/sx/examples/1042-errors-failable-closure-shape-union-reject.sx:13:12
|
||||
|
|
||||
13 | return try h(x); // Negative, Other ∉ Small → two diagnostics
|
||||
| ^^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user