fix(ir): reject non-type args to the 7 type-introspection builtins [F0.8]

size_of, align_of, field_count, type_name, type_eq, type_is_unsigned,
and is_flags silently reinterpreted a value argument as a type:
type_is_unsigned(6) read 6 as a TypeId index (types[6] = u8 -> true),
size_of(6)/size_of(true) sized its typeof (8), type_name(6) returned
types[6]'s name. Per Agra's ruling, all 7 now strictly require a type
(compile-time): a value argument is a compile error.

One shared guard (Lowering.reflectionTypeArgGuard, run at the top of
tryLowerReflectionCall) classifies each arg via reflectionArgIsType: a
spelled / compile-time type or generic type parameter (isStaticTypeArg),
or a runtime Type value (static type .any -- type_of(x), a []Type
element list[i], a Type-typed local/field/param) is accepted; anything
else is rejected with "<builtin> expects a type, got '<type>'". The
runtime path for type_name / type_is_unsigned is preserved (the {}
formatter calls type_is_unsigned(type_of(val)) at runtime). The 5
comptime-only builtins stay comptime-only (runtime reflection deferred).

Regression: examples/1144-diagnostics-reflection-builtin-needs-type.sx
(reject cases across all 7, exit 1). Unit test: reflectionArgIsType in
lower.test.zig. specs.md / readme.md document the strict type
requirement (and add the previously-undocumented align_of, type_eq,
type_is_unsigned). issues/0090 RESOLVED banner updated.
This commit is contained in:
agra
2026-06-05 11:22:59 +03:00
parent 64f77e9779
commit b053c64149
9 changed files with 210 additions and 1 deletions

View File

@@ -38,6 +38,29 @@
> pins both extremes plus a width spread (s8/s16/s32 + u8/u16/u32/u64,
> mins/maxes, 0, ordinary values). Unit tests: `isUnsignedInt` in
> `src/ir/types.test.zig`.
>
> **Follow-up (F0.8 attempt 2) — strict `$T: Type` on all 7 reflection
> builtins.** The stress-review of the additive `type_is_unsigned` builtin
> found it (and the whole reflection family) silently accepted a non-type
> argument: `type_is_unsigned(6)` reinterpreted `6` as a TypeId index and
> returned the signedness of `types[6]` (`u8` → true); `size_of(6)`/`(true)`
> sized its `typeof` (8); `type_name(6)` returned `types[6]`'s name.
> Per Agra's ruling, all 7 type-introspection builtins — `size_of`,
> `align_of`, `field_count`, `type_name`, `type_eq`, `type_is_unsigned`,
> `is_flags` — now STRICTLY require a type (compile-time): a value argument
> is rejected with `"<builtin> expects a type, got '<type>'"`.
> - `src/ir/lower.zig` — one shared guard, `reflectionTypeArgGuard` (run at
> the top of `tryLowerReflectionCall`), classifies each arg via
> `reflectionArgIsType`: a spelled / compile-time type or generic type
> param (the `isStaticTypeArg` shapes), or a runtime `Type` value (static
> type `.any` — `type_of(x)`, a `[]Type` element `list[i]`, a `Type`-typed
> local / field / param) is ACCEPTED; anything else is rejected. The
> existing runtime path for `type_name` / `type_is_unsigned` is preserved
> (the formatter calls `type_is_unsigned(type_of(val))` at runtime). The 5
> comptime-only builtins stay comptime-only (runtime reflection deferred).
> - Negative regression: `examples/1144-diagnostics-reflection-builtin-needs-type.sx`
> (reject cases across all 7, exit 1). Unit test: `reflectionArgIsType` in
> `src/ir/lower.test.zig`.
> STATUS (original): OPEN. Pre-existing + orthogonal; surfaced (not introduced) by NL.1.
> Manager-verified independent of the numeric-limit accessors. Scheduled separately.