From fff8eff2bd0cc36b192d0ab43b522161227c3c2d Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Thu, 13 Feb 2025 20:05:00 +0100 Subject: [PATCH 01/12] initial implementation of `@deprecated` --- lib/std/zig/AstGen.zig | 13 +++++++++++++ lib/std/zig/AstRlAnnotate.zig | 7 +++++-- lib/std/zig/BuiltinFn.zig | 9 +++++++++ lib/std/zig/Zir.zig | 4 ++++ src/Compilation.zig | 1 + src/Package/Module.zig | 11 +++++++++++ src/Sema.zig | 10 ++++++++++ src/main.zig | 6 ++++++ src/print_zir.zig | 1 + 9 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 5dc41cc9d2..56254411ce 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -9695,6 +9695,19 @@ fn builtinCall( .volatile_cast, => return ptrCast(gz, scope, ri, node), + .deprecated => { + _ = try gz.addExtendedNodeSmall(.deprecated, node, 0); + switch (params.len) { + 0 => return .void_value, + 1 => return expr(gz, scope, ri, params[0]), + else => return astgen.failNode( + node, + "expected 0 or 1 argument, found {}", + .{params.len}, + ), + } + }, + // zig fmt: off .has_decl => return hasDeclOrField(gz, scope, ri, node, params[0], params[1], .has_decl), .has_field => return hasDeclOrField(gz, scope, ri, node, params[0], params[1], .has_field), diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig index ccc23b18aa..f58ba213cd 100644 --- a/lib/std/zig/AstRlAnnotate.zig +++ b/lib/std/zig/AstRlAnnotate.zig @@ -817,8 +817,6 @@ fn blockExpr(astrl: *AstRlAnnotate, parent_block: ?*Block, ri: ResultInfo, node: } fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.Node.Index, args: []const Ast.Node.Index) !bool { - _ = ri; // Currently, no builtin consumes its result location. - const tree = astrl.tree; const main_tokens = tree.nodes.items(.main_token); const builtin_token = main_tokens[node]; @@ -828,6 +826,11 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast. if (expected != args.len) return false; } switch (info.tag) { + .deprecated => if (args.len >= 1) { + return astrl.expr(args[0], block, ri); + } else { + return false; + }, .import => return false, .branch_hint => { _ = try astrl.expr(args[0], block, ResultInfo.type_only); diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index 1bf31cd165..c0e0b522a3 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -121,6 +121,7 @@ pub const Tag = enum { work_item_id, work_group_size, work_group_id, + deprecated, }; pub const EvalToError = enum { @@ -1016,6 +1017,14 @@ pub const list = list: { .illegal_outside_function = true, }, }, + .{ + "@deprecated", + .{ + .tag = .deprecated, + .param_count = null, + .eval_to_error = .maybe, + }, + }, }); }; diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 5d013635cb..ea69bc2da0 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -2112,6 +2112,9 @@ pub const Inst = struct { /// any code may have gone here, avoiding false-positive "unreachable code" errors. astgen_error, + /// `operand` is `src_node: i32`. + deprecated, + pub const InstData = struct { opcode: Extended, small: u16, @@ -4310,6 +4313,7 @@ fn findTrackableInner( .value_placeholder => unreachable, // Once again, we start with the boring tags. + .deprecated, .this, .ret_addr, .builtin_src, diff --git a/src/Compilation.zig b/src/Compilation.zig index 969a399640..7331bf2097 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -869,6 +869,7 @@ pub const cache_helpers = struct { hh.add(mod.sanitize_c); hh.add(mod.sanitize_thread); hh.add(mod.fuzz); + hh.add(mod.allow_deprecated); hh.add(mod.unwind_tables); hh.add(mod.structured_cfg); hh.add(mod.no_builtin); diff --git a/src/Package/Module.zig b/src/Package/Module.zig index 0dec7bde76..6bd33dffa1 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -27,6 +27,7 @@ red_zone: bool, sanitize_c: bool, sanitize_thread: bool, fuzz: bool, +allow_deprecated: bool, unwind_tables: std.builtin.UnwindTables, cc_argv: []const []const u8, /// (SPIR-V) whether to generate a structured control flow graph or not @@ -95,6 +96,7 @@ pub const CreateOptions = struct { sanitize_c: ?bool = null, sanitize_thread: ?bool = null, fuzz: ?bool = null, + allow_deprecated: ?bool = null, structured_cfg: ?bool = null, no_builtin: ?bool = null, }; @@ -234,6 +236,12 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { break :b false; }; + const allow_deprecated = b: { + if (options.inherited.allow_deprecated) |x| break :b x; + if (options.parent) |p| break :b p.allow_deprecated; + break :b false; + }; + const code_model = b: { if (options.inherited.code_model) |x| break :b x; if (options.parent) |p| break :b p.code_model; @@ -380,6 +388,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { .sanitize_c = sanitize_c, .sanitize_thread = sanitize_thread, .fuzz = fuzz, + .allow_deprecated = allow_deprecated, .unwind_tables = unwind_tables, .cc_argv = options.cc_argv, .structured_cfg = structured_cfg, @@ -474,6 +483,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { .sanitize_c = sanitize_c, .sanitize_thread = sanitize_thread, .fuzz = fuzz, + .allow_deprecated = allow_deprecated, .unwind_tables = unwind_tables, .cc_argv = &.{}, .structured_cfg = structured_cfg, @@ -532,6 +542,7 @@ pub fn createLimited(gpa: Allocator, options: LimitedOptions) Allocator.Error!*P .sanitize_c = undefined, .sanitize_thread = undefined, .fuzz = undefined, + .allow_deprecated = undefined, .unwind_tables = undefined, .cc_argv = undefined, .structured_cfg = undefined, diff --git a/src/Sema.zig b/src/Sema.zig index 9e729a17ea..e68177433d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1091,6 +1091,7 @@ fn analyzeBodyInner( const map = &sema.inst_map; const tags = sema.code.instructions.items(.tag); const datas = sema.code.instructions.items(.data); + const mod = block.ownerModule(); var crash_info = crash_report.prepAnalyzeBody(sema, block, body); crash_info.push(); @@ -1341,6 +1342,15 @@ fn analyzeBodyInner( .extended => ext: { const extended = datas[@intFromEnum(inst)].extended; break :ext switch (extended.opcode) { + .deprecated => { + if (!mod.allow_deprecated) { + const src_node: i32 = @bitCast(extended.operand); + const src = block.nodeOffset(src_node); + return sema.fail(block, src, "found deprecated code", .{}); + } + + break :ext .void_value; + }, // zig fmt: off .struct_decl => try sema.zirStructDecl( block, extended, inst), .enum_decl => try sema.zirEnumDecl( block, extended, inst), diff --git a/src/main.zig b/src/main.zig index 5e66244484..ed862b03a0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -520,6 +520,8 @@ const usage_build_generic = \\ -fno-sanitize-thread Disable Thread Sanitizer \\ -ffuzz Enable fuzz testing instrumentation \\ -fno-fuzz Disable fuzz testing instrumentation + \\ -fallow-deprecated Allow usage of deprecated code + \\ -fno-allow-deprecated Disallow usage of deprecated code \\ -funwind-tables Always produce unwind table entries for all functions \\ -fasync-unwind-tables Always produce asynchronous unwind table entries for all functions \\ -fno-unwind-tables Never produce unwind table entries @@ -1454,6 +1456,10 @@ fn buildOutputType( mod_opts.fuzz = true; } else if (mem.eql(u8, arg, "-fno-fuzz")) { mod_opts.fuzz = false; + } else if (mem.eql(u8, arg, "-fallow-deprecated")) { + mod_opts.allow_deprecated = true; + } else if (mem.eql(u8, arg, "-fno-allow-deprecated")) { + mod_opts.allow_deprecated = false; } else if (mem.eql(u8, arg, "-fllvm")) { create_module.opts.use_llvm = true; } else if (mem.eql(u8, arg, "-fno-llvm")) { diff --git a/src/print_zir.zig b/src/print_zir.zig index 46f399dda9..ab3fa1aadc 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -535,6 +535,7 @@ const Writer = struct { .c_va_start, .in_comptime, .value_placeholder, + .deprecated, => try self.writeExtNode(stream, extended), .builtin_src => { From ba7cd8121d5a52beb1e9844a784ec7a439ee6cfd Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Thu, 13 Feb 2025 20:58:11 +0100 Subject: [PATCH 02/12] `@deprecated`: add build system support --- lib/compiler/build_runner.zig | 6 ++++++ lib/std/Build.zig | 5 +++++ lib/std/Build/Module.zig | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index a434ceed24..114a9e3acf 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -260,6 +260,10 @@ pub fn main() !void { graph.incremental = true; } else if (mem.eql(u8, arg, "-fno-incremental")) { graph.incremental = false; + } else if (mem.eql(u8, arg, "-fallow-deprecated")) { + graph.allow_deprecated = true; + } else if (mem.eql(u8, arg, "-fno-allow-deprecated")) { + graph.allow_deprecated = false; } else if (mem.eql(u8, arg, "-fwine")) { builder.enable_wine = true; } else if (mem.eql(u8, arg, "-fno-wine")) { @@ -1283,6 +1287,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void { \\ new Omit cached steps \\ failures (Default) Only print failed steps \\ none Do not print the build summary + \\ -fallow-deprecated Allow usage of deprecated code for the entire build graph + \\ -fno-allow-deprecated Disallow usage of deprecated code for the entire build graph \\ -j Limit concurrent jobs (default is to use all CPU cores) \\ --maxrss Limit memory usage (default is to use available memory) \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 4afdcd2bff..0ba5c43a46 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -94,6 +94,9 @@ available_deps: AvailableDeps, release_mode: ReleaseMode, +// True only for the top-level builder. +is_root: bool = false, + pub const ReleaseMode = enum { off, any, @@ -118,6 +121,7 @@ pub const Graph = struct { /// Information about the native target. Computed before build() is invoked. host: ResolvedTarget, incremental: ?bool = null, + allow_deprecated: ?bool = null, random_seed: u32 = 0, dependency_cache: InitializedDepMap = .empty, allow_so_scripts: ?bool = null, @@ -304,6 +308,7 @@ pub fn create( .pkg_hash = "", .available_deps = available_deps, .release_mode = .off, + .is_root = true, }; try b.top_level_steps.put(arena, b.install_tls.step.name, &b.install_tls); try b.top_level_steps.put(arena, b.uninstall_tls.step.name, &b.uninstall_tls); diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index f299946731..d93fe84416 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -25,6 +25,7 @@ stack_check: ?bool, sanitize_c: ?bool, sanitize_thread: ?bool, fuzz: ?bool, +allow_deprecated: ?bool, code_model: std.builtin.CodeModel, valgrind: ?bool, pic: ?bool, @@ -284,6 +285,7 @@ pub fn init( .owner = owner, .root_source_file = if (options.root_source_file) |lp| lp.dupe(owner) else null, .import_table = .{}, + .allow_deprecated = owner.graph.allow_deprecated orelse !owner.is_root, .resolved_target = options.target, .optimize = options.optimize, .link_libc = options.link_libc, @@ -557,6 +559,10 @@ pub fn appendZigProcessFlags( try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC"); try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); + if (m.root_source_file != null) { + try addFlag(zig_args, m.allow_deprecated, "-fallow-deprecated", "-fno-allow-deprecated"); + } + if (m.dwarf_format) |dwarf_format| { try zig_args.append(switch (dwarf_format) { .@"32" => "-gdwarf32", From 06a66745a0b5c666608482645bc61523618dab85 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Sat, 15 Feb 2025 18:04:29 +0100 Subject: [PATCH 03/12] `@deprecated`: add langref entry --- doc/langref.html.in | 37 +++++++++++++++++++++++++ doc/langref/test_deprecated_builtin.zig | 22 +++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 doc/langref/test_deprecated_builtin.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index 96034d9173..cf9aa4fb93 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4740,6 +4740,43 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

{#see_also|@cVaArg|@cVaCopy|@cVaEnd#} {#header_close#} + + {#header_open|@deprecated#} +
{#syntax#}@deprecated(value: anytype) @TypeOf(value){#endsyntax#}
+
{#syntax#}@deprecated() void{#endsyntax#}
+

+ Used to mark a given code path as deprecated. It evaluates to the same value + passed in as argument, or the {#syntax#}void{#endsyntax#} value when given none. +

+

+ As an example, in Zig 0.14.0 {#syntax#}std.time.sleep{#endsyntax#} was + deprecated and the sleep function was moved to {#syntax#}std.Thread.sleep{#endsyntax#}. + This is how this deprecation could have been expressed: + + {#syntax_block|zig|lib/std/time.zig#} +pub const sleep = @deprecated(std.Thread.sleep); // moved + {#end_syntax_block#} +

+

+ By default it is a compile error to depend on deprecated code in + a module defined by the root package, while it is not in modules defined + by dependencies. This behavior can be overridden for the entire dependency + tree by passing {#syntax#}-fallow-deprecated{#endsyntax#} or + {#syntax#}-fno-allow-deprecated{#endsyntax#} to {#syntax#}zig build{#endsyntax#}. +

+

+ Usage of this builtin is meant to help direct consumers discover (and remove) + their dependance on deprecated code during the grace period before a deprecated + functionality is turned into a {#syntax#}@compileError{#endsyntax#} or + removed entirely. +

+ +

+ {#syntax#}@deprecated{#endsyntax#} can also be used without argument: + {#code|test_deprecated_builtin.zig#} +

+ + {#header_close#} {#header_open|@divExact#}
{#syntax#}@divExact(numerator: T, denominator: T) T{#endsyntax#}
diff --git a/doc/langref/test_deprecated_builtin.zig b/doc/langref/test_deprecated_builtin.zig new file mode 100644 index 0000000000..46a0890090 --- /dev/null +++ b/doc/langref/test_deprecated_builtin.zig @@ -0,0 +1,22 @@ +test "deprecated code path" { + compute(.greedy, false, 42); +} + +const Strategy = enum { greedy, expensive, fast }; +fn compute(comptime strat: Strategy, comptime foo: bool, bar: usize) void { + switch (strat) { + .greedy => { + // This combination turned out to be ineffective. + if (!foo) @deprecated(); // use fast strategy when foo is false + runGreedy(foo, bar); + }, + .expensive => runExpensive(foo, bar), + .fast => runFast(foo, bar), + } +} + +extern fn runGreedy(foo: bool, bar: usize) void; +extern fn runExpensive(foo: bool, bar: usize) void; +extern fn runFast(foo: bool, bar: usize) void; + +// test_error=deprecated From c75fdd96d2cb9bd211ff20c7e9ee2ef119cc189f Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Sat, 15 Feb 2025 20:03:04 +0100 Subject: [PATCH 04/12] `@deprecated`: add tests --- src/Sema.zig | 18 ++++---- test/compile_errors.zig | 14 ++++++ test/tests.zig | 94 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e68177433d..c53369bffc 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1342,15 +1342,6 @@ fn analyzeBodyInner( .extended => ext: { const extended = datas[@intFromEnum(inst)].extended; break :ext switch (extended.opcode) { - .deprecated => { - if (!mod.allow_deprecated) { - const src_node: i32 = @bitCast(extended.operand); - const src = block.nodeOffset(src_node); - return sema.fail(block, src, "found deprecated code", .{}); - } - - break :ext .void_value; - }, // zig fmt: off .struct_decl => try sema.zirStructDecl( block, extended, inst), .enum_decl => try sema.zirEnumDecl( block, extended, inst), @@ -1414,6 +1405,15 @@ fn analyzeBodyInner( i += 1; continue; }, + .deprecated => { + if (!mod.allow_deprecated) { + const src_node: i32 = @bitCast(extended.operand); + const src = block.nodeOffset(src_node); + return sema.fail(block, src, "found deprecated code", .{}); + } + + break :ext .void_value; + }, .disable_instrumentation => { try sema.zirDisableInstrumentation(); i += 1; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 109347cde8..03e9324b02 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -250,4 +250,18 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { ":1:5: error: expected expression, found 'invalid token'", }); } + + { + const case = ctx.obj("usage of deprecated code", b.graph.host); + + case.addError( + \\const bad = @deprecated(42); + \\ + \\pub export fn foo() usize { + \\ return bad; + \\} + , &[_][]const u8{ + ":1:13: error: found deprecated code", + }); + } } diff --git a/test/tests.zig b/test/tests.zig index 7ef5a33a03..45cba01c7f 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1176,6 +1176,100 @@ pub fn addCliTests(b: *std.Build) *Step { step.dependOn(&cleanup.step); } + { + // Test `zig build -fallow-deprecated`. + + const deprecated_check: std.Build.Step.Run.StdIo.Check = .{ + .expect_stderr_match = "found deprecated code", + }; + + const tmp_path = b.makeTempPath(); + + // create custom main.zig file containing a deprecated decl + { + const new_main_src = + \\const bad = @deprecated(42); + \\ + \\pub fn main() u8 { + \\ return bad; + \\} + \\ + \\test { + \\ if (bad != 42) return error.Bad; + \\} + ; + + var src_dir = std.fs.cwd().makeOpenPath(b.pathJoin(&.{ tmp_path, "src" }), .{}) catch unreachable; + defer src_dir.close(); + + var main = src_dir.createFile("main.zig", .{}) catch unreachable; + defer main.close(); + + main.writeAll(new_main_src) catch unreachable; + } + + const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init" }); + init_exe.setCwd(.{ .cwd_relative = tmp_path }); + init_exe.setName("zig init"); + init_exe.expectStdOutEqual(""); + init_exe.expectStdErrEqual("info: created build.zig\n" ++ + "info: created build.zig.zon\n" ++ + "info: preserving already existing file: src" ++ s ++ "main.zig\n" ++ + "info: created src" ++ s ++ "root.zig\n"); + + const run_test_bad = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "test", "--color", "off" }); + run_test_bad.setCwd(.{ .cwd_relative = tmp_path }); + run_test_bad.setName("zig build test"); + run_test_bad.expectExitCode(1); + run_test_bad.expectStdOutEqual(""); + run_test_bad.addCheck(deprecated_check); + run_test_bad.step.dependOn(&init_exe.step); + + const run_test = b.addSystemCommand(&.{ + b.graph.zig_exe, + "build", + "test", + "--color", + "off", + "-fallow-deprecated", + }); + run_test.setCwd(.{ .cwd_relative = tmp_path }); + run_test.setName("zig build test"); + run_test.expectExitCode(0); + run_test.expectStdOutEqual(""); + run_test.expectStdErrEqual(""); + run_test.step.dependOn(&init_exe.step); + + const run_build_bad = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "--color", "off" }); + run_build_bad.setCwd(.{ .cwd_relative = tmp_path }); + run_build_bad.setName("zig build test"); + run_build_bad.expectExitCode(1); + run_build_bad.expectStdOutEqual(""); + run_build_bad.addCheck(deprecated_check); + run_build_bad.step.dependOn(&init_exe.step); + + const run_build = b.addSystemCommand(&.{ + b.graph.zig_exe, + "build", + "--color", + "off", + "-fallow-deprecated", + }); + run_build.setCwd(.{ .cwd_relative = tmp_path }); + run_build.setName("zig build test"); + run_build.expectExitCode(0); + run_build.expectStdOutEqual(""); + run_build.expectStdErrEqual(""); + run_build.step.dependOn(&init_exe.step); + + const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); + cleanup.step.dependOn(&run_test.step); + cleanup.step.dependOn(&run_test_bad.step); + cleanup.step.dependOn(&run_build.step); + cleanup.step.dependOn(&run_build_bad.step); + + step.dependOn(&cleanup.step); + } // Test Godbolt API if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) { const tmp_path = b.makeTempPath(); From 466fa311b18e7178921f6e37d6b0244b3c707b5d Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Mon, 17 Feb 2025 17:37:10 +0100 Subject: [PATCH 05/12] `@deprecated`: optimize sema implementation mlugg suggested a better way of implementing analysis of an istruction that cannot be referenced by other instructions. --- src/Sema.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sema.zig b/src/Sema.zig index c53369bffc..9e4c4d6bd2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1412,7 +1412,8 @@ fn analyzeBodyInner( return sema.fail(block, src, "found deprecated code", .{}); } - break :ext .void_value; + i += 1; + continue; }, .disable_instrumentation => { try sema.zirDisableInstrumentation(); From e3da2852f421ec2e7d568373b3f29030818e1d77 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Sun, 23 Feb 2025 17:57:28 +0100 Subject: [PATCH 06/12] `@deprecated`: add suggested changes to langref entry --- doc/langref.html.in | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index cf9aa4fb93..058a137e74 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4746,19 +4746,18 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}@deprecated() void{#endsyntax#}

Used to mark a given code path as deprecated. It evaluates to the same value - passed in as argument, or the {#syntax#}void{#endsyntax#} value when given none. + passed in as argument, or the void value when given none.

- As an example, in Zig 0.14.0 {#syntax#}std.time.sleep{#endsyntax#} was - deprecated and the sleep function was moved to {#syntax#}std.Thread.sleep{#endsyntax#}. - This is how this deprecation could have been expressed: - - {#syntax_block|zig|lib/std/time.zig#} -pub const sleep = @deprecated(std.Thread.sleep); // moved - {#end_syntax_block#} + As an example, a library that wishes to move or rename a declaration, while + deprecating usage of the old name can use {#syntax#}@deprecated{#endsyntax#} like so:

+ {#syntax_block|zig|root.zig#} +pub const fooToBar = @deprecated(bar.fromFoo); // moved + {#end_syntax_block#} +

- By default it is a compile error to depend on deprecated code in + By default it is a compile error to reference deprecated code in a module defined by the root package, while it is not in modules defined by dependencies. This behavior can be overridden for the entire dependency tree by passing {#syntax#}-fallow-deprecated{#endsyntax#} or @@ -4772,9 +4771,9 @@ pub const sleep = @deprecated(std.Thread.sleep); // moved

- {#syntax#}@deprecated{#endsyntax#} can also be used without argument: - {#code|test_deprecated_builtin.zig#} + Using {#syntax#}@deprecated{#endsyntax#} without an argument can be useful inside of blocks:

+ {#code|test_deprecated_builtin.zig#} {#header_close#} From 25790e95f1e8563f439bb13c460fdd4a46e68c43 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Sun, 23 Feb 2025 18:15:27 +0100 Subject: [PATCH 07/12] `@deprecated`: remove per-module flag in Build This implementation looks at the builder of each module in the build graph instead of storing a boolean for each module. --- lib/std/Build.zig | 2 +- lib/std/Build/Module.zig | 5 ++--- lib/std/zig/Zir.zig | 2 +- test/cases/compile_errors/deprecated.zig | 9 +++++++++ test/compile_errors.zig | 14 -------------- test/tests.zig | 6 +++--- 6 files changed, 16 insertions(+), 22 deletions(-) create mode 100644 test/cases/compile_errors/deprecated.zig diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 0ba5c43a46..1add40d5df 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -94,7 +94,7 @@ available_deps: AvailableDeps, release_mode: ReleaseMode, -// True only for the top-level builder. +/// `true` only for the root `Build`; `false` for any `Build` belonging to a dependency. is_root: bool = false, pub const ReleaseMode = enum { diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index d93fe84416..8a8b9573e4 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -25,7 +25,6 @@ stack_check: ?bool, sanitize_c: ?bool, sanitize_thread: ?bool, fuzz: ?bool, -allow_deprecated: ?bool, code_model: std.builtin.CodeModel, valgrind: ?bool, pic: ?bool, @@ -285,7 +284,6 @@ pub fn init( .owner = owner, .root_source_file = if (options.root_source_file) |lp| lp.dupe(owner) else null, .import_table = .{}, - .allow_deprecated = owner.graph.allow_deprecated orelse !owner.is_root, .resolved_target = options.target, .optimize = options.optimize, .link_libc = options.link_libc, @@ -560,7 +558,8 @@ pub fn appendZigProcessFlags( try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); if (m.root_source_file != null) { - try addFlag(zig_args, m.allow_deprecated, "-fallow-deprecated", "-fno-allow-deprecated"); + const allow_deprecated = m.owner.graph.allow_deprecated orelse !m.owner.is_root; + try addFlag(zig_args, allow_deprecated, "-fallow-deprecated", "-fno-allow-deprecated"); } if (m.dwarf_format) |dwarf_format| { diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index ea69bc2da0..2939f5be65 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -4313,7 +4313,6 @@ fn findTrackableInner( .value_placeholder => unreachable, // Once again, we start with the boring tags. - .deprecated, .this, .ret_addr, .builtin_src, @@ -4367,6 +4366,7 @@ fn findTrackableInner( .tuple_decl, .dbg_empty_stmt, .astgen_error, + .deprecated, => return, // `@TypeOf` has a body. diff --git a/test/cases/compile_errors/deprecated.zig b/test/cases/compile_errors/deprecated.zig new file mode 100644 index 0000000000..fdfabc1a5b --- /dev/null +++ b/test/cases/compile_errors/deprecated.zig @@ -0,0 +1,9 @@ +const bad = @deprecated(42); + +pub export fn foo() usize { + return bad; +} + +// error +// +// :1:13: error: found deprecated code diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 03e9324b02..109347cde8 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -250,18 +250,4 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { ":1:5: error: expected expression, found 'invalid token'", }); } - - { - const case = ctx.obj("usage of deprecated code", b.graph.host); - - case.addError( - \\const bad = @deprecated(42); - \\ - \\pub export fn foo() usize { - \\ return bad; - \\} - , &[_][]const u8{ - ":1:13: error: found deprecated code", - }); - } } diff --git a/test/tests.zig b/test/tests.zig index 45cba01c7f..1bf6741206 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1199,13 +1199,13 @@ pub fn addCliTests(b: *std.Build) *Step { \\} ; - var src_dir = std.fs.cwd().makeOpenPath(b.pathJoin(&.{ tmp_path, "src" }), .{}) catch unreachable; + var src_dir = std.fs.cwd().makeOpenPath(b.pathJoin(&.{ tmp_path, "src" }), .{}) catch @panic("unable to create tmp path"); defer src_dir.close(); - var main = src_dir.createFile("main.zig", .{}) catch unreachable; + var main = src_dir.createFile("main.zig", .{}) catch @panic("unable to create main.zig"); defer main.close(); - main.writeAll(new_main_src) catch unreachable; + main.writeAll(new_main_src) catch @panic("unable to write to main.zig"); } const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init" }); From 7c2649f89dc76afca46c3aa5c675719b647cdd10 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 25 Feb 2025 22:42:04 -0800 Subject: [PATCH 08/12] langref: fix whitespace --- doc/langref.html.in | 58 ++++++++++++++++++++++----------------------- test/tests.zig | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 058a137e74..b54ba4b152 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2288,7 +2288,7 @@ or {#code|test_aligned_struct_fields.zig#}

- Equating packed structs results in a comparison of the backing integer, + Equating packed structs results in a comparison of the backing integer, and only works for the `==` and `!=` operators.

{#code|test_packed_struct_equality.zig#} @@ -4086,7 +4086,7 @@ fn performFn(start_value: i32) i32 { special-case syntax.

- Here is an example of a generic {#syntax#}List{#endsyntax#} data structure. + Here is an example of a generic {#syntax#}List{#endsyntax#} data structure.

{#code|generic_data_structure.zig#} @@ -4291,10 +4291,10 @@ pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
{#syntax#}@addrSpaceCast(ptr: anytype) anytype{#endsyntax#}

Converts a pointer from one address space to another. The new address space is inferred - based on the result type. Depending on the current target and address spaces, this cast - may be a no-op, a complex operation, or illegal. If the cast is legal, then the resulting - pointer points to the same memory location as the pointer operand. It is always valid to - cast a pointer between the same address spaces. + based on the result type. Depending on the current target and address spaces, this cast + may be a no-op, a complex operation, or illegal. If the cast is legal, then the resulting + pointer points to the same memory location as the pointer operand. It is always valid to + cast a pointer between the same address spaces.

{#header_close#} {#header_open|@addWithOverflow#} @@ -4307,7 +4307,7 @@ pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
{#syntax#}@alignCast(ptr: anytype) anytype{#endsyntax#}

{#syntax#}ptr{#endsyntax#} can be {#syntax#}*T{#endsyntax#}, {#syntax#}?*T{#endsyntax#}, or {#syntax#}[]T{#endsyntax#}. - Changes the alignment of a pointer. The alignment to use is inferred based on the result type. + Changes the alignment of a pointer. The alignment to use is inferred based on the result type.

A {#link|pointer alignment safety check|Incorrect Pointer Alignment#} is added to the generated code to make sure the pointer is aligned as promised.

@@ -4384,7 +4384,7 @@ comptime {
{#syntax#}@bitCast(value: anytype) anytype{#endsyntax#}

Converts a value of one type to another type. The return type is the - inferred result type. + inferred result type.

Asserts that {#syntax#}@sizeOf(@TypeOf(value)) == @sizeOf(DestType){#endsyntax#}. @@ -4740,41 +4740,41 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

{#see_also|@cVaArg|@cVaCopy|@cVaEnd#} {#header_close#} - + {#header_open|@deprecated#}
{#syntax#}@deprecated(value: anytype) @TypeOf(value){#endsyntax#}
{#syntax#}@deprecated() void{#endsyntax#}

- Used to mark a given code path as deprecated. It evaluates to the same value - passed in as argument, or the void value when given none. + Used to mark a given code path as deprecated. It evaluates to the same value + passed in as argument, or the void value when given none.

As an example, a library that wishes to move or rename a declaration, while - deprecating usage of the old name can use {#syntax#}@deprecated{#endsyntax#} like so: -

+ deprecating usage of the old name can use {#syntax#}@deprecated{#endsyntax#} like so: +

{#syntax_block|zig|root.zig#} pub const fooToBar = @deprecated(bar.fromFoo); // moved {#end_syntax_block#}

By default it is a compile error to reference deprecated code in - a module defined by the root package, while it is not in modules defined - by dependencies. This behavior can be overridden for the entire dependency - tree by passing {#syntax#}-fallow-deprecated{#endsyntax#} or - {#syntax#}-fno-allow-deprecated{#endsyntax#} to {#syntax#}zig build{#endsyntax#}. + a module defined by the root package, while it is not in modules defined + by dependencies. This behavior can be overridden for the entire dependency + tree by passing {#syntax#}-fallow-deprecated{#endsyntax#} or + {#syntax#}-fno-allow-deprecated{#endsyntax#} to {#syntax#}zig build{#endsyntax#}.

- Usage of this builtin is meant to help direct consumers discover (and remove) - their dependance on deprecated code during the grace period before a deprecated - functionality is turned into a {#syntax#}@compileError{#endsyntax#} or - removed entirely. + Usage of this builtin is meant to help direct consumers discover (and remove) + their dependance on deprecated code during the grace period before a deprecated + functionality is turned into a {#syntax#}@compileError{#endsyntax#} or + removed entirely.

-

+

Using {#syntax#}@deprecated{#endsyntax#} without an argument can be useful inside of blocks: -

+

{#code|test_deprecated_builtin.zig#} - + {#header_close#} {#header_open|@divExact#} @@ -4891,8 +4891,8 @@ pub const fooToBar = @deprecated(bar.fromFoo); // moved
{#syntax#}@errorCast(value: anytype) anytype{#endsyntax#}

Converts an error set or error union value from one error set to another error set. The return type is the - inferred result type. Attempting to convert an error which is not in the destination error - set results in safety-checked {#link|Illegal Behavior#}. + inferred result type. Attempting to convert an error which is not in the destination error + set results in safety-checked {#link|Illegal Behavior#}.

{#header_close#} @@ -4971,7 +4971,7 @@ pub const fooToBar = @deprecated(bar.fromFoo); // moved
{#syntax#}@floatFromInt(int: anytype) anytype{#endsyntax#}

Converts an integer to the closest floating point representation. The return type is the inferred result type. - To convert the other way, use {#link|@intFromFloat#}. This operation is legal + To convert the other way, use {#link|@intFromFloat#}. This operation is legal for all values of all integer types.

{#header_close#} @@ -5063,7 +5063,7 @@ pub const fooToBar = @deprecated(bar.fromFoo); // moved
{#syntax#}@intCast(int: anytype) anytype{#endsyntax#}

Converts an integer to another integer while keeping the same numerical value. - The return type is the inferred result type. + The return type is the inferred result type. Attempting to convert a number which is out of range of the destination type results in safety-checked {#link|Illegal Behavior#}.

@@ -5316,7 +5316,7 @@ pub const fooToBar = @deprecated(bar.fromFoo); // moved
{#syntax#}@ptrFromInt(address: usize) anytype{#endsyntax#}

Converts an integer to a {#link|pointer|Pointers#}. The return type is the inferred result type. - To convert the other way, use {#link|@intFromPtr#}. Casting an address of 0 to a destination type + To convert the other way, use {#link|@intFromPtr#}. Casting an address of 0 to a destination type which in not {#link|optional|Optional Pointers#} and does not have the {#syntax#}allowzero{#endsyntax#} attribute will result in a {#link|Pointer Cast Invalid Null#} panic when runtime safety checks are enabled.

diff --git a/test/tests.zig b/test/tests.zig index 1bf6741206..e8bbca2963 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1195,7 +1195,7 @@ pub fn addCliTests(b: *std.Build) *Step { \\} \\ \\test { - \\ if (bad != 42) return error.Bad; + \\ if (bad != 42) return error.Bad; \\} ; From 4ddb13468b372b2f722703b5e6d60997776c251a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 25 Feb 2025 23:02:24 -0800 Subject: [PATCH 09/12] langref: update deprecated section --- doc/langref.html.in | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index b54ba4b152..1aff0f2c6d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4745,36 +4745,36 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}@deprecated(value: anytype) @TypeOf(value){#endsyntax#}
{#syntax#}@deprecated() void{#endsyntax#}

- Used to mark a given code path as deprecated. It evaluates to the same value - passed in as argument, or the void value when given none. + Marks a given code path as scheduled for removal. Evaluates to the same + value passed in as argument, or the {#syntax#}void{#endsyntax#} value + when given none.

- As an example, a library that wishes to move or rename a declaration, while - deprecating usage of the old name can use {#syntax#}@deprecated{#endsyntax#} like so: + When a public declaration has been moved to a new location, the old + location can be marked {#syntax#}@deprecated{#endsyntax#}:

{#syntax_block|zig|root.zig#} pub const fooToBar = @deprecated(bar.fromFoo); // moved {#end_syntax_block#} -

- By default it is a compile error to reference deprecated code in - a module defined by the root package, while it is not in modules defined - by dependencies. This behavior can be overridden for the entire dependency - tree by passing {#syntax#}-fallow-deprecated{#endsyntax#} or - {#syntax#}-fno-allow-deprecated{#endsyntax#} to {#syntax#}zig build{#endsyntax#}. + By default deprecated code paths are disallowed in a module defined by + the root package but allowed in modules defined by the rest of the + dependency tree. This behavior can be overridden by passing + -fallow-deprecated or -fno-allow-deprecated to + zig build.

- Usage of this builtin is meant to help direct consumers discover (and remove) - their dependance on deprecated code during the grace period before a deprecated - functionality is turned into a {#syntax#}@compileError{#endsyntax#} or - removed entirely. + The purpose of {#syntax#}@deprecated{#endsyntax#} is to provide at least + one version (a "grace period") of a package that supports both old and new APIs + simultaneously, while providing tooling for programmers to discover what needs + to be upgraded to migrate to the new API. Such a grace period has the key property + that it allows a project's dependency tree to be upgraded one package at a time.

-

- Using {#syntax#}@deprecated{#endsyntax#} without an argument can be useful inside of blocks: + Using {#syntax#}@deprecated{#endsyntax#} without an argument can be + useful inside of conditionally compiled blocks:

{#code|test_deprecated_builtin.zig#} - {#header_close#} {#header_open|@divExact#} From c5aa680c88f7c03718460ff38e7cb1f7c5482cf2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 25 Feb 2025 23:50:56 -0800 Subject: [PATCH 10/12] don't inherit allowed deprecation from parent modules Inheriting allow-deprecation from parent modules doesn't make too much sense, so instead make them default to disallow unless otherwise specified. This allows build system to avoid redundant `-fno-allow-deprecated` args. This makes the generated CLIs smaller, and makes zig1.wasm update not needed. Also represented `is_root` differently (moved to field of graph). --- lib/compiler/build_runner.zig | 2 ++ lib/std/Build.zig | 7 ++----- lib/std/Build/Module.zig | 7 +++---- src/Package/Module.zig | 1 - 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 114a9e3acf..f159863ab6 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -80,6 +80,7 @@ pub fn main() !void { .query = .{}, .result = try std.zig.system.resolveTargetQuery(.{}), }, + .root_builder = undefined, // populated below }; graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); @@ -94,6 +95,7 @@ pub fn main() !void { local_cache_directory, dependencies.root_deps, ); + graph.root_builder = builder; var targets = ArrayList([]const u8).init(arena); var debug_log_scopes = ArrayList([]const u8).init(arena); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 1add40d5df..4f3364bb19 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -94,9 +94,6 @@ available_deps: AvailableDeps, release_mode: ReleaseMode, -/// `true` only for the root `Build`; `false` for any `Build` belonging to a dependency. -is_root: bool = false, - pub const ReleaseMode = enum { off, any, @@ -121,10 +118,11 @@ pub const Graph = struct { /// Information about the native target. Computed before build() is invoked. host: ResolvedTarget, incremental: ?bool = null, - allow_deprecated: ?bool = null, random_seed: u32 = 0, dependency_cache: InitializedDepMap = .empty, allow_so_scripts: ?bool = null, + allow_deprecated: ?bool = null, + root_builder: *std.Build, }; const AvailableDeps = []const struct { []const u8, []const u8 }; @@ -308,7 +306,6 @@ pub fn create( .pkg_hash = "", .available_deps = available_deps, .release_mode = .off, - .is_root = true, }; try b.top_level_steps.put(arena, b.install_tls.step.name, &b.install_tls); try b.top_level_steps.put(arena, b.uninstall_tls.step.name, &b.uninstall_tls); diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 8a8b9573e4..40b9a5e619 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -557,10 +557,9 @@ pub fn appendZigProcessFlags( try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC"); try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); - if (m.root_source_file != null) { - const allow_deprecated = m.owner.graph.allow_deprecated orelse !m.owner.is_root; - try addFlag(zig_args, allow_deprecated, "-fallow-deprecated", "-fno-allow-deprecated"); - } + // -fno-allow-deprecated is the CLI default, and not inherited, so only pass the flag if true. + const allow_deprecated = m.owner.graph.allow_deprecated orelse (m.owner.graph.root_builder != m.owner); + if (allow_deprecated == true) try zig_args.append("-fallow-deprecated"); if (m.dwarf_format) |dwarf_format| { try zig_args.append(switch (dwarf_format) { diff --git a/src/Package/Module.zig b/src/Package/Module.zig index 6bd33dffa1..c5aa21a105 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -238,7 +238,6 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { const allow_deprecated = b: { if (options.inherited.allow_deprecated) |x| break :b x; - if (options.parent) |p| break :b p.allow_deprecated; break :b false; }; From f74a856d84af4c43057febc0a871caa0e69bbfc8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 25 Feb 2025 23:59:48 -0800 Subject: [PATCH 11/12] reword deprecated error slightly "found" -> "reached" to match "reached unreachable code" --- src/Sema.zig | 2 +- test/cases/compile_errors/deprecated.zig | 2 +- test/tests.zig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 9e4c4d6bd2..2d5407532a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1409,7 +1409,7 @@ fn analyzeBodyInner( if (!mod.allow_deprecated) { const src_node: i32 = @bitCast(extended.operand); const src = block.nodeOffset(src_node); - return sema.fail(block, src, "found deprecated code", .{}); + return sema.fail(block, src, "reached deprecated code", .{}); } i += 1; diff --git a/test/cases/compile_errors/deprecated.zig b/test/cases/compile_errors/deprecated.zig index fdfabc1a5b..98921a0742 100644 --- a/test/cases/compile_errors/deprecated.zig +++ b/test/cases/compile_errors/deprecated.zig @@ -6,4 +6,4 @@ pub export fn foo() usize { // error // -// :1:13: error: found deprecated code +// :1:13: error: reached deprecated code diff --git a/test/tests.zig b/test/tests.zig index e8bbca2963..694b0002f4 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1180,7 +1180,7 @@ pub fn addCliTests(b: *std.Build) *Step { // Test `zig build -fallow-deprecated`. const deprecated_check: std.Build.Step.Run.StdIo.Check = .{ - .expect_stderr_match = "found deprecated code", + .expect_stderr_match = "reached deprecated code", }; const tmp_path = b.makeTempPath(); From 43a949ee95ce72003bb2d2ee22e1f4b5aea96739 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Wed, 26 Feb 2025 11:33:54 +0100 Subject: [PATCH 12/12] fix regressed build system unit test --- lib/std/Build/Step/Options.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index dd09c0b5c0..fda358ee37 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -514,6 +514,7 @@ test Options { .result = try std.zig.system.resolveTargetQuery(.{}), }, .zig_lib_directory = std.Build.Cache.Directory.cwd(), + .root_builder = undefined, }; var builder = try std.Build.create( @@ -523,6 +524,8 @@ test Options { &.{}, ); + graph.root_builder = builder; + const options = builder.addOptions(); const KeywordEnum = enum {