lang: const-aggregate comptime folds (PLAN-CONST-AGG step 3)
An array const's '.len' and 'K[<const idx>]' element reads, and a
struct const's field ('LIT.r'), fold as compile-time integer leaves —
usable in array dimensions and other constants' initializers. All
source-aware (the SELECTED author's elements, folded in the author's
context with the cyclic-definition frame); a const out-of-range index
diagnoses at fold time, never wraps.
- evalConstIntExpr gains the three ctx hooks (lookupConstAggLen /
lookupConstArrayElem / lookupConstStructField) + an index_expr arm;
all five ctx implementations extended (stateless tiers fold null).
- Array consts dual-register in module_const_map (value = the literal
node) so the folders see elements; bare reads still hit the GLOBAL
arm first, so no double emission.
- Untyped consts whose RHS is a const-aggregate leaf ('L :: K.len',
'E :: K[1]', 'R :: LIT.r') register in a pass 2b AFTER aggregates,
gated on the receiver naming a const aggregate — a namespaced member
('F :: m.PI_ISH') is never mis-typed by the count placeholder.
Examples: 0179 (folds in dims + const exprs), 1163 (OOB diagnostic).
This commit is contained in:
23
examples/0179-types-const-aggregate-folds.sx
Normal file
23
examples/0179-types-const-aggregate-folds.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
// Const aggregates fold at compile time: an array const's `.len` and
|
||||
// `K[<const idx>]` element reads, and a struct const's field (`LIT.r`),
|
||||
// are compile-time integer leaves — usable in array dimensions and in
|
||||
// other constants' initializer expressions, source-aware like every
|
||||
// const fold.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: struct { r, g, b: s64; }
|
||||
K : [4]s64 : .[11, 22, 33, 44];
|
||||
LIT :: Color.{ r = 5, g = 0, b = 0 };
|
||||
|
||||
N :: K[0] + K[3];
|
||||
L :: K.len;
|
||||
R :: LIT.r;
|
||||
|
||||
main :: () {
|
||||
a : [K.len]u8 = ---;
|
||||
b : [K[1]]u8 = ---;
|
||||
c : [LIT.r]u8 = ---;
|
||||
print("N={} L={} R={}\n", N, L, R);
|
||||
print("dims={} {} {}\n", a.len, b.len, c.len);
|
||||
}
|
||||
12
examples/1163-diagnostics-array-const-index-oob.sx
Normal file
12
examples/1163-diagnostics-array-const-index-oob.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// A compile-time index into an array constant is bounds-checked at fold
|
||||
// time — out of range is a diagnostic, never a wrap or a silent
|
||||
// runtime read.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
K : [4]s64 : .[11, 22, 33, 44];
|
||||
|
||||
main :: () {
|
||||
b : [K[9]]u8 = ---;
|
||||
print("{}\n", b.len);
|
||||
}
|
||||
1
examples/expected/0179-types-const-aggregate-folds.exit
Normal file
1
examples/expected/0179-types-const-aggregate-folds.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
N=55 L=4 R=5
|
||||
dims=4 22 5
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,11 @@
|
||||
error: index 9 is out of bounds for constant 'K' (4 elements)
|
||||
--> examples/1163-diagnostics-array-const-index-oob.sx:10:10
|
||||
|
|
||||
10 | b : [K[9]]u8 = ---;
|
||||
| ^^^^
|
||||
|
||||
error: array dimension must be a compile-time integer constant
|
||||
--> examples/1163-diagnostics-array-const-index-oob.sx:10:10
|
||||
|
|
||||
10 | b : [K[9]]u8 = ---;
|
||||
| ^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -111,6 +111,15 @@ pub const SourceConstCtx = struct {
|
||||
pub fn nameIsFloatTyped(self: SourceConstCtx, name: []const u8) bool {
|
||||
return self.lowering.sourceConstIsFloatTyped(name, self.frame);
|
||||
}
|
||||
pub fn lookupConstAggLen(self: SourceConstCtx, name: []const u8) ?i64 {
|
||||
return self.lowering.foldConstAggLen(name);
|
||||
}
|
||||
pub fn lookupConstArrayElem(self: SourceConstCtx, name: []const u8, idx: i64, span: ?ast.Span) ?i64 {
|
||||
return self.lowering.foldConstArrayElem(name, idx, span, self.frame);
|
||||
}
|
||||
pub fn lookupConstStructField(self: SourceConstCtx, name: []const u8, field: []const u8) ?i64 {
|
||||
return self.lowering.foldConstStructField(name, field, self.frame);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Scope ───────────────────────────────────────────────────────────────
|
||||
@@ -773,6 +782,16 @@ pub const Lowering = struct {
|
||||
return self.sourceConstIsFloatTyped(name, null);
|
||||
}
|
||||
|
||||
pub fn lookupConstAggLen(self: *Lowering, name: []const u8) ?i64 {
|
||||
return self.foldConstAggLen(name);
|
||||
}
|
||||
pub fn lookupConstArrayElem(self: *Lowering, name: []const u8, idx: i64, span: ?ast.Span) ?i64 {
|
||||
return self.foldConstArrayElem(name, idx, span, null);
|
||||
}
|
||||
pub fn lookupConstStructField(self: *Lowering, name: []const u8, field: []const u8) ?i64 {
|
||||
return self.foldConstStructField(name, field, null);
|
||||
}
|
||||
|
||||
/// Resolve a type node, checking type_bindings first for generic type params.
|
||||
pub fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
|
||||
// Pack-index in a type position: `$<pack>[<lit>]` resolves to the
|
||||
@@ -1524,6 +1543,9 @@ pub const Lowering = struct {
|
||||
pub const selectModuleConst = lower_comptime.selectModuleConst;
|
||||
pub const GlobalAuthor = lower_comptime.GlobalAuthor;
|
||||
pub const selectGlobalAuthor = lower_comptime.selectGlobalAuthor;
|
||||
pub const foldConstAggLen = lower_comptime.foldConstAggLen;
|
||||
pub const foldConstArrayElem = lower_comptime.foldConstArrayElem;
|
||||
pub const foldConstStructField = lower_comptime.foldConstStructField;
|
||||
pub const resolveGlobalRef = lower_comptime.resolveGlobalRef;
|
||||
pub const sourceModuleConst = lower_comptime.sourceModuleConst;
|
||||
pub const pinConstAuthorSource = lower_comptime.pinConstAuthorSource;
|
||||
|
||||
@@ -898,6 +898,80 @@ pub fn selectModuleConst(self: *Lowering, name: []const u8) ConstAuthor {
|
||||
return .none;
|
||||
}
|
||||
|
||||
/// `<array const>.len` as a compile-time integer — the SELECTED author's
|
||||
/// element count (E2/F2 source-aware, like every const fold).
|
||||
pub fn foldConstAggLen(self: *Lowering, name: []const u8) ?i64 {
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |sel| if (sel.info.value.data == .array_literal)
|
||||
@intCast(sel.info.value.data.array_literal.elements.len)
|
||||
else
|
||||
null,
|
||||
.own_opaque, .ambiguous, .none => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// `K[<const idx>]` over an ARRAY const: the element's compile-time integer
|
||||
/// value, folded in the AUTHOR's context. Out-of-range diagnoses loudly —
|
||||
/// never a wrap or a silent null-into-runtime.
|
||||
pub fn foldConstArrayElem(self: *Lowering, name: []const u8, idx: i64, span: ?ast.Span, frame: ?*const ConstFoldFrame) ?i64 {
|
||||
switch (self.selectModuleConst(name)) {
|
||||
.resolved => |sel| {
|
||||
if (sel.info.value.data != .array_literal) return null;
|
||||
const elems = sel.info.value.data.array_literal.elements;
|
||||
if (idx < 0 or idx >= elems.len) {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "index {d} is out of bounds for constant '{s}' ({d} elements)", .{ idx, name, elems.len });
|
||||
return null;
|
||||
}
|
||||
if (constFoldFrameContains(frame, name, sel.source)) return null;
|
||||
var f = ConstFoldFrame{ .name = name, .source = sel.source, .parent = frame };
|
||||
const restore = self.pinConstAuthorSource(sel.source);
|
||||
defer restore.unpin();
|
||||
return program_index_mod.evalConstIntExpr(elems[@intCast(idx)], SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.own_opaque, .ambiguous, .none => return null,
|
||||
}
|
||||
}
|
||||
|
||||
/// `<struct const>.field` as a compile-time integer — the SELECTED author's
|
||||
/// field initializer, matched by name (named inits) or position, folded in
|
||||
/// the author's context.
|
||||
pub fn foldConstStructField(self: *Lowering, name: []const u8, field: []const u8, frame: ?*const ConstFoldFrame) ?i64 {
|
||||
switch (self.selectModuleConst(name)) {
|
||||
.resolved => |sel| {
|
||||
if (sel.info.value.data != .struct_literal) return null;
|
||||
const sl = &sel.info.value.data.struct_literal;
|
||||
const init_expr: ?*const Node = blk: {
|
||||
const has_names = sl.field_inits.len > 0 and sl.field_inits[0].name != null;
|
||||
if (has_names) {
|
||||
for (sl.field_inits) |fi| {
|
||||
if (fi.name) |n| if (std.mem.eql(u8, n, field)) break :blk fi.value;
|
||||
}
|
||||
break :blk null;
|
||||
}
|
||||
// Positional inits: index via the struct type's field order.
|
||||
if (sel.info.ty.isBuiltin()) break :blk null;
|
||||
const ti = self.module.types.get(sel.info.ty);
|
||||
if (ti != .@"struct") break :blk null;
|
||||
for (ti.@"struct".fields, 0..) |sf, i| {
|
||||
if (std.mem.eql(u8, self.module.types.getString(sf.name), field)) {
|
||||
if (i < sl.field_inits.len) break :blk sl.field_inits[i].value;
|
||||
break :blk null;
|
||||
}
|
||||
}
|
||||
break :blk null;
|
||||
};
|
||||
const e = init_expr orelse return null;
|
||||
if (constFoldFrameContains(frame, name, sel.source)) return null;
|
||||
var f = ConstFoldFrame{ .name = name, .source = sel.source, .parent = frame };
|
||||
const restore = self.pinConstAuthorSource(sel.source);
|
||||
defer restore.unpin();
|
||||
return program_index_mod.evalConstIntExpr(e, SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.own_opaque, .ambiguous, .none => return null,
|
||||
}
|
||||
}
|
||||
|
||||
/// `source`'s per-source const cache entry for `name` (E0's
|
||||
/// `module_consts_by_source` write side), or null.
|
||||
pub fn sourceModuleConst(self: *Lowering, source: []const u8, name: []const u8) ?ModuleConstInfo {
|
||||
|
||||
@@ -785,6 +785,30 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
// Pass 2b: untyped consts whose RHS is a CONST-AGGREGATE leaf
|
||||
// (`L :: K.len`, `E :: K[1]`, `R :: LIT.r`) register with the count
|
||||
// placeholder so the folders reach them. Runs AFTER pass 2 so the
|
||||
// aggregate (array const / struct const) is registered regardless of
|
||||
// declaration order; gated on the receiver naming a const aggregate so
|
||||
// a namespaced member (`F :: m.PI_ISH`) is never mis-typed.
|
||||
for (decls) |decl| {
|
||||
if (decl.data != .const_decl) continue;
|
||||
const cd = decl.data.const_decl;
|
||||
if (cd.type_annotation != null) continue;
|
||||
self.setCurrentSourceFile(decl.source_file);
|
||||
const obj: *const Node = switch (cd.value.data) {
|
||||
.field_access => |fa| fa.object,
|
||||
.index_expr => |ie| ie.object,
|
||||
else => continue,
|
||||
};
|
||||
if (obj.data != .identifier) continue;
|
||||
const recv_is_agg = switch (self.selectModuleConst(obj.data.identifier.name)) {
|
||||
.resolved => |sel| sel.info.value.data == .array_literal or sel.info.value.data == .struct_literal,
|
||||
.own_opaque, .ambiguous, .none => false,
|
||||
};
|
||||
if (!recv_is_agg) continue;
|
||||
self.putModuleConst(decl.source_file, cd.name, .{ .value = cd.value, .ty = .s64 });
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a typed module-level value constant (`AF_INET :s32: 2`). Run in
|
||||
@@ -966,6 +990,11 @@ pub fn registerConstArrayGlobal(self: *Lowering, cd: *const ast.ConstDecl) void
|
||||
.is_const = true,
|
||||
});
|
||||
self.putGlobal(self.current_source_file, cd.name, .{ .id = gid, .ty = arr_ty });
|
||||
// ALSO register as a module const so the comptime folders see the
|
||||
// elements (`K.len` / `K[<const idx>]` in dims and const exprs).
|
||||
// Bare value reads still hit the GLOBAL arm first (identifier arm
|
||||
// order), so this never double-emits.
|
||||
self.putModuleConst(self.current_source_file, cd.name, .{ .value = cd.value, .ty = arr_ty });
|
||||
}
|
||||
|
||||
/// Infer `[N]T` for an untyped array-literal constant. Element types unify:
|
||||
|
||||
@@ -160,6 +160,15 @@ const DimCtx = struct {
|
||||
return null;
|
||||
}
|
||||
// `xs` stands in for a pack of arity 3; every other name has no pack length.
|
||||
pub fn lookupConstAggLen(_: DimCtx, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupConstArrayElem(_: DimCtx, _: []const u8, _: i64, _: ?ast.Span) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupConstStructField(_: DimCtx, _: []const u8, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupPackLen(_: DimCtx, name: []const u8) ?i64 {
|
||||
if (std.mem.eql(u8, name, "xs")) return 3;
|
||||
return null;
|
||||
|
||||
@@ -113,6 +113,15 @@ const ModuleConstCtx = struct {
|
||||
pub fn lookupDimName(self: ModuleConstCtx, name: []const u8) ?i64 {
|
||||
return moduleConstIntFramed(self.consts, self.table, name, self.frame);
|
||||
}
|
||||
pub fn lookupConstAggLen(_: ModuleConstCtx, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupConstArrayElem(_: ModuleConstCtx, _: []const u8, _: i64, _: ?ast.Span) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupConstStructField(_: ModuleConstCtx, _: []const u8, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupPackLen(_: ModuleConstCtx, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
@@ -329,17 +338,35 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
|
||||
};
|
||||
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);
|
||||
// `inline for 0..xs.len` bound); `<array const>.len` to the
|
||||
// const's element count.
|
||||
if (std.mem.eql(u8, fa.field, "len")) {
|
||||
if (ctx.lookupPackLen(on)) |n| break :blk n;
|
||||
break :blk ctx.lookupConstAggLen(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;
|
||||
// A struct const's integer field (`LIT.r`) folds to the
|
||||
// SELECTED author's field value.
|
||||
if (ctx.lookupConstStructField(on, fa.field)) |v| break :blk v;
|
||||
}
|
||||
// Any other field access is not a compile-time integer leaf.
|
||||
break :blk null;
|
||||
},
|
||||
// `K[<const idx>]` over an ARRAY const folds to the element's value
|
||||
// (bounds-checked at fold time — out of range diagnoses, never wraps).
|
||||
.index_expr => |ie| blk: {
|
||||
const on: ?[]const u8 = switch (ie.object.data) {
|
||||
.identifier => |id| if (id.is_raw) null else id.name,
|
||||
else => null,
|
||||
};
|
||||
const name = on orelse break :blk null;
|
||||
const idx = evalConstIntExpr(ie.index, ctx) orelse break :blk null;
|
||||
break :blk ctx.lookupConstArrayElem(name, idx, node.span);
|
||||
},
|
||||
.unary_op => |u| switch (u.op) {
|
||||
.negate => {
|
||||
const v = evalConstIntExpr(u.operand, ctx) orelse return null;
|
||||
|
||||
@@ -76,6 +76,15 @@ const StatelessInner = struct {
|
||||
/// registration-time path has no pack-arity information (packs are bound
|
||||
/// during body lowering), so a `<pack>.len` dimension is never a
|
||||
/// compile-time integer here → null → the clean unresolved-dim diagnostic.
|
||||
pub fn lookupConstAggLen(_: StatelessInner, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupConstArrayElem(_: StatelessInner, _: []const u8, _: i64, _: ?ast.Span) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupConstStructField(_: StatelessInner, _: []const u8, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
pub fn lookupPackLen(_: StatelessInner, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user