fix(ir): complete const-float evaluator — resolve float-const leaves too [F0.11]
Completes issue 0095: a non-integral float→int narrowing via a FLOAT-const leaf (`F : f64 : 2.5; y : s64 = F + 0.25` = 2.75) silently truncated to 2. `evalConstFloatExpr` delegated only INTEGER leaves to `evalConstIntExpr` and had no float-const leaf arm, so the unified rule never saw the value. - program_index.zig: add `moduleConstFloat`/`moduleConstFloatFramed` — the f64 twin of `moduleConstInt` (same `isCountableConstType` gate, same cyclic- definition frame), recovering a numeric module const's value via `evalConstFloatExpr`. Add `lookupFloatName` to `ModuleConstCtx` and the `.identifier`/`.type_expr` leaf arms to `evalConstFloatExpr` that call it. Integer / integral-float leaves keep resolving through the existing `evalConstIntExpr` delegation, so the unified rule now applies to ANY compile-time-constant float expression — literal, int-const leaf, float-const leaf, and combinations — at every binding site. - lower.zig: add `Lowering.lookupFloatName` delegating to `moduleConstFloat`. Route `typedConstInitFits`' integral-fold check through `evalConstFloatExpr` + `floatToIntExact` (the SAME facility `foldComptimeFloatInit` uses) instead of the int-only `evalComptimeInt`, which folded leaf-by-leaf in i64 and so rejected an integral SUM built from a non-integral float leaf (`K : s64 : F + 1.5` = 4.0 now folds; `K : s64 : F + 0.25` errors). A LOCAL `::` const leaf is a scope ref (not in the const tables) so neither the int nor float evaluator folds it — float now matches int exactly there. Regression: examples/1146 (negative) + 0168 (positive) extended with float-const-leaf cases at local/field/param/const; unit test in program_index.test.zig covers the leaf resolution (F→2.5, F+0.25→2.75, F+1.5→4.0). specs.md + readme.md state the rule covers any compile-time-const float expression incl. float-typed const leaves. issues/0095 banner updated. Gate: zig build + zig build test green; 447 examples pass, 0 failed.
This commit is contained in:
@@ -99,6 +99,13 @@ const ModuleConstCtx = struct {
|
||||
pub fn lookupPackLen(_: ModuleConstCtx, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
/// Float counterpart of `lookupDimName`, so `evalConstFloatExpr` resolves a
|
||||
/// float-const leaf whose value references another const
|
||||
/// (`G : f64 : 2.0; F : f64 : G + 0.5`) recursively through the SAME
|
||||
/// cycle-guarded frame.
|
||||
pub fn lookupFloatName(self: ModuleConstCtx, name: []const u8) ?f64 {
|
||||
return moduleConstFloatFramed(self.consts, self.table, name, self.frame);
|
||||
}
|
||||
};
|
||||
|
||||
/// A module const may serve as an integer COUNT only when its DECLARED type is
|
||||
@@ -144,6 +151,28 @@ pub fn moduleConstInt(consts: *const std.StringHashMap(ModuleConstInfo), table:
|
||||
return moduleConstIntFramed(consts, table, name, null);
|
||||
}
|
||||
|
||||
/// FLOAT counterpart of `moduleConstInt`: a name bound to a NUMERIC module const
|
||||
/// → its compile-time `f64` value (`F : f64 : 2.5` → 2.5), else null. Mirrors
|
||||
/// `moduleConstIntFramed` exactly — same `isCountableConstType` gate, same cyclic-
|
||||
/// definition frame — but recovers the value through `evalConstFloatExpr`, so the
|
||||
/// unified float→int narrowing rule resolves a NON-INTEGRAL float-const leaf
|
||||
/// (`y : s64 = F + 0.25`) the same way the int folder resolves an int-const leaf
|
||||
/// (`M :: 2; y : s64 = M + 0.5`). An integral float / integer const folds through
|
||||
/// the int path inside `evalConstFloatExpr` and never reaches the leaf arm that
|
||||
/// calls this; this surfaces the genuinely non-integral float so `floatToIntExact`
|
||||
/// can reject it.
|
||||
fn moduleConstFloatFramed(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, parent: ?*const ModuleConstFrame) ?f64 {
|
||||
if (moduleConstFrameContains(parent, name)) return null;
|
||||
const ci = consts.get(name) orelse return null;
|
||||
if (!isCountableConstType(table, ci.ty)) return null;
|
||||
var frame = ModuleConstFrame{ .name = name, .parent = parent };
|
||||
return evalConstFloatExpr(ci.value, ModuleConstCtx{ .consts = consts, .table = table, .frame = &frame });
|
||||
}
|
||||
|
||||
pub fn moduleConstFloat(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8) ?f64 {
|
||||
return moduleConstFloatFramed(consts, table, name, null);
|
||||
}
|
||||
|
||||
/// Evaluate a constant integer expression to its value. THE single
|
||||
/// integer-expression folder for the compiler — array dimensions (`[N]T`,
|
||||
/// `[M + 1]T`), Vector lane counts (`Vector(N, f32)`), generic value-param
|
||||
@@ -228,9 +257,18 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
|
||||
/// An all-integer-foldable subtree is delegated to `evalConstIntExpr` (so module
|
||||
/// / comptime consts, `<IntType>.min`/`.max`, and integer arithmetic resolve
|
||||
/// through the SINGLE int folder — no parallel integer logic here); only the
|
||||
/// genuinely float-producing shapes — a float literal, a unary negate, and
|
||||
/// `+ - * /` arithmetic involving a float — are evaluated here in `f64`. A `%`,
|
||||
/// comparison, or any other shape is not a compile-time float leaf → null.
|
||||
/// genuinely float-producing shapes — a float literal, a NON-INTEGRAL float-const
|
||||
/// leaf, a unary negate, and `+ - * /` arithmetic involving a float — are
|
||||
/// evaluated here in `f64`. A `%`, comparison, or any other shape is not a
|
||||
/// compile-time float leaf → null.
|
||||
///
|
||||
/// A NAMED-const leaf resolves through `ctx.lookupFloatName`, the float twin of
|
||||
/// the `lookupDimName` the int folder uses: a numeric module const whose value is
|
||||
/// a non-integral float (`F : f64 : 2.5`) surfaces here so `F + 0.25` (= 2.75) is
|
||||
/// recognised as a compile-time float and rejected by the narrowing rule, exactly
|
||||
/// as `M + 0.5` (with `M :: 2`) already is. An INTEGRAL float / integer const
|
||||
/// (`K : f64 : 4.0`, `M :: 2`) is resolved by the `evalConstIntExpr` delegation
|
||||
/// above and never reaches the leaf arm.
|
||||
pub fn evalConstFloatExpr(node: *const Node, ctx: anytype) ?f64 {
|
||||
// Delegate any integer-foldable subtree (incl. an INTEGRAL float like `4.0`
|
||||
// / `M + 2.0`) to the single int folder, then promote — keeps named consts
|
||||
@@ -238,6 +276,10 @@ pub fn evalConstFloatExpr(node: *const Node, ctx: anytype) ?f64 {
|
||||
if (evalConstIntExpr(node, ctx)) |iv| return @floatFromInt(iv);
|
||||
return switch (node.data) {
|
||||
.float_literal => |lit| lit.value,
|
||||
// A name bound to a numeric module const whose value is a non-integral
|
||||
// float (the integral / integer cases were caught by the int delegation).
|
||||
.identifier => |id| ctx.lookupFloatName(id.name),
|
||||
.type_expr => |te| ctx.lookupFloatName(te.name),
|
||||
.unary_op => |u| switch (u.op) {
|
||||
.negate => {
|
||||
const v = evalConstFloatExpr(u.operand, ctx) orelse return null;
|
||||
|
||||
Reference in New Issue
Block a user