diff --git a/examples/0627-comptime-enum-value-param.sx b/examples/0627-comptime-enum-value-param.sx new file mode 100644 index 00000000..1093d7ff --- /dev/null +++ b/examples/0627-comptime-enum-value-param.sx @@ -0,0 +1,27 @@ +// Comptime ENUM value parameter: `$o: ` 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 +} diff --git a/examples/expected/0627-comptime-enum-value-param.exit b/examples/expected/0627-comptime-enum-value-param.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/expected/0627-comptime-enum-value-param.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0627-comptime-enum-value-param.stderr b/examples/expected/0627-comptime-enum-value-param.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/0627-comptime-enum-value-param.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0627-comptime-enum-value-param.stdout b/examples/expected/0627-comptime-enum-value-param.stdout new file mode 100644 index 00000000..bba73dbe --- /dev/null +++ b/examples/expected/0627-comptime-enum-value-param.stdout @@ -0,0 +1,3 @@ +20 +10 20 30 +0 1 2 diff --git a/src/ir/lower.zig b/src/ir/lower.zig index ca3d67f3..a9dca39e 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -1572,6 +1572,7 @@ pub const Lowering = struct { pub const lowerComptimeGlobal = lower_comptime.lowerComptimeGlobal; pub const lowerComptimeSideEffect = lower_comptime.lowerComptimeSideEffect; pub const lowerComptimeCall = lower_comptime.lowerComptimeCall; + pub const bindEnumValueParams = lower_comptime.bindEnumValueParams; pub const lowerInlineComptime = lower_comptime.lowerInlineComptime; pub const lowerInsertExpr = lower_comptime.lowerInsertExpr; pub const lowerInsertExprValue = lower_comptime.lowerInsertExprValue; diff --git a/src/ir/lower/comptime.zig b/src/ir/lower/comptime.zig index acdd074c..8b4002f9 100644 --- a/src/ir/lower/comptime.zig +++ b/src/ir/lower/comptime.zig @@ -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 const saved_cpn = self.comptime_param_nodes; 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); } +/// 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. /// Used by inline-comptime lowering to decide whether to allocate a /// result slot — pure tail-expression bodies skip the slot. Walks past