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

@ -2288,7 +2288,7 @@ or
{#code|test_aligned_struct_fields.zig#} {#code|test_aligned_struct_fields.zig#}
<p> <p>
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. and only works for the `==` and `!=` operators.
</p> </p>
{#code|test_packed_struct_equality.zig#} {#code|test_packed_struct_equality.zig#}
@ -4086,7 +4086,7 @@ fn performFn(start_value: i32) i32 {
special-case syntax. special-case syntax.
</p> </p>
<p> <p>
Here is an example of a generic {#syntax#}List{#endsyntax#} data structure. Here is an example of a generic {#syntax#}List{#endsyntax#} data structure.
</p> </p>
{#code|generic_data_structure.zig#} {#code|generic_data_structure.zig#}
@ -4291,10 +4291,10 @@ pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
<pre>{#syntax#}@addrSpaceCast(ptr: anytype) anytype{#endsyntax#}</pre> <pre>{#syntax#}@addrSpaceCast(ptr: anytype) anytype{#endsyntax#}</pre>
<p> <p>
Converts a pointer from one address space to another. The new address space is inferred 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 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 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 pointer points to the same memory location as the pointer operand. It is always valid to
cast a pointer between the same address spaces. cast a pointer between the same address spaces.
</p> </p>
{#header_close#} {#header_close#}
{#header_open|@addWithOverflow#} {#header_open|@addWithOverflow#}
@ -4307,7 +4307,7 @@ pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
<pre>{#syntax#}@alignCast(ptr: anytype) anytype{#endsyntax#}</pre> <pre>{#syntax#}@alignCast(ptr: anytype) anytype{#endsyntax#}</pre>
<p> <p>
{#syntax#}ptr{#endsyntax#} can be {#syntax#}*T{#endsyntax#}, {#syntax#}?*T{#endsyntax#}, or {#syntax#}[]T{#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.
</p> </p>
<p>A {#link|pointer alignment safety check|Incorrect Pointer Alignment#} is added <p>A {#link|pointer alignment safety check|Incorrect Pointer Alignment#} is added
to the generated code to make sure the pointer is aligned as promised.</p> to the generated code to make sure the pointer is aligned as promised.</p>
@ -4384,7 +4384,7 @@ comptime {
<pre>{#syntax#}@bitCast(value: anytype) anytype{#endsyntax#}</pre> <pre>{#syntax#}@bitCast(value: anytype) anytype{#endsyntax#}</pre>
<p> <p>
Converts a value of one type to another type. The return type is the Converts a value of one type to another type. The return type is the
inferred result type. inferred result type.
</p> </p>
<p> <p>
Asserts that {#syntax#}@sizeOf(@TypeOf(value)) == @sizeOf(DestType){#endsyntax#}. Asserts that {#syntax#}@sizeOf(@TypeOf(value)) == @sizeOf(DestType){#endsyntax#}.
@ -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>
@ -4855,8 +4891,8 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
<pre>{#syntax#}@errorCast(value: anytype) anytype{#endsyntax#}</pre> <pre>{#syntax#}@errorCast(value: anytype) anytype{#endsyntax#}</pre>
<p> <p>
Converts an error set or error union value from one error set to another error set. The return type is the 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 inferred result type. Attempting to convert an error which is not in the destination error
set results in safety-checked {#link|Illegal Behavior#}. set results in safety-checked {#link|Illegal Behavior#}.
</p> </p>
{#header_close#} {#header_close#}
@ -4935,7 +4971,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
<pre>{#syntax#}@floatFromInt(int: anytype) anytype{#endsyntax#}</pre> <pre>{#syntax#}@floatFromInt(int: anytype) anytype{#endsyntax#}</pre>
<p> <p>
Converts an integer to the closest floating point representation. The return type is the inferred result type. 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. for all values of all integer types.
</p> </p>
{#header_close#} {#header_close#}
@ -5027,7 +5063,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
<pre>{#syntax#}@intCast(int: anytype) anytype{#endsyntax#}</pre> <pre>{#syntax#}@intCast(int: anytype) anytype{#endsyntax#}</pre>
<p> <p>
Converts an integer to another integer while keeping the same numerical value. 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 Attempting to convert a number which is out of range of the destination type results in
safety-checked {#link|Illegal Behavior#}. safety-checked {#link|Illegal Behavior#}.
</p> </p>
@ -5280,7 +5316,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
<pre>{#syntax#}@ptrFromInt(address: usize) anytype{#endsyntax#}</pre> <pre>{#syntax#}@ptrFromInt(address: usize) anytype{#endsyntax#}</pre>
<p> <p>
Converts an integer to a {#link|pointer|Pointers#}. The return type is the inferred result type. 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 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. {#link|Pointer Cast Invalid Null#} panic when runtime safety checks are enabled.
</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();