Merge pull request #22898 from kristoff-it/deprecated-proposal

Implement `@deprecated`
This commit is contained in:
Andrew Kelley 2025-02-27 01:31:09 -05:00 committed by GitHub
commit dea72d15da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 251 additions and 15 deletions

View File

@ -4741,6 +4741,42 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#see_also|@cVaArg|@cVaCopy|@cVaEnd#} {#see_also|@cVaArg|@cVaCopy|@cVaEnd#}
{#header_close#} {#header_close#}
{#header_open|@deprecated#}
<pre>{#syntax#}@deprecated(value: anytype) @TypeOf(value){#endsyntax#}</pre>
<pre>{#syntax#}@deprecated() void{#endsyntax#}</pre>
<p>
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.
</p>
<p>
When a public declaration has been moved to a new location, the old
location can be marked {#syntax#}@deprecated{#endsyntax#}:
</p>
{#syntax_block|zig|root.zig#}
pub const fooToBar = @deprecated(bar.fromFoo); // moved
{#end_syntax_block#}
<p>
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
<code>-fallow-deprecated</code> or <code>-fno-allow-deprecated</code> to
<code>zig build</code>.
</p>
<p>
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 <em>one package at a time</em>.
</p>
<p>
Using {#syntax#}@deprecated{#endsyntax#} without an argument can be
useful inside of conditionally compiled blocks:
</p>
{#code|test_deprecated_builtin.zig#}
{#header_close#}
{#header_open|@divExact#} {#header_open|@divExact#}
<pre>{#syntax#}@divExact(numerator: T, denominator: T) T{#endsyntax#}</pre> <pre>{#syntax#}@divExact(numerator: T, denominator: T) T{#endsyntax#}</pre>
<p> <p>

View File

@ -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

View File

@ -80,6 +80,7 @@ pub fn main() !void {
.query = .{}, .query = .{},
.result = try std.zig.system.resolveTargetQuery(.{}), .result = try std.zig.system.resolveTargetQuery(.{}),
}, },
.root_builder = undefined, // populated below
}; };
graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
@ -94,6 +95,7 @@ pub fn main() !void {
local_cache_directory, local_cache_directory,
dependencies.root_deps, dependencies.root_deps,
); );
graph.root_builder = builder;
var targets = ArrayList([]const u8).init(arena); var targets = ArrayList([]const u8).init(arena);
var debug_log_scopes = ArrayList([]const u8).init(arena); var debug_log_scopes = ArrayList([]const u8).init(arena);
@ -260,6 +262,10 @@ pub fn main() !void {
graph.incremental = true; graph.incremental = true;
} else if (mem.eql(u8, arg, "-fno-incremental")) { } else if (mem.eql(u8, arg, "-fno-incremental")) {
graph.incremental = false; 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")) { } else if (mem.eql(u8, arg, "-fwine")) {
builder.enable_wine = true; builder.enable_wine = true;
} else if (mem.eql(u8, arg, "-fno-wine")) { } else if (mem.eql(u8, arg, "-fno-wine")) {
@ -1290,6 +1296,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
\\ new Omit cached steps \\ new Omit cached steps
\\ failures (Default) Only print failed steps \\ failures (Default) Only print failed steps
\\ none Do not print the build summary \\ 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<N> Limit concurrent jobs (default is to use all CPU cores) \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
\\ --maxrss <bytes> Limit memory usage (default is to use available memory) \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
\\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss

View File

@ -121,6 +121,8 @@ pub const Graph = struct {
random_seed: u32 = 0, random_seed: u32 = 0,
dependency_cache: InitializedDepMap = .empty, dependency_cache: InitializedDepMap = .empty,
allow_so_scripts: ?bool = null, allow_so_scripts: ?bool = null,
allow_deprecated: ?bool = null,
root_builder: *std.Build,
}; };
const AvailableDeps = []const struct { []const u8, []const u8 }; const AvailableDeps = []const struct { []const u8, []const u8 };

View File

@ -557,6 +557,10 @@ pub fn appendZigProcessFlags(
try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC"); try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");
try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");
// -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| { if (m.dwarf_format) |dwarf_format| {
try zig_args.append(switch (dwarf_format) { try zig_args.append(switch (dwarf_format) {
.@"32" => "-gdwarf32", .@"32" => "-gdwarf32",

View File

@ -514,6 +514,7 @@ test Options {
.result = try std.zig.system.resolveTargetQuery(.{}), .result = try std.zig.system.resolveTargetQuery(.{}),
}, },
.zig_lib_directory = std.Build.Cache.Directory.cwd(), .zig_lib_directory = std.Build.Cache.Directory.cwd(),
.root_builder = undefined,
}; };
var builder = try std.Build.create( var builder = try std.Build.create(
@ -523,6 +524,8 @@ test Options {
&.{}, &.{},
); );
graph.root_builder = builder;
const options = builder.addOptions(); const options = builder.addOptions();
const KeywordEnum = enum { const KeywordEnum = enum {

View File

@ -9695,6 +9695,19 @@ fn builtinCall(
.volatile_cast, .volatile_cast,
=> return ptrCast(gz, scope, ri, node), => 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 // zig fmt: off
.has_decl => return hasDeclOrField(gz, scope, ri, node, params[0], params[1], .has_decl), .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), .has_field => return hasDeclOrField(gz, scope, ri, node, params[0], params[1], .has_field),

View File

@ -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 { 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 tree = astrl.tree;
const main_tokens = tree.nodes.items(.main_token); const main_tokens = tree.nodes.items(.main_token);
const builtin_token = main_tokens[node]; 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; if (expected != args.len) return false;
} }
switch (info.tag) { switch (info.tag) {
.deprecated => if (args.len >= 1) {
return astrl.expr(args[0], block, ri);
} else {
return false;
},
.import => return false, .import => return false,
.branch_hint => { .branch_hint => {
_ = try astrl.expr(args[0], block, ResultInfo.type_only); _ = try astrl.expr(args[0], block, ResultInfo.type_only);

View File

@ -121,6 +121,7 @@ pub const Tag = enum {
work_item_id, work_item_id,
work_group_size, work_group_size,
work_group_id, work_group_id,
deprecated,
}; };
pub const EvalToError = enum { pub const EvalToError = enum {
@ -1016,6 +1017,14 @@ pub const list = list: {
.illegal_outside_function = true, .illegal_outside_function = true,
}, },
}, },
.{
"@deprecated",
.{
.tag = .deprecated,
.param_count = null,
.eval_to_error = .maybe,
},
},
}); });
}; };

View File

@ -2112,6 +2112,9 @@ pub const Inst = struct {
/// any code may have gone here, avoiding false-positive "unreachable code" errors. /// any code may have gone here, avoiding false-positive "unreachable code" errors.
astgen_error, astgen_error,
/// `operand` is `src_node: i32`.
deprecated,
pub const InstData = struct { pub const InstData = struct {
opcode: Extended, opcode: Extended,
small: u16, small: u16,
@ -4363,6 +4366,7 @@ fn findTrackableInner(
.tuple_decl, .tuple_decl,
.dbg_empty_stmt, .dbg_empty_stmt,
.astgen_error, .astgen_error,
.deprecated,
=> return, => return,
// `@TypeOf` has a body. // `@TypeOf` has a body.

View File

@ -869,6 +869,7 @@ pub const cache_helpers = struct {
hh.add(mod.sanitize_c); hh.add(mod.sanitize_c);
hh.add(mod.sanitize_thread); hh.add(mod.sanitize_thread);
hh.add(mod.fuzz); hh.add(mod.fuzz);
hh.add(mod.allow_deprecated);
hh.add(mod.unwind_tables); hh.add(mod.unwind_tables);
hh.add(mod.structured_cfg); hh.add(mod.structured_cfg);
hh.add(mod.no_builtin); hh.add(mod.no_builtin);

View File

@ -27,6 +27,7 @@ red_zone: bool,
sanitize_c: bool, sanitize_c: bool,
sanitize_thread: bool, sanitize_thread: bool,
fuzz: bool, fuzz: bool,
allow_deprecated: bool,
unwind_tables: std.builtin.UnwindTables, unwind_tables: std.builtin.UnwindTables,
cc_argv: []const []const u8, cc_argv: []const []const u8,
/// (SPIR-V) whether to generate a structured control flow graph or not /// (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_c: ?bool = null,
sanitize_thread: ?bool = null, sanitize_thread: ?bool = null,
fuzz: ?bool = null, fuzz: ?bool = null,
allow_deprecated: ?bool = null,
structured_cfg: ?bool = null, structured_cfg: ?bool = null,
no_builtin: ?bool = null, no_builtin: ?bool = null,
}; };
@ -234,6 +236,11 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
break :b false; break :b false;
}; };
const allow_deprecated = b: {
if (options.inherited.allow_deprecated) |x| break :b x;
break :b false;
};
const code_model = b: { const code_model = b: {
if (options.inherited.code_model) |x| break :b x; if (options.inherited.code_model) |x| break :b x;
if (options.parent) |p| break :b p.code_model; if (options.parent) |p| break :b p.code_model;
@ -380,6 +387,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
.sanitize_c = sanitize_c, .sanitize_c = sanitize_c,
.sanitize_thread = sanitize_thread, .sanitize_thread = sanitize_thread,
.fuzz = fuzz, .fuzz = fuzz,
.allow_deprecated = allow_deprecated,
.unwind_tables = unwind_tables, .unwind_tables = unwind_tables,
.cc_argv = options.cc_argv, .cc_argv = options.cc_argv,
.structured_cfg = structured_cfg, .structured_cfg = structured_cfg,
@ -474,6 +482,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
.sanitize_c = sanitize_c, .sanitize_c = sanitize_c,
.sanitize_thread = sanitize_thread, .sanitize_thread = sanitize_thread,
.fuzz = fuzz, .fuzz = fuzz,
.allow_deprecated = allow_deprecated,
.unwind_tables = unwind_tables, .unwind_tables = unwind_tables,
.cc_argv = &.{}, .cc_argv = &.{},
.structured_cfg = structured_cfg, .structured_cfg = structured_cfg,
@ -532,6 +541,7 @@ pub fn createLimited(gpa: Allocator, options: LimitedOptions) Allocator.Error!*P
.sanitize_c = undefined, .sanitize_c = undefined,
.sanitize_thread = undefined, .sanitize_thread = undefined,
.fuzz = undefined, .fuzz = undefined,
.allow_deprecated = undefined,
.unwind_tables = undefined, .unwind_tables = undefined,
.cc_argv = undefined, .cc_argv = undefined,
.structured_cfg = undefined, .structured_cfg = undefined,

View File

@ -1091,6 +1091,7 @@ fn analyzeBodyInner(
const map = &sema.inst_map; const map = &sema.inst_map;
const tags = sema.code.instructions.items(.tag); const tags = sema.code.instructions.items(.tag);
const datas = sema.code.instructions.items(.data); const datas = sema.code.instructions.items(.data);
const mod = block.ownerModule();
var crash_info = crash_report.prepAnalyzeBody(sema, block, body); var crash_info = crash_report.prepAnalyzeBody(sema, block, body);
crash_info.push(); crash_info.push();
@ -1404,6 +1405,16 @@ fn analyzeBodyInner(
i += 1; i += 1;
continue; continue;
}, },
.deprecated => {
if (!mod.allow_deprecated) {
const src_node: i32 = @bitCast(extended.operand);
const src = block.nodeOffset(src_node);
return sema.fail(block, src, "reached deprecated code", .{});
}
i += 1;
continue;
},
.disable_instrumentation => { .disable_instrumentation => {
try sema.zirDisableInstrumentation(); try sema.zirDisableInstrumentation();
i += 1; i += 1;

View File

@ -520,6 +520,8 @@ const usage_build_generic =
\\ -fno-sanitize-thread Disable Thread Sanitizer \\ -fno-sanitize-thread Disable Thread Sanitizer
\\ -ffuzz Enable fuzz testing instrumentation \\ -ffuzz Enable fuzz testing instrumentation
\\ -fno-fuzz Disable 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 \\ -funwind-tables Always produce unwind table entries for all functions
\\ -fasync-unwind-tables Always produce asynchronous 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 \\ -fno-unwind-tables Never produce unwind table entries
@ -1454,6 +1456,10 @@ fn buildOutputType(
mod_opts.fuzz = true; mod_opts.fuzz = true;
} else if (mem.eql(u8, arg, "-fno-fuzz")) { } else if (mem.eql(u8, arg, "-fno-fuzz")) {
mod_opts.fuzz = false; 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")) { } else if (mem.eql(u8, arg, "-fllvm")) {
create_module.opts.use_llvm = true; create_module.opts.use_llvm = true;
} else if (mem.eql(u8, arg, "-fno-llvm")) { } else if (mem.eql(u8, arg, "-fno-llvm")) {

View File

@ -535,6 +535,7 @@ const Writer = struct {
.c_va_start, .c_va_start,
.in_comptime, .in_comptime,
.value_placeholder, .value_placeholder,
.deprecated,
=> try self.writeExtNode(stream, extended), => try self.writeExtNode(stream, extended),
.builtin_src => { .builtin_src => {

View File

@ -0,0 +1,9 @@
const bad = @deprecated(42);
pub export fn foo() usize {
return bad;
}
// error
//
// :1:13: error: reached deprecated code

View File

@ -1176,6 +1176,100 @@ pub fn addCliTests(b: *std.Build) *Step {
step.dependOn(&cleanup.step); step.dependOn(&cleanup.step);
} }
{
// Test `zig build -fallow-deprecated`.
const deprecated_check: std.Build.Step.Run.StdIo.Check = .{
.expect_stderr_match = "reached 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 @panic("unable to create tmp path");
defer src_dir.close();
var main = src_dir.createFile("main.zig", .{}) catch @panic("unable to create main.zig");
defer main.close();
main.writeAll(new_main_src) catch @panic("unable to write to main.zig");
}
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 // Test Godbolt API
if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) { if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
const tmp_path = b.makeTempPath(); const tmp_path = b.makeTempPath();