feat(lang): integer numeric-limit accessors (s64.max, u8.min, s3.max) [NL.1]

A field-like access on a builtin INTEGER type name folds to a compile-time
constant of the queried type, driven by (width, signedness) arithmetic:
  sN: min=-(2^(N-1)), max=2^(N-1)-1;  uN: min=0, max=2^N-1
for every width s1..s64 / u1..u64 (not just power-of-two), plus usize/isize.

- type_resolver.zig: extract the single width parser (parseWidthInt) reused by
  resolveNamed AND the new accessors (no second parser — issue-0083 class);
  add resolveBuiltinName / integerWidthSign / integerLimitBits / integerLimitFor.
- lower.zig: lowerNumericLimit intercept beside the error.X / Struct.CONST /
  pack-arity identifier-receiver intercepts; folds ints via constInt, emits a
  clean diagnostic for a non-numeric receiver (bool/string/void/Any/noreturn),
  falls through for floats (NL.2).
- expr_typer.zig: mirror the result type so inferExprType reports the queried type.
- program_index.zig: recognize the accessors in the comptime-int / array-dim path
  so [u8.max]T (255) / [s16.max]T (32767) work; [u64.max]T is rejected oversized.
- u64.max / usize.max stored as the all-ones bit pattern with TYPE u64 (i64 -1),
  asserted via union { u: u64; s: s64 } reinterpret.

Docs: specs.md numeric-limits subsection (formulas + result-type + u64 note);
readme.md language overview. Examples 0148 (positive) / 0149 (negative-receiver).
Unit tests for the value computation in type_resolver.test.zig.

Gate: zig build, zig build test (359/359), tests/run_examples.sh (416 ok, 0 failed).
This commit is contained in:
agra
2026-06-04 16:14:06 +03:00
parent bc9777d81f
commit 04f46ef384
15 changed files with 384 additions and 14 deletions

View File

@@ -3,6 +3,7 @@ const ast = @import("../ast.zig");
const types = @import("types.zig");
const inst = @import("inst.zig");
const errors = @import("../errors.zig");
const type_resolver = @import("type_resolver.zig");
const Node = ast.Node;
const TypeId = types.TypeId;
@@ -156,12 +157,22 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
.identifier => |id| ctx.lookupDimName(id.name),
.type_expr => |te| ctx.lookupDimName(te.name),
.field_access => |fa| blk: {
// `<pack>.len` resolves to the monomorphised arity (e.g. an
// `inline for 0..xs.len` bound). Any other field access is not a
// compile-time integer leaf.
if (fa.object.data == .identifier and std.mem.eql(u8, fa.field, "len")) {
break :blk ctx.lookupPackLen(fa.object.data.identifier.name);
const obj_name: ?[]const u8 = switch (fa.object.data) {
.identifier => |id| id.name,
.type_expr => |te| te.name,
else => null,
};
if (obj_name) |on| {
// `<pack>.len` resolves to the monomorphised arity (e.g. an
// `inline for 0..xs.len` bound).
if (std.mem.eql(u8, fa.field, "len")) break :blk ctx.lookupPackLen(on);
// `<IntType>.min` / `.max` — the same fold the value path uses
// (type_resolver), so `[u8.max]T` agrees with `u8.max` in
// expression position. A `u64.max` (= -1 as i64) folds here too;
// `foldDimU32` then rejects it as a negative array dimension.
if (type_resolver.TypeResolver.integerLimitFor(on, fa.field)) |v| break :blk v;
}
// Any other field access is not a compile-time integer leaf.
break :blk null;
},
.unary_op => |u| switch (u.op) {