fix(ir): reflection builtins on an Any read its runtime tag, not payload [F0.8]
`type_name` / `type_is_unsigned` on an `Any` argument unconditionally read
the Any's payload as a TypeId index. That is correct only when the Any holds
a Type value (`{ .any, tid }`); for an Any holding a runtime *value*
(`av : Any = 6`, tag s64, payload 6) it returned `types[6]` — `type_name(av)`
gave "u8" and `type_is_unsigned(av)` gave true.
Both backends now branch on the Any's runtime type-tag: tag == `.any` → the
box is a Type value, use the payload as the TypeId; otherwise the tag IS the
held value's type. So `type_name(av)` → "s64", `type_is_unsigned(av)` → false,
while `type_name(type_of(x))` still names the held type. The `{}` formatter is
unchanged (it already passed `type_of(val)`, a proper Type value).
- src/ir/interp.zig: shared `Value.reflectTypeId` tag-branching resolver; the
`type_name` / `type_is_unsigned` interp arms route through it.
- src/backend/llvm/ops.zig: shared `Ops.reflectArgTypeId` emits
extractvalue-tag / icmp-eq-.any / select for the runtime path; both
reflection arms route through it. The two backends agree.
- examples/0164-types-reflection-any-tag.sx: regression pinning type_name /
type_is_unsigned / print on an Any holding a value vs a Type.
- src/ir/interp.test.zig: unit test for `reflectTypeId`.
- 22 .ir snapshots: the new select appears in every std-importing program's
IR (any_to_string embeds these builtins) — benign, verified structurally
identical apart from the three new instructions.
- issues/0090, specs.md: documented the Any-tag rule.
This commit is contained in:
@@ -61,6 +61,32 @@
|
||||
> - 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`.
|
||||
>
|
||||
> **Follow-up (F0.8 attempt 3) — reflection builtins on an `Any` consult the
|
||||
> Any's runtime TYPE-TAG, not its payload.** The attempt-2 guard correctly
|
||||
> accepts an `Any` argument (the formatter passes `val: Any`), but the dynamic
|
||||
> `type_name` / `type_is_unsigned` path still read the Any's payload as a
|
||||
> TypeId index unconditionally — correct only when the Any holds a *Type
|
||||
> value*. For an Any holding a *value* (`av : Any = 6`, runtime tag `s64`,
|
||||
> payload `6`) it reported `types[6]` (`u8`): `type_name(av)` → `"u8"`,
|
||||
> `type_is_unsigned(av)` → `true`. Per Agra's ruling ("Any is a type AND a
|
||||
> value, so it's expected to work"), both builtins now branch on the Any's
|
||||
> runtime tag: tag `== .any` → the box is a Type value, use the payload as the
|
||||
> TypeId; otherwise the tag IS the held value's type. So `type_name(av)` →
|
||||
> `"s64"`, `type_is_unsigned(av)` → `false`, while `type_name(type_of(x))`
|
||||
> still names the held type. The formatter is unchanged (it already passed
|
||||
> `type_of(val)`, a proper Type value).
|
||||
> - `src/ir/interp.zig` — shared `Value.reflectTypeId` (the tag-branching
|
||||
> resolver); the `type_name` / `type_is_unsigned` interp arms route through
|
||||
> it. `src/backend/llvm/ops.zig` — shared `Ops.reflectArgTypeId` emits
|
||||
> `extractvalue tag` / `icmp eq tag, .any` / `select` for the runtime path;
|
||||
> both reflection arms route through it. The two backends agree.
|
||||
> - Regression: `examples/0164-types-reflection-any-tag.sx` pins `type_name` /
|
||||
> `type_is_unsigned` / `print` on an Any holding a value vs. a Type value.
|
||||
> Unit test: `reflectTypeId` in `src/ir/interp.test.zig`.
|
||||
> - Out of scope (kept comptime-only / deferred): the 5 comptime-only builtins
|
||||
> (`size_of`/`align_of`/`field_count`/`is_flags`/`type_eq`). `type_eq` has no
|
||||
> dynamic emit path (it folds at lower time), so it is unaffected.
|
||||
|
||||
> STATUS (original): OPEN. Pre-existing + orthogonal; surfaced (not introduced) by NL.1.
|
||||
> Manager-verified independent of the numeric-limit accessors. Scheduled separately.
|
||||
|
||||
Reference in New Issue
Block a user