fibers: address adversarial review of the B1 changes (6 findings)
UFCS generic overload resolution (issue 0157 follow-ups): - P1-a: call planning (calls.zig) used the last-wins fn_ast_map winner while lowering reselected by receiver, so the planned result type could disagree with the dispatched function and misbox the result. Both now share selectUfcsGenericByReceiver(.., fd0). - P1-b: selection scanned module_decls globally, flagging a transitively-hidden same-named overload as a false ambiguity. Now two-tier: directly-visible authors first (ambiguity only among those), global fallback for receiver-reachable namespaced methods (e.g. Task.cancel) that defers to fd0 on a hidden tie. - P2-b: boolean specificity tied *$T with *Box($T). Now peels pointer layers so the structurally-narrower receiver wins. Scheduler (sched.sx): - P1-c: a second concurrent Task.wait overwrote the single waiter slot -> silent deadlock. Now one-awaiter-per-task loud abort. - P2-c: sleep(negative) rewound the monotonic virtual clock. Rejected loudly. (P2-a, non-generic-winner-hides-generic, did not reproduce -- the non-generic arm already falls through.) Regressions: examples/generics/0218 (receiver specificity + plan/lowering agreement), examples/concurrency/1818 (negative-sleep abort), 1819 (double-wait abort). Suite green 758/0.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
// `sleep(ms)` rejects a NEGATIVE duration loudly — the virtual clock is
|
||||
// monotonic (advances only as timers fire), so a negative deadline would rewind
|
||||
// it and break every ordering contract. Regression (B1.4b review, P2-c): the
|
||||
// guard aborts instead of silently arming a past deadline.
|
||||
//
|
||||
// aborts (exit 134) after the diagnostic — aarch64-macOS-pinned.
|
||||
#import "modules/std.sx";
|
||||
sched :: #import "modules/std/sched.sx";
|
||||
main :: () -> i64 {
|
||||
s := sched.Scheduler.init(); ps := @s;
|
||||
ps.spawn(() => { ps.sleep(10); ps.sleep(-5); }); // -5 → loud abort
|
||||
s.run();
|
||||
print("unreachable\n");
|
||||
return 0;
|
||||
}
|
||||
18
examples/concurrency/1819-concurrency-fiber-double-wait.sx
Normal file
18
examples/concurrency/1819-concurrency-fiber-double-wait.sx
Normal file
@@ -0,0 +1,18 @@
|
||||
// A `Task` allows ONE awaiter — a second concurrent `wait` on the same pending
|
||||
// task would overwrite the single `waiter` slot, and completion would wake only
|
||||
// the second, stranding the first forever. Regression (B1.4a review, P1-c): the
|
||||
// guard aborts loudly instead of silently deadlocking.
|
||||
//
|
||||
// aborts (exit 134) after the diagnostic — aarch64-macOS-pinned.
|
||||
#import "modules/std.sx";
|
||||
sched :: #import "modules/std/sched.sx";
|
||||
S :: struct { t: *sched.Task(i64); }
|
||||
main :: () -> i64 {
|
||||
st : S = ---; st.t = null;
|
||||
s := sched.Scheduler.init(); ps := @s; pst := @st;
|
||||
mkprod :: (ps: *sched.Scheduler, pst: *S) { pst.t = ps.go(() -> i64 => { ps.yield_now(); 42 }); }
|
||||
mkw :: (ps: *sched.Scheduler, pst: *S) { ps.spawn(() => { x := pst.t.wait() or { -1 }; print("got {}\n", x); }); }
|
||||
mkprod(ps, pst); mkw(ps, pst); mkw(ps, pst); // second waiter → loud abort
|
||||
s.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "target": "macos" }
|
||||
@@ -0,0 +1 @@
|
||||
134
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
sched: sleep(-5) — negative duration would rewind the virtual clock
|
||||
@@ -0,0 +1 @@
|
||||
{ "target": "macos" }
|
||||
@@ -0,0 +1 @@
|
||||
134
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
sched: wait() — task already has a waiter (one awaiter per task in the M:1 model)
|
||||
28
examples/generics/0218-generics-ufcs-receiver-specificity.sx
Normal file
28
examples/generics/0218-generics-ufcs-receiver-specificity.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// A more-specific UFCS receiver overload must win over a fully-generic one
|
||||
// (`*Box($T)` beats `*$T`), AND call planning must agree with lowering on which
|
||||
// overload is dispatched — else the result is typed by one function but produced
|
||||
// by another, misboxing it.
|
||||
//
|
||||
// Regression (issue 0157 review, P1-a + P2-b): the planner (calls.zig) used the
|
||||
// last-wins `fn_ast_map` winner while lowering reselected by receiver, so a
|
||||
// `*$T->string` winner could type a call that lowering dispatched to
|
||||
// `*Box($T)->i64` — boxing the i64 as a string pointer. And boolean specificity
|
||||
// treated `*$T` and `*Box($T)` as equal (false ambiguity). Fixed by sharing one
|
||||
// receiver-aware, pointer-peeling selection between planner and lowering.
|
||||
#import "modules/std.sx";
|
||||
#import "0218-generics-ufcs-receiver-specificity/0218-shared.sx";
|
||||
#import "0218-generics-ufcs-receiver-specificity/anyref.sx";
|
||||
|
||||
// More-specific receiver than the imported `pick(x: *$T)`.
|
||||
pick :: ufcs (b: *Box($T)) -> i64 { return 7; }
|
||||
|
||||
// Generic passthrough — its result type is the PLANNED type of its argument, so
|
||||
// a planner/lowering disagreement on `pick`'s return type surfaces here.
|
||||
echo :: (x: $U) -> $U { return x; }
|
||||
|
||||
main :: () -> i64 {
|
||||
b : Box(i64) = ---; b.v = 0;
|
||||
r := echo((@b).pick()); // *Box wins → i64 7; plan must agree (else misbox)
|
||||
print("r: {}\n", r);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Box :: struct ($T: Type) { v: T; }
|
||||
@@ -0,0 +1,5 @@
|
||||
// A fully-generic-receiver overload of `pick` (matches ANY pointer receiver),
|
||||
// returning a DIFFERENT type than the specific one — so a plan/lowering
|
||||
// disagreement would misbox the result.
|
||||
#import "0218-shared.sx";
|
||||
pick :: ufcs (x: *$T) -> string { return "generic"; }
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
r: 7
|
||||
Reference in New Issue
Block a user