Commit 28caaea093

mlugg <mlugg@mlugg.co.uk>
2023-09-16 23:35:42
AstGen: allow closure over known-runtime values within @TypeOf
AstGen emits an error when a closure over a known-runtime value crosses a namespace boundary. This usually makes sense: however, this usage is actually valid if the capture is within a `@TypeOf` operand. Sema already has a special case to allow such closure within `@TypeOf` when AstGen could not determine a value to be runtime-known. This commit simply introduces analagous logic to AstGen to allow `var`s to cross namespace boundaries within `@TypeOf`.
1 parent f40f81c
Changed files (3)
src/AstGen.zig
@@ -7468,7 +7468,7 @@ fn localVarRef(
                 }
 
                 // Can't close over a runtime variable
-                if (num_namespaces_out != 0 and !local_ptr.maybe_comptime) {
+                if (num_namespaces_out != 0 and !local_ptr.maybe_comptime and !gz.is_typeof) {
                     const ident_name = try astgen.identifierTokenString(ident_token);
                     return astgen.failNodeNotes(ident, "mutable '{s}' not accessible from here", .{ident_name}, &.{
                         try astgen.errNoteTok(local_ptr.token_src, "declared mutable here", .{}),
@@ -8041,6 +8041,7 @@ fn typeOf(
 
         var typeof_scope = gz.makeSubBlock(scope);
         typeof_scope.is_comptime = false;
+        typeof_scope.is_typeof = true;
         typeof_scope.c_import = false;
         defer typeof_scope.unstack();
 
@@ -10882,6 +10883,9 @@ const GenZir = struct {
     /// whenever we know Sema will analyze the current block with `is_comptime`,
     /// for instance when we're within a `struct_decl` or a `block_comptime`.
     is_comptime: bool,
+    /// Whether we're in an expression within a `@TypeOf` operand. In this case, closure of runtime
+    /// variables is permitted where it is usually not.
+    is_typeof: bool = false,
     /// This is set to true for inline loops; false otherwise.
     is_inline: bool = false,
     c_import: bool = false,
@@ -10953,6 +10957,7 @@ const GenZir = struct {
     fn makeSubBlock(gz: *GenZir, scope: *Scope) GenZir {
         return .{
             .is_comptime = gz.is_comptime,
+            .is_typeof = gz.is_typeof,
             .c_import = gz.c_import,
             .decl_node_index = gz.decl_node_index,
             .decl_line = gz.decl_line,
test/behavior/eval.zig
@@ -991,6 +991,16 @@ test "closure capture type of runtime-known parameter" {
     try S.b(c);
 }
 
+test "closure capture type of runtime-known var" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
+
+    var x: u32 = 1234;
+    const S = struct { val: @TypeOf(x + 100) };
+    const s: S = .{ .val = x };
+    try expect(s.val == 1234);
+}
+
 test "comptime break passing through runtime condition converted to runtime break" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
test/run_translated_c.zig
@@ -1895,4 +1895,14 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
         \\    return 0;
         \\}
     , "");
+
+    cases.add("Closure over local in typeof",
+        \\#include <stdlib.h>
+        \\int main(void) {
+        \\    int x = 123;
+        \\    union { typeof(x) val; } u = { x };
+        \\    if (u.val != 123) abort();
+        \\    return 0;
+        \\}
+    , "");
 }