Merge branch 'flow/sx-foundation/F0.12' into dist-foundation
This commit is contained in:
32
examples/0613-comptime-print-any-type.sx
Normal file
32
examples/0613-comptime-print-any-type.sx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Comptime `#run` formatting of an `Any` that holds a `Type`.
|
||||||
|
//
|
||||||
|
// `print("{}", at)` where `at: Any` holds a `Type` value routes through
|
||||||
|
// `format` → `any_to_string`, whose `type := type_of(val)` lowers (for an
|
||||||
|
// `.any` operand) to `struct_get(val, 0)` — reading the Any-Type's tag.
|
||||||
|
// At runtime a `Type` value is the aggregate `{ tag=.any, value=tid }`,
|
||||||
|
// so the read works and the type's name prints. The comptime interpreter
|
||||||
|
// stores a first-class `Type` as a bare `.type_tag`, so the struct_get
|
||||||
|
// must mirror that same `{ .any, tid }` layout — otherwise it bails and
|
||||||
|
// the `#run` truncates. Reflection over the same `Any` (`type_name`,
|
||||||
|
// `type_is_unsigned`) already works; the value-print must match.
|
||||||
|
//
|
||||||
|
// Regression (issue 0096): a comptime `#run` print of an `Any`-held
|
||||||
|
// `Type` silently stopped (omitted `value=` + every later line) yet still
|
||||||
|
// built with exit 0.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
ct_probe :: () {
|
||||||
|
print("before\n");
|
||||||
|
x : u64 = 1;
|
||||||
|
t : Type = type_of(x);
|
||||||
|
at : Any = t;
|
||||||
|
print("name={}\n", type_name(at));
|
||||||
|
print("unsigned={}\n", type_is_unsigned(at));
|
||||||
|
print("value={}\n", at);
|
||||||
|
print("after\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#run ct_probe();
|
||||||
|
|
||||||
|
main :: () {}
|
||||||
1
examples/expected/0613-comptime-print-any-type.exit
Normal file
1
examples/expected/0613-comptime-print-any-type.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/0613-comptime-print-any-type.stderr
Normal file
1
examples/expected/0613-comptime-print-any-type.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
6
examples/expected/0613-comptime-print-any-type.stdout
Normal file
6
examples/expected/0613-comptime-print-any-type.stdout
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
before
|
||||||
|
name=u64
|
||||||
|
unsigned=true
|
||||||
|
value=u64
|
||||||
|
after
|
||||||
|
--- build done ---
|
||||||
79
issues/0096-comptime-print-any-type-stops.md
Normal file
79
issues/0096-comptime-print-any-type-stops.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# issue 0096 — `#run`/comptime print of an `Any` holding a `Type` silently stops
|
||||||
|
|
||||||
|
> **RESOLVED** (F0.12).
|
||||||
|
> **Root cause:** `any_to_string` runs `type := type_of(val)`; for an `.any`
|
||||||
|
> operand `type_of` lowers to `struct_get(val, 0)` (read the Any's tag field).
|
||||||
|
> At runtime a first-class `Type` value is the aggregate `{ tag=.any, value=tid }`,
|
||||||
|
> so the read succeeds. The comptime interpreter stores a `Type` as a bare
|
||||||
|
> `.type_tag(tid)` Value, and the comptime `struct_get` arm had no case for
|
||||||
|
> `.type_tag` — it fell through to `typeErrorDetail("…base has no fields…")` and
|
||||||
|
> raised `CannotEvalComptime`. That error was then swallowed silently:
|
||||||
|
> `runComptimeSideEffects` ran `interp.call(...) catch Value.void_val`, so the
|
||||||
|
> `#run` truncated mid-execution yet the build still exited 0.
|
||||||
|
> **Fix:** (1) `src/ir/interp.zig` — the comptime `struct_get` arm now handles a
|
||||||
|
> `.type_tag(tid)` base by mirroring the runtime Any-Type layout: field 0 →
|
||||||
|
> `.int(TypeId.any.index())` (the `.any` tag), field 1 → `.type_tag(tid)`. So
|
||||||
|
> `type_of` of an Any-held Type evaluates the same as runtime and execution
|
||||||
|
> continues. (2) `src/ir/emit_llvm.zig` — `runComptimeSideEffects` no longer
|
||||||
|
> swallows a side-effect bail into `void_val`; it prints a loud diagnostic and
|
||||||
|
> sets `comptime_failed` (→ `error.ComptimeError`, non-zero exit), matching the
|
||||||
|
> const-init path. A truncated `#run` can no longer ship a successful build.
|
||||||
|
> **Regression test:** `examples/0613-comptime-print-any-type.sx` (all five
|
||||||
|
> lines print, exit 0). Verified fail-before / pass-after.
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
During `#run`/comptime execution, `print("{}", at)` where `at : Any` holds a
|
||||||
|
`Type` value **silently halts** the comptime interpreter: the formatted value
|
||||||
|
and every following statement are omitted, yet **the build still succeeds
|
||||||
|
(exit 0)**. At runtime the same `Any`-held `Type` prints fine (`u64`). A
|
||||||
|
successful build with truncated `#run` execution is the dangerous part — a
|
||||||
|
silent stop, the exact class of failure the project's REJECTED-PATTERNS rule
|
||||||
|
forbids.
|
||||||
|
|
||||||
|
## Reproduction (only imports `modules/std.sx`)
|
||||||
|
|
||||||
|
```sx
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
ct_probe :: () {
|
||||||
|
print("before\n");
|
||||||
|
x : u64 = 1;
|
||||||
|
t : Type = type_of(x);
|
||||||
|
at : Any = t;
|
||||||
|
print("name={}\n", type_name(at));
|
||||||
|
print("unsigned={}\n", type_is_unsigned(at));
|
||||||
|
print("value={}\n", at);
|
||||||
|
print("after\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#run ct_probe();
|
||||||
|
|
||||||
|
main :: () {}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Observed pre-fix** (comptime stops after `unsigned=true`, build still exit 0):
|
||||||
|
|
||||||
|
```text
|
||||||
|
before
|
||||||
|
name=u64
|
||||||
|
unsigned=true
|
||||||
|
--- build done ---
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected / post-fix** (same as runtime, execution continues):
|
||||||
|
|
||||||
|
```text
|
||||||
|
before
|
||||||
|
name=u64
|
||||||
|
unsigned=true
|
||||||
|
value=u64
|
||||||
|
after
|
||||||
|
--- build done ---
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bisect (ground-truth)
|
||||||
|
|
||||||
|
Pre-existing: the minimal repro stops on `dist-foundation` too (and pre-F0.8),
|
||||||
|
so it is NOT introduced by F0.8's Any-tag fix and is orthogonal to issue 0090.
|
||||||
|
A standing comptime-interpreter limitation, scheduled and fixed as F0.12.
|
||||||
@@ -828,7 +828,23 @@ pub const LLVMEmitter = struct {
|
|||||||
interp_inst.build_config = &self.build_config;
|
interp_inst.build_config = &self.build_config;
|
||||||
if (self.import_sources) |sm| interp_inst.setSourceMap(sm);
|
if (self.import_sources) |sm| interp_inst.setSourceMap(sm);
|
||||||
sx_trace_clear();
|
sx_trace_clear();
|
||||||
const result = interp_inst.call(func_id, &.{}) catch Value.void_val;
|
Interpreter.last_bail_op = null;
|
||||||
|
Interpreter.last_bail_builtin = null;
|
||||||
|
Interpreter.last_bail_detail = null;
|
||||||
|
const result = interp_inst.call(func_id, &.{}) catch |err| blk: {
|
||||||
|
// A comptime `#run` side-effect that bails must NOT silently
|
||||||
|
// truncate its output and still ship a successful build.
|
||||||
|
// Surface the bail loudly and fail the build, mirroring the
|
||||||
|
// const-init path in emitGlobals. Whatever output the run
|
||||||
|
// produced before the bail is flushed below so the user sees
|
||||||
|
// where execution stopped.
|
||||||
|
const op = Interpreter.last_bail_op orelse "<unknown>";
|
||||||
|
const detail = Interpreter.last_bail_detail orelse "";
|
||||||
|
const sep: []const u8 = if (detail.len > 0) ": " else "";
|
||||||
|
std.debug.print("error: comptime `#run` ({s}) failed: {s} (op={s}{s}{s})\n", .{ fname, @errorName(err), op, sep, detail });
|
||||||
|
self.comptime_failed = true;
|
||||||
|
break :blk Value.void_val;
|
||||||
|
};
|
||||||
// Route #run `print` output to fd 1 so it joins the
|
// Route #run `print` output to fd 1 so it joins the
|
||||||
// JIT-executed runtime's stream. Same call site shape as
|
// JIT-executed runtime's stream. Same call site shape as
|
||||||
// `core.flushInterpOutput` — see issue-0047.
|
// `core.flushInterpOutput` — see issue-0047.
|
||||||
|
|||||||
@@ -916,6 +916,17 @@ pub const Interpreter = struct {
|
|||||||
if (fa.field_index == 0) return .{ .value = .{ .int = v } };
|
if (fa.field_index == 0) return .{ .value = .{ .int = v } };
|
||||||
return error.OutOfBounds;
|
return error.OutOfBounds;
|
||||||
},
|
},
|
||||||
|
.type_tag => |tid| {
|
||||||
|
// A first-class Type value is the comptime form of the
|
||||||
|
// runtime Any-Type aggregate `{ tag=.any, value=tid }`
|
||||||
|
// (see `const_type` lowering in buildPackSliceValue).
|
||||||
|
// `type_of(any_holding_a_Type)` lowers to struct_get
|
||||||
|
// field 0, expecting that runtime layout — mirror it so
|
||||||
|
// field 0 reads the `.any` tag and field 1 the type id.
|
||||||
|
if (fa.field_index == 0) return .{ .value = .{ .int = @intCast(TypeId.any.index()) } };
|
||||||
|
if (fa.field_index == 1) return .{ .value = .{ .type_tag = tid } };
|
||||||
|
return error.OutOfBounds;
|
||||||
|
},
|
||||||
else => return typeErrorDetail("comptime struct_get: base has no fields (not an aggregate/string/int)"),
|
else => return typeErrorDetail("comptime struct_get: base has no fields (not an aggregate/string/int)"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user