comptime enum value params: $o: EnumType binds+resolves variant tag
A comptime value param whose constraint is a plain enum ($o: Ord) now binds its enum-literal argument to the variant tag during inlined comptime-call lowering. The tag is recorded in comptime_value_bindings (readable downstream via comptimeIntNamed / direct map lookup, and as an array-dim style const-int leaf) AND the param is bound into scope as an enum_init value so body comparisons like 'if o == .a' lower as ordinary enum comparisons. Distinct ordering args monomorphize the inlined body per value. A non-constant argument or an unknown variant emits a loud diagnostic and binds nothing — never a silent default. Locked by examples/0627-comptime-enum-value-param.sx.
This commit is contained in:
27
examples/0627-comptime-enum-value-param.sx
Normal file
27
examples/0627-comptime-enum-value-param.sx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Comptime ENUM value parameter: `$o: <EnumType>` binds the enum-literal
|
||||||
|
// argument to its variant tag, monomorphizes the inlined body per distinct
|
||||||
|
// ordering value, and resolves `o` in the body as a compile-time-known enum
|
||||||
|
// constant — usable both in `if o == .a` comparisons AND as a comptime-readable
|
||||||
|
// variant tag during lowering (a downstream lowerer reads it via
|
||||||
|
// `comptime_value_bindings`, exercised here as the `[o]i64` array dimension).
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Ord :: enum { a; b; c; }
|
||||||
|
|
||||||
|
pick :: ($o: Ord) -> i64 {
|
||||||
|
if o == .a { return 10; }
|
||||||
|
if o == .b { return 20; }
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `o` read as a compile-time integer (its variant tag) in a TYPE position.
|
||||||
|
tag_dim :: ($o: Ord) -> i64 {
|
||||||
|
arr : [o]i64 = ---;
|
||||||
|
return arr.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("{}\n", pick(.b)); // 20
|
||||||
|
print("{} {} {}\n", pick(.a), pick(.b), pick(.c)); // 10 20 30
|
||||||
|
print("{} {} {}\n", tag_dim(.a), tag_dim(.b), tag_dim(.c)); // 0 1 2
|
||||||
|
}
|
||||||
1
examples/expected/0627-comptime-enum-value-param.exit
Normal file
1
examples/expected/0627-comptime-enum-value-param.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/0627-comptime-enum-value-param.stderr
Normal file
1
examples/expected/0627-comptime-enum-value-param.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
3
examples/expected/0627-comptime-enum-value-param.stdout
Normal file
3
examples/expected/0627-comptime-enum-value-param.stdout
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
20
|
||||||
|
10 20 30
|
||||||
|
0 1 2
|
||||||
@@ -1572,6 +1572,7 @@ pub const Lowering = struct {
|
|||||||
pub const lowerComptimeGlobal = lower_comptime.lowerComptimeGlobal;
|
pub const lowerComptimeGlobal = lower_comptime.lowerComptimeGlobal;
|
||||||
pub const lowerComptimeSideEffect = lower_comptime.lowerComptimeSideEffect;
|
pub const lowerComptimeSideEffect = lower_comptime.lowerComptimeSideEffect;
|
||||||
pub const lowerComptimeCall = lower_comptime.lowerComptimeCall;
|
pub const lowerComptimeCall = lower_comptime.lowerComptimeCall;
|
||||||
|
pub const bindEnumValueParams = lower_comptime.bindEnumValueParams;
|
||||||
pub const lowerInlineComptime = lower_comptime.lowerInlineComptime;
|
pub const lowerInlineComptime = lower_comptime.lowerInlineComptime;
|
||||||
pub const lowerInsertExpr = lower_comptime.lowerInsertExpr;
|
pub const lowerInsertExpr = lower_comptime.lowerInsertExpr;
|
||||||
pub const lowerInsertExprValue = lower_comptime.lowerInsertExprValue;
|
pub const lowerInsertExprValue = lower_comptime.lowerInsertExprValue;
|
||||||
|
|||||||
@@ -739,6 +739,21 @@ 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);
|
||||||
|
defer {
|
||||||
|
if (enum_value_bindings) |*evb| evb.deinit();
|
||||||
|
self.comptime_value_bindings = saved_enum_value_bindings;
|
||||||
|
}
|
||||||
|
|
||||||
// Install comptime param nodes and lower the function body inline
|
// Install comptime param nodes and lower the function body inline
|
||||||
const saved_cpn = self.comptime_param_nodes;
|
const saved_cpn = self.comptime_param_nodes;
|
||||||
self.comptime_param_nodes = cpn;
|
self.comptime_param_nodes = cpn;
|
||||||
@@ -836,6 +851,92 @@ pub fn lowerComptimeCall(self: *Lowering, fd: *const ast.FnDecl, call_node: *con
|
|||||||
return self.builder.constInt(0, .void);
|
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.
|
||||||
|
/// A non-constant argument, or one naming an unknown variant, emits a loud
|
||||||
|
/// diagnostic and binds nothing — never a silent default.
|
||||||
|
pub fn bindEnumValueParams(
|
||||||
|
self: *Lowering,
|
||||||
|
fd: *const ast.FnDecl,
|
||||||
|
cpn: std.StringHashMap(*const Node),
|
||||||
|
store: *?std.StringHashMap(i64),
|
||||||
|
) 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).
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// True if `node` (a fn body) contains any top-level `return` statement.
|
/// True if `node` (a fn body) contains any top-level `return` statement.
|
||||||
/// Used by inline-comptime lowering to decide whether to allocate a
|
/// Used by inline-comptime lowering to decide whether to allocate a
|
||||||
/// result slot — pure tail-expression bodies skip the slot. Walks past
|
/// result slot — pure tail-expression bodies skip the slot. Walks past
|
||||||
|
|||||||
Reference in New Issue
Block a user