diff --git a/src/AstGen.zig b/src/AstGen.zig index 61476b505a..a602be8538 100644 --- a/src/AstGen.zig +++ b/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, diff --git a/src/translate_c.zig b/src/translate_c.zig index 93c85ab8bd..b8b221301c 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -5524,22 +5524,38 @@ const MacroCtx = struct { return MacroSlicer{ .source = self.source, .tokens = self.list }; } - fn containsUndefinedIdentifier(self: *MacroCtx, scope: *Scope, params: []const ast.Payload.Param) ?[]const u8 { + const MacroTranslateError = union(enum) { + undefined_identifier: []const u8, + invalid_arg_usage: []const u8, + }; + + fn checkTranslatableMacro(self: *MacroCtx, scope: *Scope, params: []const ast.Payload.Param) ?MacroTranslateError { const slicer = self.makeSlicer(); + var last_is_type_kw = false; var i: usize = 1; // index 0 is the macro name while (i < self.list.len) : (i += 1) { const token = self.list[i]; switch (token.id) { .Period, .Arrow => i += 1, // skip next token since field identifiers can be unknown + .Keyword_struct, .Keyword_union, .Keyword_enum => if (!last_is_type_kw) { + last_is_type_kw = true; + continue; + }, .Identifier => { const identifier = slicer.slice(token); const is_param = for (params) |param| { if (param.name != null and mem.eql(u8, identifier, param.name.?)) break true; } else false; - if (!scope.contains(identifier) and !isBuiltinDefined(identifier) and !is_param) return identifier; + if (is_param and last_is_type_kw) { + return .{ .invalid_arg_usage = identifier }; + } + if (!scope.contains(identifier) and !isBuiltinDefined(identifier) and !is_param) { + return .{ .undefined_identifier = identifier }; + } }, else => {}, } + last_is_type_kw = false; } return null; } @@ -5649,8 +5665,10 @@ fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void { fn transMacroDefine(c: *Context, m: *MacroCtx) ParseError!void { const scope = &c.global_scope.base; - if (m.containsUndefinedIdentifier(scope, &.{})) |ident| - return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}); + if (m.checkTranslatableMacro(scope, &.{})) |err| switch (err) { + .undefined_identifier => |ident| return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}), + .invalid_arg_usage => unreachable, // no args + }; const init_node = try parseCExpr(c, m, scope); const last = m.next().?; @@ -5698,8 +5716,10 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { try m.skip(c, .RParen); - if (m.containsUndefinedIdentifier(scope, fn_params.items)) |ident| - return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}); + if (m.checkTranslatableMacro(scope, fn_params.items)) |err| switch (err) { + .undefined_identifier => |ident| return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}), + .invalid_arg_usage => |ident| return m.fail(c, "unable to translate macro: untranslatable usage of arg `{s}`", .{ident}), + }; const expr = try parseCExpr(c, m, scope); const last = m.next().?; diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 35cfab9211..aae5c33635 100644 --- a/test/behavior/eval.zig +++ b/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; diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 0e3000a113..13b72c1738 100644 --- a/test/run_translated_c.zig +++ b/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 + \\int main(void) { + \\ int x = 123; + \\ union { typeof(x) val; } u = { x }; + \\ if (u.val != 123) abort(); + \\ return 0; + \\} + , ""); } diff --git a/test/translate_c.zig b/test/translate_c.zig index 3a453b9aa8..3fad7f8fe5 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -4129,4 +4129,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ }) != 0) {} \\} }); + + cases.add("macro using argument as struct name is not translated", + \\#define FOO(x) struct x + , &[_][]const u8{ + \\pub const FOO = @compileError("unable to translate macro: untranslatable usage of arg `x`"); + }); }