issue-0028: ?Protocol = null sentinel-shaped optional protocols

Protocol structs registered via registerProtocolDecl carry a new
is_protocol flag; the ?T paths in sizeOf/typeSizeBytes/toLLVMType
recognise it and lay out ?Protocol as the protocol struct itself
(ctx == null IS the "none" state), matching how ?Closure / ?*T are
sentinel-shaped — no extra storage.

Method dispatch on ?Protocol auto-unwraps in lowerCall's field-access
path; the unwrap is structurally a no-op so we just rebind obj_ty to
the payload type. resolveCallParamTypes extended for optional-protocol
receivers so enum-literal args (gpu.create_texture(.r8, ...)) get the
right target_type and don't silently collapse to tag=0 : s32 — same
issue-0031-class bug closed in Session 66, one type-system layer
deeper.

Library: UIRenderer / UIPipeline / GlyphCache migrated from the verbose
gpu: GPU = ---; has_gpu: bool pattern to gpu: ?GPU = null. set_gpu no
longer maintains a parallel bool flag.

Bundled: dock.sx threads delta_time as a struct field rather than via
a global pointer (cleanup unrelated to issue-0028, committed alongside).

Verified: 85/85 regression tests pass; iOS-sim chess + macOS chess
both render correctly post-migration.
This commit is contained in:
agra
2026-05-18 18:32:55 +03:00
parent f9ecf9d00e
commit 79419b99bd
11 changed files with 117 additions and 93 deletions

View File

@@ -0,0 +1,34 @@
// `?Protocol = null` — optional protocol boxes use sentinel-shape
// (ctx == null is the "none" state), so they cost no extra storage
// beyond the protocol's standard 2-pointer layout. Method calls on
// a non-null optional protocol auto-unwrap and dispatch through the
// vtable / inline fn-ptrs as usual.
#import "modules/std.sx";
GPU :: protocol {
ping :: () -> s64;
}
Impl :: struct {}
impl GPU for Impl {
ping :: (self: *Impl) -> s64 { 42; }
}
main :: () -> s32 {
g : ?GPU = null;
if g != null {
print("BAD: g not null at start\n");
} else {
print("g initially null\n");
}
g = xx @Impl.{};
if g != null {
n := g.ping();
print("after assign: g.ping() = {}\n", n);
} else {
print("BAD: g still null after assign\n");
}
0;
}

View File

@@ -1,53 +0,0 @@
// issue-0028: Feature — make protocol boxes assignable to an optional
// type so callers can spell "no GPU bound" as `?GPU = null` instead of
// the verbose `T = ---; has_T: bool` pattern.
//
// ── Current pattern (verbose) ─────────────────────────────────────────────
//
// gpu: GPU = ---;
// has_gpu: bool = false;
// ...
// if self.has_gpu { self.gpu.create_shader(...); }
//
// ── Proposed pattern ──────────────────────────────────────────────────────
//
// gpu: ?GPU = null;
// ...
// if self.gpu != null { self.gpu.create_shader(...); }
//
// ── Where the verbose pattern lives today ─────────────────────────────────
//
// library/modules/ui/renderer.sx — UIRenderer.gpu + has_gpu
// library/modules/ui/glyph_cache.sx — GlyphCache.gpu + has_gpu
// library/modules/ui/pipeline.sx — UIPipeline.gpu + has_gpu (+ set_gpu)
// library/modules/platform/uikit.sx — UIKitPlatform.frame_closure +
// has_frame_closure (Closure type,
// same pattern but on a closure)
//
// ── Implementation sketch ─────────────────────────────────────────────────
//
// Protocol boxes are 2-pointer structs ({vtable, ctx} or {ctx, fn_ptrs...}
// depending on the inline-vs-vtable shape — see src/ir/lower.zig
// `buildProtocolValue` ~7800-7869). `?T` for these can use `vtable_ptr ==
// null` (or `ctx == null`, depending on layout choice) as the "none"
// sentinel — no extra storage needed. This matches the existing
// optional-closure handling at src/ir/emit_llvm.zig where `?Closure` uses
// `fn_ptr == null` as none.
//
// Approach:
// 1. Extend `?T` type construction to accept T being a protocol type.
// Files: src/ir/types.zig + src/ir/lower.zig (type-resolution).
// 2. Implement `optional_wrap` / `optional_unwrap` /
// `optional_has_value` for protocol-typed payloads in
// src/ir/emit_llvm.zig — model after the closure-optional path.
// 3. Keep the existing `T = ---; has_T: bool` pattern working — the
// new `?T` is additive, not a replacement. Don't churn existing
// files (uikit.sx's frame_closure pattern stays).
//
// ── Syntax constraint ─────────────────────────────────────────────────────
//
// `?T` syntax already exists for primitives + pointers. Extending to
// protocols is a type-system change; no new surface syntax needed.
#import "modules/std.sx";
main :: () -> s32 { 0; }