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:
5
specs.md
5
specs.md
@@ -2221,6 +2221,7 @@ Built-in functions are declared in `std.sx` with the `#builtin` suffix, which te
|
||||
- `memcpy(dst: *void, src: *void, size: s64) -> *void` — copy `size` bytes from `src` to `dst`
|
||||
- `memset(dst: *void, val: s64, size: s64) -> void` — fill `size` bytes at `dst` with `val`
|
||||
- `size_of($T: Type) -> s64` — size of type `T` in bytes
|
||||
- `align_of($T: Type) -> s64` — alignment of type `T` in bytes
|
||||
|
||||
### Type Introspection
|
||||
- `type_of(val: $T) -> Type` — returns the runtime type tag of a value
|
||||
@@ -2231,6 +2232,10 @@ Built-in functions are declared in `std.sx` with the `#builtin` suffix, which te
|
||||
- `field_value_int($T: Type, idx: s64) -> s64` — returns the integer value of the `idx`-th enum variant
|
||||
- `field_index($T: Type, val: T) -> s64` — returns the sequential variant index for an explicit enum value (reverse of `field_value_int`). Returns `-1` if no variant matches.
|
||||
- `is_flags($T: Type) -> bool` — returns `true` if `T` is a flags enum (declared with `#flags`)
|
||||
- `type_eq($A: Type, $B: Type) -> bool` — structural TypeId equality (`type_eq(s64, s64)` is `true`, distinct shapes are `false`); folds at compile time, so `inline if type_eq(...)` is comptime-decidable
|
||||
- `type_is_unsigned($T: Type) -> bool` — `true` if `T` is an unsigned integer (`u8`/`u16`/`u32`/`u64`/`usize`); used by `{}` formatting to print unsigned integers as unsigned decimal
|
||||
|
||||
The seven type-only builtins — `size_of`, `align_of`, `field_count`, `type_name`, `type_eq`, `type_is_unsigned`, `is_flags` — strictly require a **type** argument: a spelled type (`s64`, `*u8`, `Point`), a generic type parameter (`T`), or a runtime `Type` value (`type_of(x)`, a `[]Type` element, a `Type`-typed local). Passing a value (`size_of(6)`, `type_is_unsigned(true)`) is a compile-time error — `<builtin> expects a type, got '<type>'` — not a silent reinterpretation of the value's bits as a type.
|
||||
|
||||
### Type Conversion
|
||||
- `cast(Type) expr` — prefix operator that converts `expr` to `Type`. Examples: `cast(s32) 3.14`, `cast(f64) n`. When `Type` is a runtime `Type` value inside a type-category match arm, the compiler generates a dispatch switch over all types in the category, monomorphizing the callee for each concrete type.
|
||||
|
||||
Reference in New Issue
Block a user