WIP: float numeric-limit accessors (NL.2) — blocked on 0091 (nan != nan); examples/docs pending
This commit is contained in:
@@ -126,10 +126,13 @@ pub const ExprTyper = struct {
|
||||
if (info.ty) |t| return t;
|
||||
}
|
||||
}
|
||||
// Numeric-limit accessor: `<IntType>.min` / `.max` is a comptime
|
||||
// const of the queried integer type — mirrors the lowerFieldAccess
|
||||
// intercept so inference reports the same type (without it the
|
||||
// const would be mistyped, e.g. boxed into an Any slot).
|
||||
// Numeric-limit accessor: `<Type>.min`/`.max` (int or float) or a
|
||||
// float-only `.epsilon`/`.min_positive`/`.true_min`/`.inf`/`.nan`
|
||||
// is a comptime const of the queried type — mirrors the
|
||||
// lowerFieldAccess intercept so inference reports the same type
|
||||
// (without it the const would be mistyped, e.g. boxed into an Any
|
||||
// slot). Only valid folds carry a type here; the cross-type error
|
||||
// cases fall through (lowerNumericLimit emits the diagnostic).
|
||||
{
|
||||
const type_name: ?[]const u8 = switch (fa.object.data) {
|
||||
.identifier => |id| id.name,
|
||||
@@ -137,7 +140,9 @@ pub const ExprTyper = struct {
|
||||
else => null,
|
||||
};
|
||||
if (type_name) |tn| {
|
||||
if (TypeResolver.integerLimitFor(tn, fa.field) != null) {
|
||||
if (TypeResolver.integerLimitFor(tn, fa.field) != null or
|
||||
TypeResolver.floatLimitFor(tn, fa.field) != null)
|
||||
{
|
||||
if (TypeResolver.resolveBuiltinName(tn, &self.l.module.types)) |t| return t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4904,33 +4904,45 @@ pub const Lowering = struct {
|
||||
return self.lowerFieldAccessOnType(obj, obj_ty, fa.field, span);
|
||||
}
|
||||
|
||||
/// Numeric-limit accessor intercept (`<IntType>.min` / `.max`), a sibling of
|
||||
/// the `error.X` / `Struct.CONST` / pack-arity identifier-receiver intercepts
|
||||
/// in `lowerFieldAccess`. Folds an integer type's `.min`/`.max` to a comptime
|
||||
/// const of that type via the shared `TypeResolver` width logic (no second
|
||||
/// width parser) + the existing `constInt` const path. Returns null when this
|
||||
/// is not an integer-limit access, so the caller continues normal field
|
||||
/// lowering. A `.min`/`.max` on a builtin NON-numeric receiver
|
||||
/// (`bool`/`string`/`void`/`Any`/`noreturn`) is a clean diagnostic here (then
|
||||
/// a placeholder, so lowering finishes and `hasErrors()` aborts the build); a
|
||||
/// float receiver falls through (float limits are NL.2).
|
||||
/// Numeric-limit accessor intercept (`<Type>.min`/`.max`/`.epsilon`/
|
||||
/// `.min_positive`/`.true_min`/`.inf`/`.nan`), a sibling of the `error.X` /
|
||||
/// `Struct.CONST` / pack-arity identifier-receiver intercepts in
|
||||
/// `lowerFieldAccess`. Folds the limit to a comptime const of the queried
|
||||
/// type via the shared `TypeResolver` logic (no second computor) + the
|
||||
/// existing `constInt` / `constFloat` const paths:
|
||||
/// - integer `.min`/`.max` → `constInt` (NL.1, via `integerLimitFor`);
|
||||
/// - float `.min`/`.max`/`.epsilon`/`.min_positive`/`.true_min`/`.inf`/
|
||||
/// `.nan` → `constFloat` (via `floatLimitFor`).
|
||||
/// Returns null when the field is not a limit accessor, or the receiver is not
|
||||
/// a builtin type (a user struct → ordinary field lowering reports
|
||||
/// field-not-found). Two clean diagnostics (then a placeholder, so lowering
|
||||
/// finishes and `hasErrors()` aborts the build):
|
||||
/// - a FLOAT-only accessor on an integer type (`s32.epsilon`, `u8.inf`);
|
||||
/// - any accessor on a builtin NON-numeric receiver
|
||||
/// (`bool`/`string`/`void`/`Any`/`noreturn`).
|
||||
fn lowerNumericLimit(self: *Lowering, fa: *const ast.FieldAccess, span: ast.Span) ?Ref {
|
||||
const name = switch (fa.object.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
else => return null,
|
||||
};
|
||||
if (!std.mem.eql(u8, fa.field, "min") and !std.mem.eql(u8, fa.field, "max")) return null;
|
||||
if (!TypeResolver.isLimitField(fa.field)) return null;
|
||||
const ty = TypeResolver.resolveBuiltinName(name, &self.module.types) orelse return null;
|
||||
if (TypeResolver.integerLimitFor(name, fa.field)) |value| {
|
||||
return self.builder.constInt(value, ty);
|
||||
}
|
||||
// A builtin receiver that is not an integer: floats are NL.2 (fall
|
||||
// through), every other builtin (bool/string/void/Any/noreturn) has no
|
||||
// numeric limit.
|
||||
if (ty == .f32 or ty == .f64) return null;
|
||||
if (TypeResolver.floatLimitFor(name, fa.field)) |value| {
|
||||
return self.builder.constFloat(value, ty);
|
||||
}
|
||||
// The field is a limit accessor, but it does not apply to this type.
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, span, "type '{s}' has no '.{s}' — numeric limits apply only to integer and float types", .{ name, fa.field });
|
||||
if (TypeResolver.integerWidthSign(name) != null) {
|
||||
// Integer receiver + a float-only accessor.
|
||||
d.addFmt(.err, span, "type '{s}' has no '.{s}' — '.{s}' applies only to float types (f32/f64); integer types expose only '.min'/'.max'", .{ name, fa.field, fa.field });
|
||||
} else {
|
||||
// Non-numeric builtin receiver (bool/string/void/Any/noreturn).
|
||||
d.addFmt(.err, span, "type '{s}' has no '.{s}' — numeric limits apply only to integer and float types", .{ name, fa.field });
|
||||
}
|
||||
}
|
||||
return self.emitPlaceholder(fa.field);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,49 @@ pub const TypeResolver = struct {
|
||||
return integerLimitBits(wi, want_max);
|
||||
}
|
||||
|
||||
/// The full numeric-limit accessor field set: `.min`/`.max` (valid on int AND
|
||||
/// float) plus the float-only `.epsilon`/`.min_positive`/`.true_min`/`.inf`/
|
||||
/// `.nan`. THE single trigger for the `lowerNumericLimit` intercept — only a
|
||||
/// field in this set is treated as a limit access; anything else falls through
|
||||
/// to ordinary field lowering. Keeps the accessor name set in one place so the
|
||||
/// intercept and `expr_typer` can't recognize different surfaces.
|
||||
pub fn isLimitField(field: []const u8) bool {
|
||||
const names = [_][]const u8{ "min", "max", "epsilon", "min_positive", "true_min", "inf", "nan" };
|
||||
for (names) |n| if (std.mem.eql(u8, field, n)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// `<FloatType>.<field>` → the limit as an `f64` value (the queried type is
|
||||
/// `f32`/`f64`; every f32 limit is exactly representable in f64, so widening
|
||||
/// is lossless and the caller pairs the value with the queried `TypeId` —
|
||||
/// `builder.constFloat` narrows it back at emit), or null when `name` is not a
|
||||
/// builtin float type or `field` is not a limit accessor. Values come straight
|
||||
/// from `std.math` (`floatMax`/`floatEps`/`floatMin`/`floatTrueMin`/`inf`/`nan`):
|
||||
/// - `.min` = most-NEGATIVE finite (`-max`, NOT C's DBL_MIN)
|
||||
/// - `.max` = largest finite
|
||||
/// - `.epsilon` = ULP of 1.0 (`floatEps`; f64 = 2^-52, f32 = 2^-23)
|
||||
/// - `.min_positive` = smallest positive NORMAL (`floatMin`; = C DBL_MIN)
|
||||
/// - `.true_min` = smallest positive SUBNORMAL (`floatTrueMin`)
|
||||
/// - `.inf` = +infinity, `.nan` = a quiet NaN
|
||||
/// THE single name+field → float fold, shared by the value path (lower.zig)
|
||||
/// and `expr_typer` so they can't disagree.
|
||||
pub fn floatLimitFor(name: []const u8, field: []const u8) ?f64 {
|
||||
if (std.mem.eql(u8, name, "f64")) return floatLimitValue(f64, field);
|
||||
if (std.mem.eql(u8, name, "f32")) return floatLimitValue(f32, field);
|
||||
return null;
|
||||
}
|
||||
|
||||
fn floatLimitValue(comptime T: type, field: []const u8) ?f64 {
|
||||
if (std.mem.eql(u8, field, "min")) return -@as(f64, std.math.floatMax(T));
|
||||
if (std.mem.eql(u8, field, "max")) return @as(f64, std.math.floatMax(T));
|
||||
if (std.mem.eql(u8, field, "epsilon")) return @as(f64, std.math.floatEps(T));
|
||||
if (std.mem.eql(u8, field, "min_positive")) return @as(f64, std.math.floatMin(T));
|
||||
if (std.mem.eql(u8, field, "true_min")) return @as(f64, std.math.floatTrueMin(T));
|
||||
if (std.mem.eql(u8, field, "inf")) return @as(f64, std.math.inf(T));
|
||||
if (std.mem.eql(u8, field, "nan")) return @as(f64, std.math.nan(T));
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Single owner of structural AST-type-shape construction. Builds the
|
||||
/// shapes whose `TypeId` is fully determined by their node kind plus their
|
||||
/// element types resolved through `inner.resolveInner`: `*T`, `[*]T`, `[]T`,
|
||||
|
||||
Reference in New Issue
Block a user