comptime: empty-member types are valid for all kinds; keep never-defined declare rejected
A comptime-constructed type with NO members is now VALID for every kind
(empty struct, empty tuple, empty enum, empty tagged_union) — only a bare
`declare("X")` placeholder that is never completed by a matching `define`
stays rejected (it would panic codegen).
- comptime_vm.zig registerTypeVm: drop the blanket "a type with no members
is never valid" rejection. The per-kind loops are vacuous for an empty
member list and the dup-name checks stay correct.
- types.zig TaggedUnionInfo: add `defined: bool = true`. Every real
construction (normal unions, error sets, register_type completion) is
"defined" by default; only the two declare-PLACEHOLDER sites set it false:
comptime_vm.declareNominal and lower/comptime.preregisterForwardTypes.
- lower/comptime.checkComptimeTypeResult: reject on `!defined` (never-defined
placeholder) instead of `fields.len == 0`, so an explicitly-defined empty
union passes through while a never-completed declare is still gated.
- types.zig typeSizeBytes(tagged_union): floor the payload area at 8 bytes
when no field carries a payload, mirroring the LLVM lowering — fixes a
verifySizes panic on an empty/all-void tagged_union (IR sized to tag-only,
LLVM laid out tag + [8 x i8]).
Tests:
- examples/1179: repurposed from "empty enum rejected" (now valid) to the
never-defined `declare` case (the remaining rejection); preserves its
issue-0140 regression role.
- examples/1180 (duplicate variant): still rejected, unchanged output.
- examples/0641 (new): construct empty struct/tuple/enum/tagged_union via
define/declare; instantiate the constructible ones; exit 0.
This commit is contained in:
36
examples/0641-comptime-empty-types-valid.sx
Normal file
36
examples/0641-comptime-empty-types-valid.sx
Normal file
@@ -0,0 +1,36 @@
|
||||
// A comptime-constructed type with NO members is VALID for every kind:
|
||||
// - an empty struct `struct {}` and empty tuple `()` are zero-size aggregates
|
||||
// you can instantiate (`.{}`),
|
||||
// - an empty enum and an empty tagged_union are valid uninhabited / zero-member
|
||||
// types — legitimate to NAME and reference even though they have no
|
||||
// constructible value (no variant to construct).
|
||||
//
|
||||
// This mirrors normal sx, where `struct {}` and `enum {}` already codegen fine;
|
||||
// the metatype `define`/`declare` path now agrees (the old blanket
|
||||
// "a type with no members is never valid" rejection is gone).
|
||||
//
|
||||
// The ONLY thing still rejected on this path is a bare `declare("X")` that is
|
||||
// never completed by a matching `define` — an INCOMPLETE forward slot that would
|
||||
// panic codegen. That case is exercised by examples/1179.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
// Explicitly-defined empty types of every kind.
|
||||
EmptyStruct :: define(declare("EmptyStruct"), .struct(.{ fields = .[] }));
|
||||
EmptyTuple :: define(declare("EmptyTuple"), .tuple(.{ elements = .[] }));
|
||||
EmptyEnum :: define(declare("EmptyEnum"), .enum(.{ variants = .[] }));
|
||||
// An empty tagged_union (kind 3): no variants, but a valid named type. (The
|
||||
// `define` DSL maps an all-void variant set to a payloadless enum, so reach for
|
||||
// the register_type primitive directly to mint a 0-variant tagged_union.)
|
||||
EmptyUnion :: register_type(declare("EmptyUnion"), 3, .[]);
|
||||
|
||||
main :: () -> i32 {
|
||||
// Instantiate the constructible ones.
|
||||
s : EmptyStruct = .{};
|
||||
t : EmptyTuple = .{};
|
||||
_ = s;
|
||||
_ = t;
|
||||
// EmptyEnum / EmptyUnion are uninhabited — valid as types, no value to make.
|
||||
print("empty struct/tuple/enum/tagged_union are all valid\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
// A comptime type construction (declare/define, reflection) that bails in the
|
||||
// interpreter must surface a build-gating DIAGNOSTIC naming the reason — not
|
||||
// A comptime type construction (declare/define, reflection) that leaves a type
|
||||
// INCOMPLETE must surface a build-gating DIAGNOSTIC naming the reason — not
|
||||
// poison the decl to `.unresolved` silently and let that crash at LLVM emission
|
||||
// or hide behind a downstream cascade. Here `define` is handed an empty variant
|
||||
// list; the interp bails "enum has no variants", and `evalComptimeType` renders
|
||||
// that at the construction site (exit 1, no panic).
|
||||
// or hide behind a downstream cascade. Here `declare("Undefined")` mints a
|
||||
// forward nominal slot that is NEVER completed by a matching `define(handle, …)`;
|
||||
// the compiler rejects the incomplete type at its construction site (exit 1, no
|
||||
// panic).
|
||||
//
|
||||
// NOTE: an EXPLICITLY-defined empty type (empty struct/tuple/enum/tagged_union)
|
||||
// is VALID — see examples/0641. The remaining rejection is purely the
|
||||
// never-defined `declare` placeholder, which would otherwise panic codegen
|
||||
// (`verifySizes`: llvm_size != ir_size on an unsized forward slot).
|
||||
//
|
||||
// Regression (issue 0140): before the fix this panicked with "unresolved type
|
||||
// reached LLVM emission" (exit 134), because the interp's bail detail was
|
||||
@@ -11,9 +17,14 @@
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
Empty :: define(declare("Empty"), .enum(.{ variants = .[] }));
|
||||
// Declared but never `define`d — an incomplete forward slot.
|
||||
mk_undefined :: () -> Type {
|
||||
return declare("Undefined");
|
||||
}
|
||||
|
||||
Undefined :: mk_undefined();
|
||||
|
||||
main :: () -> i32 {
|
||||
e : Empty = ---;
|
||||
u : Undefined = ---;
|
||||
return 0;
|
||||
}
|
||||
|
||||
1
examples/expected/0641-comptime-empty-types-valid.exit
Normal file
1
examples/expected/0641-comptime-empty-types-valid.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/0641-comptime-empty-types-valid.stderr
Normal file
1
examples/expected/0641-comptime-empty-types-valid.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/expected/0641-comptime-empty-types-valid.stdout
Normal file
1
examples/expected/0641-comptime-empty-types-valid.stdout
Normal file
@@ -0,0 +1 @@
|
||||
empty struct/tuple/enum/tagged_union are all valid
|
||||
@@ -1,5 +1,5 @@
|
||||
error: comptime type construction failed: comptime register_type: a type with no members is never valid
|
||||
--> examples/1179-diagnostics-comptime-type-construction-bail.sx:14:10
|
||||
error: type 'Undefined' is declared but never defined — complete it with define(handle, info)
|
||||
--> examples/1179-diagnostics-comptime-type-construction-bail.sx:25:14
|
||||
|
|
||||
14 | Empty :: define(declare("Empty"), .enum(.{ variants = .[] }));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
25 | Undefined :: mk_undefined();
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
Reference in New Issue
Block a user