|
|
|
|
@@ -739,19 +739,25 @@ pub fn lowerComptimeCall(self: *Lowering, fd: *const ast.FnDecl, call_node: *con
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Bind ENUM-typed comptime value params (`$o: Ord`) to their argument's
|
|
|
|
|
// variant tag — both into `comptime_value_bindings` (so a downstream
|
|
|
|
|
// lowerer can read the constant tag for an identifier) AND as a scoped
|
|
|
|
|
// enum value (so `if o == .a` lowers as an ordinary enum comparison).
|
|
|
|
|
// Saved/restored around the body so nested comptime calls don't leak the
|
|
|
|
|
// outer call's tags. A non-constant / unknown-variant enum arg is a loud
|
|
|
|
|
// diagnostic (never a silent default) per the value-param rules.
|
|
|
|
|
const saved_enum_value_bindings = self.comptime_value_bindings;
|
|
|
|
|
var enum_value_bindings: ?std.StringHashMap(i64) = null;
|
|
|
|
|
self.bindEnumValueParams(fd, cpn, &enum_value_bindings);
|
|
|
|
|
// Bind VALUE-typed comptime params (`$o: Ord`, `$s: Shape`, ...) to their
|
|
|
|
|
// argument's materialized comptime value — both into
|
|
|
|
|
// `comptime_value_bindings` (the comptime-readable scalar: int / enum-tag /
|
|
|
|
|
// tagged-union-tag, so a downstream lowerer can read the constant for an
|
|
|
|
|
// identifier) AND as a scoped value (so `if o == .a` / `if s == .circle`
|
|
|
|
|
// lowers as an ordinary comparison). Saved/restored around the body so
|
|
|
|
|
// nested comptime calls don't leak the outer call's bindings. A
|
|
|
|
|
// non-constant / unknown-variant arg is a loud diagnostic (never a silent
|
|
|
|
|
// default) per the value-param rules.
|
|
|
|
|
const saved_value_bindings = self.comptime_value_bindings;
|
|
|
|
|
const saved_value_ref_bindings = self.comptime_value_ref_bindings;
|
|
|
|
|
var value_bindings: ?std.StringHashMap(i64) = null;
|
|
|
|
|
var value_ref_bindings: ?std.StringHashMap(Ref) = null;
|
|
|
|
|
self.bindComptimeValueParams(fd, cpn, &value_bindings, &value_ref_bindings);
|
|
|
|
|
defer {
|
|
|
|
|
if (enum_value_bindings) |*evb| evb.deinit();
|
|
|
|
|
self.comptime_value_bindings = saved_enum_value_bindings;
|
|
|
|
|
if (value_bindings) |*vb| vb.deinit();
|
|
|
|
|
if (value_ref_bindings) |*vrb| vrb.deinit();
|
|
|
|
|
self.comptime_value_bindings = saved_value_bindings;
|
|
|
|
|
self.comptime_value_ref_bindings = saved_value_ref_bindings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Install comptime param nodes and lower the function body inline
|
|
|
|
|
@@ -851,92 +857,204 @@ pub fn lowerComptimeCall(self: *Lowering, fd: *const ast.FnDecl, call_node: *con
|
|
|
|
|
return self.builder.constInt(0, .void);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Bind ENUM-typed comptime value params (`$o: Ord`) for an inlined comptime
|
|
|
|
|
/// call. For each comptime param whose declared constraint resolves to an
|
|
|
|
|
/// `.@"enum"` type and whose call-site argument is a constant enum literal
|
|
|
|
|
/// (`.b`), this:
|
|
|
|
|
/// 1. folds the literal to its variant tag (declaration-order index, or the
|
|
|
|
|
/// explicit value),
|
|
|
|
|
/// 2. records `param_name -> tag` in `self.comptime_value_bindings` (lazily
|
|
|
|
|
/// allocated into `*store` and activated on `self`), so a downstream
|
|
|
|
|
/// lowerer / `#builtin` recognizer can read the constant tag for the
|
|
|
|
|
/// param identifier via `comptimeIntNamed(param_name)` (or directly off
|
|
|
|
|
/// `comptime_value_bindings.get(param_name)`), and
|
|
|
|
|
/// 3. binds `param_name` into the active scope as an `enum_init(tag, …)`
|
|
|
|
|
/// value of the constraint enum type, so the body's `if o == .a` lowers
|
|
|
|
|
/// as an ordinary enum comparison.
|
|
|
|
|
/// Bind VALUE-typed comptime params for an inlined comptime call. For each
|
|
|
|
|
/// comptime param whose declared constraint resolves to a value type and whose
|
|
|
|
|
/// call-site argument is a constant literal of that type, this materializes the
|
|
|
|
|
/// argument as a compile-time-known value and binds the param so it resolves in
|
|
|
|
|
/// the body. Two stores are written:
|
|
|
|
|
/// - `int_store` (`comptime_value_bindings`, param → i64): the
|
|
|
|
|
/// comptime-readable SCALAR — the int for an `int` param, the variant TAG
|
|
|
|
|
/// for an `enum` or `tagged_union` param. Preserves the integration
|
|
|
|
|
/// contract: `comptimeIntNamed(param)` keeps returning the tag/int, and the
|
|
|
|
|
/// param remains usable in a type position (`[o]i64`).
|
|
|
|
|
/// - `ref_store` (`comptime_value_ref_bindings`, param → Ref): the full
|
|
|
|
|
/// materialized value Ref for a non-scalar param (the `enum_init(tag,
|
|
|
|
|
/// payload)` of a tagged-union, the aggregate const of a struct/array), so
|
|
|
|
|
/// a lowering-time consumer can read the WHOLE bound value via
|
|
|
|
|
/// `comptimeValueRefNamed(param)`.
|
|
|
|
|
/// Supported constraint kinds:
|
|
|
|
|
/// - `.@"enum"` (payload-less): bind the variant tag, scope an
|
|
|
|
|
/// `enum_init(tag, none)` so `if o == .a` lowers as an enum comparison.
|
|
|
|
|
/// - `.tagged_union` (payload-bearing enum): bind the variant tag (int_store)
|
|
|
|
|
/// AND the full `enum_init(tag, payload)` value (ref_store + scope), so a
|
|
|
|
|
/// bare variant (`.point`) or a payload variant (`.circle(5.0)`) both
|
|
|
|
|
/// resolve — `if s == .circle` lowers as a tag comparison and any payload
|
|
|
|
|
/// read works off the bound value.
|
|
|
|
|
/// A non-constant argument, or one naming an unknown variant, emits a loud
|
|
|
|
|
/// diagnostic and binds nothing — never a silent default.
|
|
|
|
|
pub fn bindEnumValueParams(
|
|
|
|
|
/// diagnostic and binds nothing — never a silent default. Constraint kinds that
|
|
|
|
|
/// are not value types (a metatype / `type` constraint, a generic type param)
|
|
|
|
|
/// are left to the type-binding machinery and skipped here.
|
|
|
|
|
pub fn bindComptimeValueParams(
|
|
|
|
|
self: *Lowering,
|
|
|
|
|
fd: *const ast.FnDecl,
|
|
|
|
|
cpn: std.StringHashMap(*const Node),
|
|
|
|
|
store: *?std.StringHashMap(i64),
|
|
|
|
|
int_store: *?std.StringHashMap(i64),
|
|
|
|
|
ref_store: *?std.StringHashMap(Ref),
|
|
|
|
|
) void {
|
|
|
|
|
for (fd.params) |param| {
|
|
|
|
|
if (!param.is_comptime or param.is_variadic) continue;
|
|
|
|
|
const arg_node = cpn.get(param.name) orelse continue;
|
|
|
|
|
|
|
|
|
|
// Resolve the param's declared constraint type in the function's own
|
|
|
|
|
// defining module (the enum may be bare-visible only there).
|
|
|
|
|
// defining module (the enum/union may be bare-visible only there).
|
|
|
|
|
const constraint_ty = self.resolveParamTypeInSource(fd.body.source_file, ¶m);
|
|
|
|
|
if (constraint_ty.isBuiltin()) continue;
|
|
|
|
|
const info = self.module.types.get(constraint_ty);
|
|
|
|
|
if (info != .@"enum") continue; // only plain enums carry a bare tag
|
|
|
|
|
|
|
|
|
|
// The argument must be a constant enum literal (`.b`). A non-literal
|
|
|
|
|
// arg (a runtime value, an arbitrary expression) cannot bind a
|
|
|
|
|
// compile-time variant tag — diagnose loudly rather than fabricate one.
|
|
|
|
|
if (arg_node.data != .enum_literal) {
|
|
|
|
|
if (self.diagnostics) |d|
|
|
|
|
|
d.addFmt(.err, arg_node.span, "comptime enum value parameter '{s}' must be a constant enum literal of '{s}'", .{ param.name, self.formatTypeName(constraint_ty) });
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const variant_name = arg_node.data.enum_literal.name;
|
|
|
|
|
|
|
|
|
|
// The variant must exist on the constraint enum — otherwise this is a
|
|
|
|
|
// typo / wrong-enum literal, not a bindable tag.
|
|
|
|
|
const name_id = self.module.types.internString(variant_name);
|
|
|
|
|
var known = false;
|
|
|
|
|
for (info.@"enum".variants) |v| {
|
|
|
|
|
if (v == name_id) {
|
|
|
|
|
known = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!known) {
|
|
|
|
|
self.emitBadEnumVariant(constraint_ty, info.@"enum", variant_name, arg_node.span);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tag = self.resolveVariantValue(constraint_ty, variant_name);
|
|
|
|
|
|
|
|
|
|
// 1+2: record the constant tag, lazily creating the binding map and
|
|
|
|
|
// activating it on `self` (so `comptimeIntNamed`/the downstream lowerer
|
|
|
|
|
// can read it). Seed the map from any already-active bindings so an
|
|
|
|
|
// outer comptime call's value params stay visible in the inlined body.
|
|
|
|
|
if (store.* == null) {
|
|
|
|
|
var m = std.StringHashMap(i64).init(self.alloc);
|
|
|
|
|
if (self.comptime_value_bindings) |outer| {
|
|
|
|
|
var it = outer.iterator();
|
|
|
|
|
while (it.next()) |e| m.put(e.key_ptr.*, e.value_ptr.*) catch {};
|
|
|
|
|
}
|
|
|
|
|
store.* = m;
|
|
|
|
|
self.comptime_value_bindings = store.*;
|
|
|
|
|
}
|
|
|
|
|
store.*.?.put(param.name, @intCast(tag)) catch {};
|
|
|
|
|
self.comptime_value_bindings = store.*;
|
|
|
|
|
|
|
|
|
|
// 3: bind the param as a scoped enum value so body comparisons lower.
|
|
|
|
|
if (self.scope) |scope| {
|
|
|
|
|
const enum_val = self.builder.enumInit(tag, Ref.none, constraint_ty);
|
|
|
|
|
const slot = self.builder.alloca(constraint_ty);
|
|
|
|
|
self.builder.store(slot, enum_val);
|
|
|
|
|
scope.put(param.name, .{ .ref = slot, .ty = constraint_ty, .is_alloca = true });
|
|
|
|
|
switch (info) {
|
|
|
|
|
.@"enum" => self.bindEnumValueParam(param.name, constraint_ty, info.@"enum", arg_node, int_store),
|
|
|
|
|
.tagged_union => self.bindTaggedUnionValueParam(param.name, constraint_ty, info.tagged_union, arg_node, int_store, ref_store),
|
|
|
|
|
// Other constraint kinds (`type`-metatype params, generic type
|
|
|
|
|
// params, structs/arrays) are not bound here. struct/array
|
|
|
|
|
// aggregate value params are not yet wired (no literal-shape repro
|
|
|
|
|
// in the corpus drives them); when one lands, add a `.@"struct"` /
|
|
|
|
|
// `.array` arm that lowers the literal to an aggregate const and
|
|
|
|
|
// writes `ref_store`. Until then we leave the param to whatever
|
|
|
|
|
// downstream resolution already applies — never a silent default.
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Record `param → tag` in `int_store` (lazily creating/activating it, seeded
|
|
|
|
|
/// from any outer-active bindings so a surrounding comptime call's value params
|
|
|
|
|
/// stay visible). Shared by the enum and tagged-union binders.
|
|
|
|
|
pub fn recordComptimeTag(self: *Lowering, store: *?std.StringHashMap(i64), name: []const u8, tag: i64) void {
|
|
|
|
|
if (store.* == null) {
|
|
|
|
|
var m = std.StringHashMap(i64).init(self.alloc);
|
|
|
|
|
if (self.comptime_value_bindings) |outer| {
|
|
|
|
|
var it = outer.iterator();
|
|
|
|
|
while (it.next()) |e| m.put(e.key_ptr.*, e.value_ptr.*) catch {};
|
|
|
|
|
}
|
|
|
|
|
store.* = m;
|
|
|
|
|
self.comptime_value_bindings = store.*;
|
|
|
|
|
}
|
|
|
|
|
store.*.?.put(name, tag) catch {};
|
|
|
|
|
self.comptime_value_bindings = store.*;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Record `param → value Ref` in `ref_store` (lazily creating/activating it,
|
|
|
|
|
/// seeded from any outer-active ref bindings). Companion to `recordComptimeTag`
|
|
|
|
|
/// for the full materialized value of a non-scalar comptime value param.
|
|
|
|
|
pub fn recordComptimeValueRef(self: *Lowering, store: *?std.StringHashMap(Ref), name: []const u8, ref: Ref) void {
|
|
|
|
|
if (store.* == null) {
|
|
|
|
|
var m = std.StringHashMap(Ref).init(self.alloc);
|
|
|
|
|
if (self.comptime_value_ref_bindings) |outer| {
|
|
|
|
|
var it = outer.iterator();
|
|
|
|
|
while (it.next()) |e| m.put(e.key_ptr.*, e.value_ptr.*) catch {};
|
|
|
|
|
}
|
|
|
|
|
store.* = m;
|
|
|
|
|
self.comptime_value_ref_bindings = store.*;
|
|
|
|
|
}
|
|
|
|
|
store.*.?.put(name, ref) catch {};
|
|
|
|
|
self.comptime_value_ref_bindings = store.*;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Bind a payload-less `.@"enum"` comptime value param. The arg must be a
|
|
|
|
|
/// constant enum literal (`.b`) naming a known variant; records the tag and
|
|
|
|
|
/// scopes an `enum_init(tag, none)`.
|
|
|
|
|
pub fn bindEnumValueParam(
|
|
|
|
|
self: *Lowering,
|
|
|
|
|
name: []const u8,
|
|
|
|
|
constraint_ty: TypeId,
|
|
|
|
|
enum_info: types.TypeInfo.EnumInfo,
|
|
|
|
|
arg_node: *const Node,
|
|
|
|
|
int_store: *?std.StringHashMap(i64),
|
|
|
|
|
) void {
|
|
|
|
|
// The argument must be a constant enum literal (`.b`). A non-literal arg
|
|
|
|
|
// (a runtime value, an arbitrary expression) cannot bind a compile-time
|
|
|
|
|
// variant tag — diagnose loudly rather than fabricate one.
|
|
|
|
|
if (arg_node.data != .enum_literal) {
|
|
|
|
|
if (self.diagnostics) |d|
|
|
|
|
|
d.addFmt(.err, arg_node.span, "comptime enum value parameter '{s}' must be a constant enum literal of '{s}'", .{ name, self.formatTypeName(constraint_ty) });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const variant_name = arg_node.data.enum_literal.name;
|
|
|
|
|
if (!self.enumHasVariant(enum_info.variants, variant_name)) {
|
|
|
|
|
self.emitBadEnumVariant(constraint_ty, enum_info, variant_name, arg_node.span);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tag = self.resolveVariantValue(constraint_ty, variant_name);
|
|
|
|
|
self.recordComptimeTag(int_store, name, @intCast(tag));
|
|
|
|
|
|
|
|
|
|
if (self.scope) |scope| {
|
|
|
|
|
const enum_val = self.builder.enumInit(tag, Ref.none, constraint_ty);
|
|
|
|
|
const slot = self.builder.alloca(constraint_ty);
|
|
|
|
|
self.builder.store(slot, enum_val);
|
|
|
|
|
scope.put(name, .{ .ref = slot, .ty = constraint_ty, .is_alloca = true });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Bind a `.tagged_union` (payload-bearing enum) comptime value param. The arg
|
|
|
|
|
/// is one of two constant forms:
|
|
|
|
|
/// - a bare variant literal `.point` (no payload), an `.enum_literal` node, or
|
|
|
|
|
/// - a payload variant `.circle(5.0)`, a `.call` node whose callee is an
|
|
|
|
|
/// `.enum_literal`.
|
|
|
|
|
/// Both must name a known variant. Records the variant TAG in `int_store` (so
|
|
|
|
|
/// `comptimeIntNamed`/`if s == .circle` work) AND the full materialized
|
|
|
|
|
/// `enum_init(tag, payload)` value in `ref_store` + scope (so a payload read off
|
|
|
|
|
/// the bound value resolves). A non-constant arg (any other node shape) or an
|
|
|
|
|
/// unknown variant is a loud diagnostic.
|
|
|
|
|
pub fn bindTaggedUnionValueParam(
|
|
|
|
|
self: *Lowering,
|
|
|
|
|
name: []const u8,
|
|
|
|
|
constraint_ty: TypeId,
|
|
|
|
|
union_info: types.TypeInfo.TaggedUnionInfo,
|
|
|
|
|
arg_node: *const Node,
|
|
|
|
|
int_store: *?std.StringHashMap(i64),
|
|
|
|
|
ref_store: *?std.StringHashMap(Ref),
|
|
|
|
|
) void {
|
|
|
|
|
// Identify the variant name from either accepted literal shape.
|
|
|
|
|
const variant_name: []const u8 = switch (arg_node.data) {
|
|
|
|
|
.enum_literal => |el| el.name,
|
|
|
|
|
.call => |c| if (c.callee.data == .enum_literal) c.callee.data.enum_literal.name else {
|
|
|
|
|
if (self.diagnostics) |d|
|
|
|
|
|
d.addFmt(.err, arg_node.span, "comptime tagged-union value parameter '{s}' must be a constant variant literal of '{s}' (e.g. `.variant` or `.variant(payload)`)", .{ name, self.formatTypeName(constraint_ty) });
|
|
|
|
|
return;
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
if (self.diagnostics) |d|
|
|
|
|
|
d.addFmt(.err, arg_node.span, "comptime tagged-union value parameter '{s}' must be a constant variant literal of '{s}' (e.g. `.variant` or `.variant(payload)`)", .{ name, self.formatTypeName(constraint_ty) });
|
|
|
|
|
return;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (self.findTaggedVariant(union_info, variant_name) == null) {
|
|
|
|
|
self.emitBadVariant(constraint_ty, union_info, variant_name, arg_node.span);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tag = self.resolveVariantIndex(constraint_ty, variant_name);
|
|
|
|
|
self.recordComptimeTag(int_store, name, @intCast(tag));
|
|
|
|
|
|
|
|
|
|
// Materialize the full value by lowering the argument expression with the
|
|
|
|
|
// constraint as its target type — `.circle(5.0)` lowers to an
|
|
|
|
|
// `enum_init(tag, payload)`, `.point` to `enum_init(tag, none)`. This runs
|
|
|
|
|
// in the CALLER's scope/source context (the arg was authored there), which
|
|
|
|
|
// is exactly where its payload sub-expressions must resolve.
|
|
|
|
|
const saved_target = self.target_type;
|
|
|
|
|
self.target_type = constraint_ty;
|
|
|
|
|
const value = self.lowerExpr(arg_node);
|
|
|
|
|
self.target_type = saved_target;
|
|
|
|
|
|
|
|
|
|
self.recordComptimeValueRef(ref_store, name, value);
|
|
|
|
|
|
|
|
|
|
if (self.scope) |scope| {
|
|
|
|
|
const slot = self.builder.alloca(constraint_ty);
|
|
|
|
|
self.builder.store(slot, value);
|
|
|
|
|
scope.put(name, .{ .ref = slot, .ty = constraint_ty, .is_alloca = true });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// True iff `variants` (interned enum variant name-ids) contains `variant_name`.
|
|
|
|
|
pub fn enumHasVariant(self: *Lowering, variants: []const types.StringId, variant_name: []const u8) bool {
|
|
|
|
|
const name_id = self.module.types.internString(variant_name);
|
|
|
|
|
for (variants) |v| {
|
|
|
|
|
if (v == name_id) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// True if `node` (a fn body) contains any top-level `return` statement.
|
|
|
|
|
/// Used by inline-comptime lowering to decide whether to allocate a
|
|
|
|
|
/// result slot — pure tail-expression bodies skip the slot. Walks past
|
|
|
|
|
@@ -1104,6 +1222,19 @@ pub fn comptimeIntNamed(self: *Lowering, name: []const u8) ?i64 {
|
|
|
|
|
return self.foldSourceConstInt(name, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Lowering-time accessor for the full materialized value of a NON-scalar
|
|
|
|
|
/// comptime value param (a tagged-union literal, a struct/array aggregate):
|
|
|
|
|
/// returns the IR `Ref` of the bound value (e.g. the `enum_init(tag, payload)`
|
|
|
|
|
/// of a `$s: Shape` bound to `.circle(5.0)`), or null if `name` is not a
|
|
|
|
|
/// non-scalar comptime value binding. Companion to `comptimeIntNamed`, which
|
|
|
|
|
/// returns the comptime-readable SCALAR (the variant tag) for the same param.
|
|
|
|
|
pub fn comptimeValueRefNamed(self: *Lowering, name: []const u8) ?Ref {
|
|
|
|
|
if (self.comptime_value_ref_bindings) |crb| {
|
|
|
|
|
if (crb.get(name)) |r| return r;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Source-aware INTEGER fold of a module const `name` (E2/F2/R1). Select the
|
|
|
|
|
/// SOURCE-AWARE author (own-wins; ≥2 flat-visible → ambiguous → null, the loud
|
|
|
|
|
/// diagnostic is the reference site's job), then fold ITS RHS with nested const
|
|
|
|
|
|