Issue 0095: a typed local/param/field silently TRUNCATED a float initializer to an integer annotation (`y : s64 = 1.5` → 1) with no diagnostic. Agra ruled the UNIFIED rule (Option B): an implicit float→int in a typed binding behaves like the array-dimension rule — - an INTEGRAL compile-time float FOLDS to its int (`4.0` → 4, `-2.0` → -2); - a NON-integral float is a COMPILE ERROR (`1.5`, `4.5`); - explicit `xx` / `cast(T)` ALWAYS truncates (the escape hatch). Applied consistently to typed local / param-default / field-default, typed module CONST, and array dim — all reusing the single `program_index.floatToIntExact` / `evalConstIntExpr` facility (no second integral check). - `Builder.constFloatInfo` reads a compile-time `const_float` back from its Ref (value + span). - `coerceToType` is now the IMPLICIT path: its `.float_to_int` arm folds an integral const-float to `constInt`, else emits the narrowing diagnostic. `coerceExplicit` is the raw truncating path; `xx` (lowerXX) and `cast(T)` route through it so the escape still truncates. - Field-default lowering (struct-literal pad, named-field default, buildDefaultValue) now coerces the default to the field type at the IR level (was silently bit-coerced by emitStructInit). - Const path: `typedConstInitFits` accepts an integral float (literal or a `M + 2.0`-style expression folding via `evalComptimeInt`); `emitModuleConst` / `constExprValue` / `globalInitValue` fold an integral float to its int and reject a non-integral one — relaxing F0.7's blanket float rejection. Tests: examples/0168 (positive: local/field/param/const fold, xx/cast truncate), examples/1146 (negative: local/param/field error), integral-float const cases added to examples/0162; non-integral const cases in 1143 stay errors. specs.md + readme.md document the unified rule, cross-referencing the array-dim rule. issues/0095 marked RESOLVED.
142 lines
4.8 KiB
Zig
142 lines
4.8 KiB
Zig
// Tests for module.zig
|
|
const std = @import("std");
|
|
const types = @import("types.zig");
|
|
const inst_mod = @import("inst.zig");
|
|
const mod_mod = @import("module.zig");
|
|
|
|
const TypeId = types.TypeId;
|
|
const Ref = inst_mod.Ref;
|
|
const BlockId = inst_mod.BlockId;
|
|
const FuncId = inst_mod.FuncId;
|
|
const Function = inst_mod.Function;
|
|
const GlobalId = inst_mod.GlobalId;
|
|
const Module = mod_mod.Module;
|
|
const Builder = mod_mod.Builder;
|
|
|
|
test "Builder: build add(a: s64, b: s64) -> s64" {
|
|
const alloc = std.testing.allocator;
|
|
var mod = Module.init(alloc);
|
|
defer mod.deinit();
|
|
|
|
var b = Builder.init(&mod);
|
|
|
|
const name_add = mod.types.internString("add");
|
|
const name_a = mod.types.internString("a");
|
|
const name_b = mod.types.internString("b");
|
|
const name_entry = mod.types.internString("entry");
|
|
|
|
const params = &[_]Function.Param{
|
|
.{ .name = name_a, .ty = .s64 },
|
|
.{ .name = name_b, .ty = .s64 },
|
|
};
|
|
const func_id = b.beginFunction(name_add, params, .s64);
|
|
|
|
const entry = b.appendBlock(name_entry, &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// Load params (in real lowering, params are block params of entry)
|
|
const a_ref = b.constInt(0, .s64); // placeholder for param a
|
|
const b_ref = b.constInt(0, .s64); // placeholder for param b
|
|
const sum = b.add(a_ref, b_ref, .s64);
|
|
b.ret(sum, .s64);
|
|
|
|
b.finalize();
|
|
|
|
// Verify
|
|
const func = mod.getFunction(func_id);
|
|
try std.testing.expectEqual(@as(usize, 2), func.params.len);
|
|
try std.testing.expectEqual(TypeId.s64, func.ret);
|
|
try std.testing.expectEqual(@as(usize, 1), func.blocks.items.len);
|
|
|
|
const blk = &func.blocks.items[0];
|
|
try std.testing.expectEqual(@as(usize, 4), blk.insts.items.len); // 2 consts + add + ret
|
|
}
|
|
|
|
test "Builder: conditional branch" {
|
|
const alloc = std.testing.allocator;
|
|
var mod = Module.init(alloc);
|
|
defer mod.deinit();
|
|
|
|
var b = Builder.init(&mod);
|
|
|
|
const name_fn = mod.types.internString("test_fn");
|
|
const name_entry = mod.types.internString("entry");
|
|
const name_then = mod.types.internString("then");
|
|
const name_else = mod.types.internString("else");
|
|
const name_merge = mod.types.internString("merge");
|
|
|
|
_ = b.beginFunction(name_fn, &.{}, .s32);
|
|
|
|
const entry = b.appendBlock(name_entry, &.{});
|
|
const then_bb = b.appendBlock(name_then, &.{});
|
|
const else_bb = b.appendBlock(name_else, &.{});
|
|
const merge_bb = b.appendBlock(name_merge, &[_]TypeId{.s32});
|
|
|
|
b.switchToBlock(entry);
|
|
const cond = b.constBool(true);
|
|
b.condBr(cond, then_bb, &.{}, else_bb, &.{});
|
|
|
|
b.switchToBlock(then_bb);
|
|
const v1 = b.constInt(42, .s32);
|
|
b.br(merge_bb, &.{v1});
|
|
|
|
b.switchToBlock(else_bb);
|
|
const v2 = b.constInt(0, .s32);
|
|
b.br(merge_bb, &.{v2});
|
|
|
|
b.switchToBlock(merge_bb);
|
|
const result = b.emit(.{ .block_param = .{ .block = merge_bb, .param_index = 0 } }, .s32);
|
|
b.ret(result, .s32);
|
|
|
|
b.finalize();
|
|
|
|
// Verify: 4 blocks, correct instruction counts
|
|
const func = mod.getFunction(@enumFromInt(0));
|
|
try std.testing.expectEqual(@as(usize, 4), func.blocks.items.len);
|
|
try std.testing.expectEqual(@as(usize, 2), func.blocks.items[0].insts.items.len); // const_bool + cond_br
|
|
try std.testing.expectEqual(@as(usize, 2), func.blocks.items[1].insts.items.len); // const_int + br
|
|
try std.testing.expectEqual(@as(usize, 2), func.blocks.items[2].insts.items.len); // const_int + br
|
|
try std.testing.expectEqual(@as(usize, 2), func.blocks.items[3].insts.items.len); // block_param + ret
|
|
}
|
|
|
|
test "Module: globals" {
|
|
const alloc = std.testing.allocator;
|
|
var mod = Module.init(alloc);
|
|
defer mod.deinit();
|
|
|
|
const name = mod.types.internString("counter");
|
|
const id = mod.addGlobal(.{
|
|
.name = name,
|
|
.ty = .s32,
|
|
.init_val = .{ .int = 0 },
|
|
});
|
|
|
|
try std.testing.expectEqual(GlobalId.fromIndex(0), id);
|
|
try std.testing.expectEqual(TypeId.s32, mod.globals.items[0].ty);
|
|
}
|
|
|
|
test "Builder.constFloatInfo reads a const_float back, null for non-floats" {
|
|
const alloc = std.testing.allocator;
|
|
var mod = Module.init(alloc);
|
|
defer mod.deinit();
|
|
|
|
var b = Builder.init(&mod);
|
|
const name = mod.types.internString("f");
|
|
const entry_name = mod.types.internString("entry");
|
|
_ = b.beginFunction(name, &.{}, .void);
|
|
const entry = b.appendBlock(entry_name, &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// A const_float reads back its value (the implicit float→int rule consults
|
|
// this to fold an integral literal / locate a non-integral one).
|
|
const fref = b.constFloat(4.0, .f64);
|
|
const info = b.constFloatInfo(fref) orelse return error.TestUnexpectedResult;
|
|
try std.testing.expectEqual(@as(f64, 4.0), info.value);
|
|
|
|
// A non-float instruction is not a const_float — null.
|
|
const iref = b.constInt(7, .s64);
|
|
try std.testing.expect(b.constFloatInfo(iref) == null);
|
|
|
|
b.finalize();
|
|
}
|