mirror of
https://github.com/ziglang/zig.git
synced 2025-12-16 11:13:08 +00:00
stage2: progress towards ability to compile compiler-rt
* prepare compiler-rt to support being compiled by stage2
- put in a few minor workarounds that will be removed later, such as
using `builtin.stage2_arch` rather than `builtin.cpu.arch`.
- only try to export a few symbols for now - we'll move more symbols
over to the "working in stage2" section as they become functional
and gain test coverage.
- use `inline fn` at function declarations rather than `@call` with an
always_inline modifier at the callsites, to avoid depending on the
anonymous array literal syntax language feature (for now).
* AIR: replace floatcast instruction with fptrunc and fpext for
shortening and widening floating point values, respectively.
* Introduce a new ZIR instruction, `export_value`, which implements
`@export` for the case when the thing to be exported is a local
comptime value that points to a function.
- AstGen: fix `@export` not properly reporting ambiguous decl
references.
* Sema: handle ExportOptions linkage. The value is now available to all
backends.
- Implement setting global linkage as appropriate in the LLVM
backend. I did not yet inspect the LLVM IR, so this still needs to
be audited. There is already a pending task to make sure the alias
stuff is working as intended, and this is related.
- Sema almost handles section, just a tiny bit more code is needed in
`resolveExportOptions`.
* Sema: implement float widening and shortening for both `@floatCast`
and float coercion.
- Implement the LLVM backend code for this as well.
This commit is contained in:
parent
0e2b9ac777
commit
aecebf38ac
@ -1,17 +1,34 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = std.builtin;
|
const builtin = @import("builtin");
|
||||||
const is_test = builtin.is_test;
|
const is_test = builtin.is_test;
|
||||||
const os_tag = std.Target.current.os.tag;
|
const os_tag = std.Target.current.os.tag;
|
||||||
const arch = std.Target.current.cpu.arch;
|
const arch = builtin.stage2_arch;
|
||||||
const abi = std.Target.current.abi;
|
const abi = std.Target.current.abi;
|
||||||
|
|
||||||
const is_gnu = abi.isGnu();
|
const is_gnu = abi.isGnu();
|
||||||
const is_mingw = os_tag == .windows and is_gnu;
|
const is_mingw = os_tag == .windows and is_gnu;
|
||||||
|
|
||||||
comptime {
|
const linkage = if (is_test)
|
||||||
const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak;
|
std.builtin.GlobalLinkage.Internal
|
||||||
const strong_linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong;
|
else
|
||||||
|
std.builtin.GlobalLinkage.Weak;
|
||||||
|
|
||||||
|
const strong_linkage = if (is_test)
|
||||||
|
std.builtin.GlobalLinkage.Internal
|
||||||
|
else
|
||||||
|
std.builtin.GlobalLinkage.Strong;
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
const __extenddftf2 = @import("compiler_rt/extendXfYf2.zig").__extenddftf2;
|
||||||
|
@export(__extenddftf2, .{ .name = "__extenddftf2", .linkage = linkage });
|
||||||
|
const __extendsftf2 = @import("compiler_rt/extendXfYf2.zig").__extendsftf2;
|
||||||
|
@export(__extendsftf2, .{ .name = "__extendsftf2", .linkage = linkage });
|
||||||
|
const __extendhfsf2 = @import("compiler_rt/extendXfYf2.zig").__extendhfsf2;
|
||||||
|
@export(__extendhfsf2, .{ .name = "__extendhfsf2", .linkage = linkage });
|
||||||
|
const __extendhftf2 = @import("compiler_rt/extendXfYf2.zig").__extendhftf2;
|
||||||
|
@export(__extendhftf2, .{ .name = "__extendhftf2", .linkage = linkage });
|
||||||
|
|
||||||
|
if (!builtin.zig_is_stage2) {
|
||||||
switch (arch) {
|
switch (arch) {
|
||||||
.i386,
|
.i386,
|
||||||
.x86_64,
|
.x86_64,
|
||||||
@ -72,10 +89,14 @@ comptime {
|
|||||||
@export(__gtdf2, .{ .name = "__gtdf2", .linkage = linkage });
|
@export(__gtdf2, .{ .name = "__gtdf2", .linkage = linkage });
|
||||||
@export(__getf2, .{ .name = "__gttf2", .linkage = linkage });
|
@export(__getf2, .{ .name = "__gttf2", .linkage = linkage });
|
||||||
|
|
||||||
const __extendhfsf2 = @import("compiler_rt/extendXfYf2.zig").__extendhfsf2;
|
@export(@import("compiler_rt/extendXfYf2.zig").__extendhfsf2, .{
|
||||||
@export(__extendhfsf2, .{ .name = "__gnu_h2f_ieee", .linkage = linkage });
|
.name = "__gnu_h2f_ieee",
|
||||||
const __truncsfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncsfhf2;
|
.linkage = linkage,
|
||||||
@export(__truncsfhf2, .{ .name = "__gnu_f2h_ieee", .linkage = linkage });
|
});
|
||||||
|
@export(@import("compiler_rt/truncXfYf2.zig").__truncsfhf2, .{
|
||||||
|
.name = "__gnu_f2h_ieee",
|
||||||
|
.linkage = linkage,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const __unordsf2 = @import("compiler_rt/compareXf2.zig").__unordsf2;
|
const __unordsf2 = @import("compiler_rt/compareXf2.zig").__unordsf2;
|
||||||
@ -166,15 +187,6 @@ comptime {
|
|||||||
const __floatuntisf = @import("compiler_rt/floatuntisf.zig").__floatuntisf;
|
const __floatuntisf = @import("compiler_rt/floatuntisf.zig").__floatuntisf;
|
||||||
@export(__floatuntisf, .{ .name = "__floatuntisf", .linkage = linkage });
|
@export(__floatuntisf, .{ .name = "__floatuntisf", .linkage = linkage });
|
||||||
|
|
||||||
const __extenddftf2 = @import("compiler_rt/extendXfYf2.zig").__extenddftf2;
|
|
||||||
@export(__extenddftf2, .{ .name = "__extenddftf2", .linkage = linkage });
|
|
||||||
const __extendsftf2 = @import("compiler_rt/extendXfYf2.zig").__extendsftf2;
|
|
||||||
@export(__extendsftf2, .{ .name = "__extendsftf2", .linkage = linkage });
|
|
||||||
const __extendhfsf2 = @import("compiler_rt/extendXfYf2.zig").__extendhfsf2;
|
|
||||||
@export(__extendhfsf2, .{ .name = "__extendhfsf2", .linkage = linkage });
|
|
||||||
const __extendhftf2 = @import("compiler_rt/extendXfYf2.zig").__extendhftf2;
|
|
||||||
@export(__extendhftf2, .{ .name = "__extendhftf2", .linkage = linkage });
|
|
||||||
|
|
||||||
const __truncsfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncsfhf2;
|
const __truncsfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncsfhf2;
|
||||||
@export(__truncsfhf2, .{ .name = "__truncsfhf2", .linkage = linkage });
|
@export(__truncsfhf2, .{ .name = "__truncsfhf2", .linkage = linkage });
|
||||||
const __truncdfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncdfhf2;
|
const __truncdfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncdfhf2;
|
||||||
@ -608,13 +620,19 @@ comptime {
|
|||||||
@export(__mulodi4, .{ .name = "__mulodi4", .linkage = linkage });
|
@export(__mulodi4, .{ .name = "__mulodi4", .linkage = linkage });
|
||||||
|
|
||||||
_ = @import("compiler_rt/atomics.zig");
|
_ = @import("compiler_rt/atomics.zig");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid dragging in the runtime safety mechanisms into this .o file,
|
// Avoid dragging in the runtime safety mechanisms into this .o file,
|
||||||
// unless we're trying to test this file.
|
// unless we're trying to test this file.
|
||||||
pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn {
|
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn {
|
||||||
_ = error_return_trace;
|
_ = error_return_trace;
|
||||||
@setCold(true);
|
@setCold(true);
|
||||||
|
if (builtin.zig_is_stage2) {
|
||||||
|
while (true) {
|
||||||
|
@breakpoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (is_test) {
|
if (is_test) {
|
||||||
std.debug.panic("{s}", .{msg});
|
std.debug.panic("{s}", .{msg});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -3,23 +3,23 @@ const builtin = @import("builtin");
|
|||||||
const is_test = builtin.is_test;
|
const is_test = builtin.is_test;
|
||||||
|
|
||||||
pub fn __extendsfdf2(a: f32) callconv(.C) f64 {
|
pub fn __extendsfdf2(a: f32) callconv(.C) f64 {
|
||||||
return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f64, f32, @bitCast(u32, a) });
|
return extendXfYf2(f64, f32, @bitCast(u32, a));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __extenddftf2(a: f64) callconv(.C) f128 {
|
pub fn __extenddftf2(a: f64) callconv(.C) f128 {
|
||||||
return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f128, f64, @bitCast(u64, a) });
|
return extendXfYf2(f128, f64, @bitCast(u64, a));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __extendsftf2(a: f32) callconv(.C) f128 {
|
pub fn __extendsftf2(a: f32) callconv(.C) f128 {
|
||||||
return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f128, f32, @bitCast(u32, a) });
|
return extendXfYf2(f128, f32, @bitCast(u32, a));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __extendhfsf2(a: u16) callconv(.C) f32 {
|
pub fn __extendhfsf2(a: u16) callconv(.C) f32 {
|
||||||
return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f32, f16, a });
|
return extendXfYf2(f32, f16, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __extendhftf2(a: u16) callconv(.C) f128 {
|
pub fn __extendhftf2(a: u16) callconv(.C) f128 {
|
||||||
return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f128, f16, a });
|
return extendXfYf2(f128, f16, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __aeabi_h2f(arg: u16) callconv(.AAPCS) f32 {
|
pub fn __aeabi_h2f(arg: u16) callconv(.AAPCS) f32 {
|
||||||
@ -34,7 +34,7 @@ pub fn __aeabi_f2d(arg: f32) callconv(.AAPCS) f64 {
|
|||||||
|
|
||||||
const CHAR_BIT = 8;
|
const CHAR_BIT = 8;
|
||||||
|
|
||||||
fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: std.meta.Int(.unsigned, @typeInfo(src_t).Float.bits)) dst_t {
|
inline fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: std.meta.Int(.unsigned, @typeInfo(src_t).Float.bits)) dst_t {
|
||||||
@setRuntimeSafety(builtin.is_test);
|
@setRuntimeSafety(builtin.is_test);
|
||||||
|
|
||||||
const src_rep_t = std.meta.Int(.unsigned, @typeInfo(src_t).Float.bits);
|
const src_rep_t = std.meta.Int(.unsigned, @typeInfo(src_t).Float.bits);
|
||||||
|
|||||||
10
src/Air.zig
10
src/Air.zig
@ -227,9 +227,12 @@ pub const Inst = struct {
|
|||||||
/// Indicates the program counter will never get to this instruction.
|
/// Indicates the program counter will never get to this instruction.
|
||||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||||
unreach,
|
unreach,
|
||||||
/// Convert from one float type to another.
|
/// Convert from a float type to a smaller one.
|
||||||
/// Uses the `ty_op` field.
|
/// Uses the `ty_op` field.
|
||||||
floatcast,
|
fptrunc,
|
||||||
|
/// Convert from a float type to a wider one.
|
||||||
|
/// Uses the `ty_op` field.
|
||||||
|
fpext,
|
||||||
/// Returns an integer with a different type than the operand. The new type may have
|
/// Returns an integer with a different type than the operand. The new type may have
|
||||||
/// fewer, the same, or more bits than the operand type. However, the instruction
|
/// fewer, the same, or more bits than the operand type. However, the instruction
|
||||||
/// guarantees that the same integer value fits in both types.
|
/// guarantees that the same integer value fits in both types.
|
||||||
@ -586,7 +589,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
|
|||||||
.not,
|
.not,
|
||||||
.bitcast,
|
.bitcast,
|
||||||
.load,
|
.load,
|
||||||
.floatcast,
|
.fpext,
|
||||||
|
.fptrunc,
|
||||||
.intcast,
|
.intcast,
|
||||||
.trunc,
|
.trunc,
|
||||||
.optional_payload,
|
.optional_payload,
|
||||||
|
|||||||
@ -2166,6 +2166,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
|
|||||||
.ensure_result_used,
|
.ensure_result_used,
|
||||||
.ensure_result_non_error,
|
.ensure_result_non_error,
|
||||||
.@"export",
|
.@"export",
|
||||||
|
.export_value,
|
||||||
.set_eval_branch_quota,
|
.set_eval_branch_quota,
|
||||||
.ensure_err_payload_void,
|
.ensure_err_payload_void,
|
||||||
.atomic_store,
|
.atomic_store,
|
||||||
@ -7095,14 +7096,19 @@ fn builtinCall(
|
|||||||
.identifier => {
|
.identifier => {
|
||||||
const ident_token = main_tokens[params[0]];
|
const ident_token = main_tokens[params[0]];
|
||||||
decl_name = try astgen.identAsString(ident_token);
|
decl_name = try astgen.identAsString(ident_token);
|
||||||
{
|
|
||||||
var s = scope;
|
var s = scope;
|
||||||
|
var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
|
||||||
while (true) switch (s.tag) {
|
while (true) switch (s.tag) {
|
||||||
.local_val => {
|
.local_val => {
|
||||||
const local_val = s.cast(Scope.LocalVal).?;
|
const local_val = s.cast(Scope.LocalVal).?;
|
||||||
if (local_val.name == decl_name) {
|
if (local_val.name == decl_name) {
|
||||||
local_val.used = true;
|
local_val.used = true;
|
||||||
break;
|
_ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{
|
||||||
|
.operand = local_val.inst,
|
||||||
|
.options = try comptimeExpr(gz, scope, .{ .coerced_ty = .export_options_type }, params[1]),
|
||||||
|
});
|
||||||
|
return rvalue(gz, rl, .void_value, node);
|
||||||
}
|
}
|
||||||
s = local_val.parent;
|
s = local_val.parent;
|
||||||
},
|
},
|
||||||
@ -7112,15 +7118,33 @@ fn builtinCall(
|
|||||||
if (!local_ptr.maybe_comptime)
|
if (!local_ptr.maybe_comptime)
|
||||||
return astgen.failNode(params[0], "unable to export runtime-known value", .{});
|
return astgen.failNode(params[0], "unable to export runtime-known value", .{});
|
||||||
local_ptr.used = true;
|
local_ptr.used = true;
|
||||||
break;
|
const loaded = try gz.addUnNode(.load, local_ptr.ptr, node);
|
||||||
|
_ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{
|
||||||
|
.operand = loaded,
|
||||||
|
.options = try comptimeExpr(gz, scope, .{ .coerced_ty = .export_options_type }, params[1]),
|
||||||
|
});
|
||||||
|
return rvalue(gz, rl, .void_value, node);
|
||||||
}
|
}
|
||||||
s = local_ptr.parent;
|
s = local_ptr.parent;
|
||||||
},
|
},
|
||||||
.gen_zir => s = s.cast(GenZir).?.parent,
|
.gen_zir => s = s.cast(GenZir).?.parent,
|
||||||
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
|
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
|
||||||
.namespace, .top => break,
|
.namespace => {
|
||||||
};
|
const ns = s.cast(Scope.Namespace).?;
|
||||||
|
if (ns.decls.get(decl_name)) |i| {
|
||||||
|
if (found_already) |f| {
|
||||||
|
return astgen.failNodeNotes(node, "ambiguous reference", .{}, &.{
|
||||||
|
try astgen.errNoteNode(f, "declared here", .{}),
|
||||||
|
try astgen.errNoteNode(i, "also declared here", .{}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
// We found a match but must continue looking for ambiguous references to decls.
|
||||||
|
found_already = i;
|
||||||
|
}
|
||||||
|
s = ns.parent;
|
||||||
|
},
|
||||||
|
.top => break,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
.field_access => {
|
.field_access => {
|
||||||
const namespace_node = node_datas[params[0]].lhs;
|
const namespace_node = node_datas[params[0]].lhs;
|
||||||
|
|||||||
@ -274,7 +274,8 @@ fn analyzeInst(
|
|||||||
.not,
|
.not,
|
||||||
.bitcast,
|
.bitcast,
|
||||||
.load,
|
.load,
|
||||||
.floatcast,
|
.fpext,
|
||||||
|
.fptrunc,
|
||||||
.intcast,
|
.intcast,
|
||||||
.trunc,
|
.trunc,
|
||||||
.optional_payload,
|
.optional_payload,
|
||||||
|
|||||||
@ -2389,6 +2389,7 @@ pub fn deinit(mod: *Module) void {
|
|||||||
fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
|
fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
|
||||||
for (export_list) |exp| {
|
for (export_list) |exp| {
|
||||||
gpa.free(exp.options.name);
|
gpa.free(exp.options.name);
|
||||||
|
if (exp.options.section) |s| gpa.free(s);
|
||||||
gpa.destroy(exp);
|
gpa.destroy(exp);
|
||||||
}
|
}
|
||||||
gpa.free(export_list);
|
gpa.free(export_list);
|
||||||
@ -3317,7 +3318,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
|
|||||||
return mod.fail(&block_scope.base, export_src, "export of inline function", .{});
|
return mod.fail(&block_scope.base, export_src, "export of inline function", .{});
|
||||||
}
|
}
|
||||||
// The scope needs to have the decl in it.
|
// The scope needs to have the decl in it.
|
||||||
try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
|
const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) };
|
||||||
|
try mod.analyzeExport(&block_scope.base, export_src, options, decl);
|
||||||
}
|
}
|
||||||
return type_changed or is_inline != prev_is_inline;
|
return type_changed or is_inline != prev_is_inline;
|
||||||
}
|
}
|
||||||
@ -3376,7 +3378,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
|
|||||||
if (decl.is_exported) {
|
if (decl.is_exported) {
|
||||||
const export_src = src; // TODO point to the export token
|
const export_src = src; // TODO point to the export token
|
||||||
// The scope needs to have the decl in it.
|
// The scope needs to have the decl in it.
|
||||||
try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
|
const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) };
|
||||||
|
try mod.analyzeExport(&block_scope.base, export_src, options, decl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return type_changed;
|
return type_changed;
|
||||||
@ -4119,7 +4122,7 @@ pub fn analyzeExport(
|
|||||||
mod: *Module,
|
mod: *Module,
|
||||||
scope: *Scope,
|
scope: *Scope,
|
||||||
src: LazySrcLoc,
|
src: LazySrcLoc,
|
||||||
borrowed_symbol_name: []const u8,
|
borrowed_options: std.builtin.ExportOptions,
|
||||||
exported_decl: *Decl,
|
exported_decl: *Decl,
|
||||||
) !void {
|
) !void {
|
||||||
try mod.ensureDeclAnalyzed(exported_decl);
|
try mod.ensureDeclAnalyzed(exported_decl);
|
||||||
@ -4128,23 +4131,32 @@ pub fn analyzeExport(
|
|||||||
else => return mod.fail(scope, src, "unable to export type '{}'", .{exported_decl.ty}),
|
else => return mod.fail(scope, src, "unable to export type '{}'", .{exported_decl.ty}),
|
||||||
}
|
}
|
||||||
|
|
||||||
try mod.decl_exports.ensureUnusedCapacity(mod.gpa, 1);
|
const gpa = mod.gpa;
|
||||||
try mod.export_owners.ensureUnusedCapacity(mod.gpa, 1);
|
|
||||||
|
|
||||||
const new_export = try mod.gpa.create(Export);
|
try mod.decl_exports.ensureUnusedCapacity(gpa, 1);
|
||||||
errdefer mod.gpa.destroy(new_export);
|
try mod.export_owners.ensureUnusedCapacity(gpa, 1);
|
||||||
|
|
||||||
const symbol_name = try mod.gpa.dupe(u8, borrowed_symbol_name);
|
const new_export = try gpa.create(Export);
|
||||||
errdefer mod.gpa.free(symbol_name);
|
errdefer gpa.destroy(new_export);
|
||||||
|
|
||||||
|
const symbol_name = try gpa.dupe(u8, borrowed_options.name);
|
||||||
|
errdefer gpa.free(symbol_name);
|
||||||
|
|
||||||
|
const section: ?[]const u8 = if (borrowed_options.section) |s| try gpa.dupe(u8, s) else null;
|
||||||
|
errdefer if (section) |s| gpa.free(s);
|
||||||
|
|
||||||
const owner_decl = scope.ownerDecl().?;
|
const owner_decl = scope.ownerDecl().?;
|
||||||
|
|
||||||
log.debug("exporting Decl '{s}' as symbol '{s}' from Decl '{s}'", .{
|
log.debug("exporting Decl '{s}' as symbol '{s}' from Decl '{s}'", .{
|
||||||
exported_decl.name, borrowed_symbol_name, owner_decl.name,
|
exported_decl.name, symbol_name, owner_decl.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
new_export.* = .{
|
new_export.* = .{
|
||||||
.options = .{ .name = symbol_name },
|
.options = .{
|
||||||
|
.name = symbol_name,
|
||||||
|
.linkage = borrowed_options.linkage,
|
||||||
|
.section = section,
|
||||||
|
},
|
||||||
.src = src,
|
.src = src,
|
||||||
.link = switch (mod.comp.bin_file.tag) {
|
.link = switch (mod.comp.bin_file.tag) {
|
||||||
.coff => .{ .coff = {} },
|
.coff => .{ .coff = {} },
|
||||||
@ -4165,18 +4177,18 @@ pub fn analyzeExport(
|
|||||||
if (!eo_gop.found_existing) {
|
if (!eo_gop.found_existing) {
|
||||||
eo_gop.value_ptr.* = &[0]*Export{};
|
eo_gop.value_ptr.* = &[0]*Export{};
|
||||||
}
|
}
|
||||||
eo_gop.value_ptr.* = try mod.gpa.realloc(eo_gop.value_ptr.*, eo_gop.value_ptr.len + 1);
|
eo_gop.value_ptr.* = try gpa.realloc(eo_gop.value_ptr.*, eo_gop.value_ptr.len + 1);
|
||||||
eo_gop.value_ptr.*[eo_gop.value_ptr.len - 1] = new_export;
|
eo_gop.value_ptr.*[eo_gop.value_ptr.len - 1] = new_export;
|
||||||
errdefer eo_gop.value_ptr.* = mod.gpa.shrink(eo_gop.value_ptr.*, eo_gop.value_ptr.len - 1);
|
errdefer eo_gop.value_ptr.* = gpa.shrink(eo_gop.value_ptr.*, eo_gop.value_ptr.len - 1);
|
||||||
|
|
||||||
// Add to exported_decl table.
|
// Add to exported_decl table.
|
||||||
const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl);
|
const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl);
|
||||||
if (!de_gop.found_existing) {
|
if (!de_gop.found_existing) {
|
||||||
de_gop.value_ptr.* = &[0]*Export{};
|
de_gop.value_ptr.* = &[0]*Export{};
|
||||||
}
|
}
|
||||||
de_gop.value_ptr.* = try mod.gpa.realloc(de_gop.value_ptr.*, de_gop.value_ptr.len + 1);
|
de_gop.value_ptr.* = try gpa.realloc(de_gop.value_ptr.*, de_gop.value_ptr.len + 1);
|
||||||
de_gop.value_ptr.*[de_gop.value_ptr.len - 1] = new_export;
|
de_gop.value_ptr.*[de_gop.value_ptr.len - 1] = new_export;
|
||||||
errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1);
|
errdefer de_gop.value_ptr.* = gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes ownership of `name` even if it returns an error.
|
/// Takes ownership of `name` even if it returns an error.
|
||||||
|
|||||||
138
src/Sema.zig
138
src/Sema.zig
@ -458,6 +458,11 @@ pub fn analyzeBody(
|
|||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
|
.export_value => {
|
||||||
|
try sema.zirExportValue(block, inst);
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
},
|
||||||
.set_align_stack => {
|
.set_align_stack => {
|
||||||
try sema.zirSetAlignStack(block, inst);
|
try sema.zirSetAlignStack(block, inst);
|
||||||
i += 1;
|
i += 1;
|
||||||
@ -2392,30 +2397,33 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro
|
|||||||
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
|
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
|
||||||
const extra = sema.code.extraData(Zir.Inst.Export, inst_data.payload_index).data;
|
const extra = sema.code.extraData(Zir.Inst.Export, inst_data.payload_index).data;
|
||||||
const src = inst_data.src();
|
const src = inst_data.src();
|
||||||
const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
|
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
|
||||||
const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
|
const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
|
||||||
const decl_name = sema.code.nullTerminatedString(extra.decl_name);
|
const decl_name = sema.code.nullTerminatedString(extra.decl_name);
|
||||||
if (extra.namespace != .none) {
|
if (extra.namespace != .none) {
|
||||||
return sema.mod.fail(&block.base, src, "TODO: implement exporting with field access", .{});
|
return sema.mod.fail(&block.base, src, "TODO: implement exporting with field access", .{});
|
||||||
}
|
}
|
||||||
const decl = try sema.lookupIdentifier(block, lhs_src, decl_name);
|
const decl = try sema.lookupIdentifier(block, operand_src, decl_name);
|
||||||
const options = try sema.resolveInstConst(block, rhs_src, extra.options);
|
const options = try sema.resolveExportOptions(block, options_src, extra.options);
|
||||||
const struct_obj = options.ty.castTag(.@"struct").?.data;
|
try sema.mod.analyzeExport(&block.base, src, options, decl);
|
||||||
const fields = options.val.castTag(.@"struct").?.data[0..struct_obj.fields.count()];
|
}
|
||||||
const name_index = struct_obj.fields.getIndex("name").?;
|
|
||||||
const linkage_index = struct_obj.fields.getIndex("linkage").?;
|
|
||||||
const section_index = struct_obj.fields.getIndex("section").?;
|
|
||||||
const export_name = try fields[name_index].toAllocatedBytes(sema.arena);
|
|
||||||
const linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage);
|
|
||||||
|
|
||||||
if (linkage != .Strong) {
|
fn zirExportValue(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
|
||||||
return sema.mod.fail(&block.base, src, "TODO: implement exporting with non-strong linkage", .{});
|
const tracy = trace(@src());
|
||||||
}
|
defer tracy.end();
|
||||||
if (!fields[section_index].isNull()) {
|
|
||||||
return sema.mod.fail(&block.base, src, "TODO: implement exporting with linksection", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
try sema.mod.analyzeExport(&block.base, src, export_name, decl);
|
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
|
||||||
|
const extra = sema.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data;
|
||||||
|
const src = inst_data.src();
|
||||||
|
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
|
||||||
|
const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
|
||||||
|
const operand = try sema.resolveInstConst(block, operand_src, extra.operand);
|
||||||
|
const options = try sema.resolveExportOptions(block, options_src, extra.options);
|
||||||
|
const decl = switch (operand.val.tag()) {
|
||||||
|
.function => operand.val.castTag(.function).?.data.owner_decl,
|
||||||
|
else => return sema.mod.fail(&block.base, operand_src, "TODO implement exporting arbitrary Value objects", .{}), // TODO put this Value into an anonymous Decl and then export it.
|
||||||
|
};
|
||||||
|
try sema.mod.analyzeExport(&block.base, src, options, decl);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zirSetAlignStack(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
|
fn zirSetAlignStack(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
|
||||||
@ -4516,11 +4524,18 @@ fn zirFloatCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
|
|||||||
|
|
||||||
if (try sema.isComptimeKnown(block, operand_src, operand)) {
|
if (try sema.isComptimeKnown(block, operand_src, operand)) {
|
||||||
return sema.coerce(block, dest_type, operand, operand_src);
|
return sema.coerce(block, dest_type, operand, operand_src);
|
||||||
} else if (dest_is_comptime_float) {
|
}
|
||||||
|
if (dest_is_comptime_float) {
|
||||||
return sema.mod.fail(&block.base, src, "unable to cast runtime value to 'comptime_float'", .{});
|
return sema.mod.fail(&block.base, src, "unable to cast runtime value to 'comptime_float'", .{});
|
||||||
}
|
}
|
||||||
|
const target = sema.mod.getTarget();
|
||||||
return sema.mod.fail(&block.base, src, "TODO implement analyze widen or shorten float", .{});
|
const src_bits = operand_ty.floatBits(target);
|
||||||
|
const dst_bits = dest_type.floatBits(target);
|
||||||
|
if (dst_bits >= src_bits) {
|
||||||
|
return sema.coerce(block, dest_type, operand, operand_src);
|
||||||
|
}
|
||||||
|
try sema.requireRuntimeBlock(block, operand_src);
|
||||||
|
return block.addTyOp(.fptrunc, dest_type, operand);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zirElemVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
fn zirElemVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||||
@ -7936,6 +7951,31 @@ fn checkAtomicOperandType(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolveExportOptions(
|
||||||
|
sema: *Sema,
|
||||||
|
block: *Scope.Block,
|
||||||
|
src: LazySrcLoc,
|
||||||
|
zir_ref: Zir.Inst.Ref,
|
||||||
|
) CompileError!std.builtin.ExportOptions {
|
||||||
|
const export_options_ty = try sema.getBuiltinType(block, src, "ExportOptions");
|
||||||
|
const air_ref = sema.resolveInst(zir_ref);
|
||||||
|
const coerced = try sema.coerce(block, export_options_ty, air_ref, src);
|
||||||
|
const val = try sema.resolveConstValue(block, src, coerced);
|
||||||
|
const fields = val.castTag(.@"struct").?.data;
|
||||||
|
const struct_obj = export_options_ty.castTag(.@"struct").?.data;
|
||||||
|
const name_index = struct_obj.fields.getIndex("name").?;
|
||||||
|
const linkage_index = struct_obj.fields.getIndex("linkage").?;
|
||||||
|
const section_index = struct_obj.fields.getIndex("section").?;
|
||||||
|
if (!fields[section_index].isNull()) {
|
||||||
|
return sema.mod.fail(&block.base, src, "TODO: implement exporting with linksection", .{});
|
||||||
|
}
|
||||||
|
return std.builtin.ExportOptions{
|
||||||
|
.name = try fields[name_index].toAllocatedBytes(sema.arena),
|
||||||
|
.linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage),
|
||||||
|
.section = null, // TODO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn resolveAtomicOrder(
|
fn resolveAtomicOrder(
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
block: *Scope.Block,
|
block: *Scope.Block,
|
||||||
@ -9581,7 +9621,7 @@ fn coerce(
|
|||||||
const dst_bits = dest_type.floatBits(target);
|
const dst_bits = dest_type.floatBits(target);
|
||||||
if (dst_bits >= src_bits) {
|
if (dst_bits >= src_bits) {
|
||||||
try sema.requireRuntimeBlock(block, inst_src);
|
try sema.requireRuntimeBlock(block, inst_src);
|
||||||
return block.addTyOp(.floatcast, dest_type, inst);
|
return block.addTyOp(.fpext, dest_type, inst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -9729,35 +9769,53 @@ fn coerceNum(
|
|||||||
const target = sema.mod.getTarget();
|
const target = sema.mod.getTarget();
|
||||||
|
|
||||||
switch (dst_zig_tag) {
|
switch (dst_zig_tag) {
|
||||||
.ComptimeInt, .Int => {
|
.ComptimeInt, .Int => switch (src_zig_tag) {
|
||||||
if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
|
.Float, .ComptimeFloat => {
|
||||||
if (val.floatHasFraction()) {
|
if (val.floatHasFraction()) {
|
||||||
return sema.mod.fail(&block.base, inst_src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst_ty });
|
return sema.mod.fail(&block.base, inst_src, "fractional component prevents float value {} from coercion to type '{}'", .{ val, dest_type });
|
||||||
}
|
}
|
||||||
return sema.mod.fail(&block.base, inst_src, "TODO float to int", .{});
|
return sema.mod.fail(&block.base, inst_src, "TODO float to int", .{});
|
||||||
} else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
|
},
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
if (!val.intFitsInType(dest_type, target)) {
|
if (!val.intFitsInType(dest_type, target)) {
|
||||||
return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ dest_type, val });
|
return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ dest_type, val });
|
||||||
}
|
}
|
||||||
return try sema.addConstant(dest_type, val);
|
return try sema.addConstant(dest_type, val);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
.ComptimeFloat, .Float => {
|
else => {},
|
||||||
if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
|
},
|
||||||
const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) {
|
.ComptimeFloat, .Float => switch (src_zig_tag) {
|
||||||
error.Overflow => return sema.mod.fail(
|
.ComptimeFloat => {
|
||||||
|
const result_val = try val.floatCast(sema.arena, dest_type);
|
||||||
|
return try sema.addConstant(dest_type, result_val);
|
||||||
|
},
|
||||||
|
.Float => {
|
||||||
|
const result_val = try val.floatCast(sema.arena, dest_type);
|
||||||
|
if (!val.eql(result_val, dest_type)) {
|
||||||
|
return sema.mod.fail(
|
||||||
&block.base,
|
&block.base,
|
||||||
inst_src,
|
inst_src,
|
||||||
"cast of value {} to type '{}' loses information",
|
"type {} cannot represent float value {}",
|
||||||
.{ val, dest_type },
|
.{ dest_type, val },
|
||||||
),
|
);
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
|
||||||
};
|
|
||||||
return try sema.addConstant(dest_type, res);
|
|
||||||
} else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
|
|
||||||
const result_val = try val.intToFloat(sema.arena, dest_type, target);
|
|
||||||
return try sema.addConstant(dest_type, result_val);
|
|
||||||
}
|
}
|
||||||
|
return try sema.addConstant(dest_type, result_val);
|
||||||
|
},
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
|
const result_val = try val.intToFloat(sema.arena, dest_type, target);
|
||||||
|
// TODO implement this compile error
|
||||||
|
//const int_again_val = try result_val.floatToInt(sema.arena, inst_ty);
|
||||||
|
//if (!int_again_val.eql(val, inst_ty)) {
|
||||||
|
// return sema.mod.fail(
|
||||||
|
// &block.base,
|
||||||
|
// inst_src,
|
||||||
|
// "type {} cannot represent integer value {}",
|
||||||
|
// .{ dest_type, val },
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
return try sema.addConstant(dest_type, result_val);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/Zir.zig
14
src/Zir.zig
@ -319,9 +319,13 @@ pub const Inst = struct {
|
|||||||
/// `error.Foo` syntax. Uses the `str_tok` field of the Data union.
|
/// `error.Foo` syntax. Uses the `str_tok` field of the Data union.
|
||||||
error_value,
|
error_value,
|
||||||
/// Implements the `@export` builtin function, based on either an identifier to a Decl,
|
/// Implements the `@export` builtin function, based on either an identifier to a Decl,
|
||||||
/// or field access of a Decl.
|
/// or field access of a Decl. The thing being exported is the Decl.
|
||||||
/// Uses the `pl_node` union field. Payload is `Export`.
|
/// Uses the `pl_node` union field. Payload is `Export`.
|
||||||
@"export",
|
@"export",
|
||||||
|
/// Implements the `@export` builtin function, based on a comptime-known value.
|
||||||
|
/// The thing being exported is the comptime-known value which is the operand.
|
||||||
|
/// Uses the `pl_node` union field. Payload is `ExportValue`.
|
||||||
|
export_value,
|
||||||
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
|
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
|
||||||
/// to the named field. The field name is stored in string_bytes. Used by a.b syntax.
|
/// to the named field. The field name is stored in string_bytes. Used by a.b syntax.
|
||||||
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
|
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
|
||||||
@ -1010,6 +1014,7 @@ pub const Inst = struct {
|
|||||||
.ensure_result_used,
|
.ensure_result_used,
|
||||||
.ensure_result_non_error,
|
.ensure_result_non_error,
|
||||||
.@"export",
|
.@"export",
|
||||||
|
.export_value,
|
||||||
.field_ptr,
|
.field_ptr,
|
||||||
.field_val,
|
.field_val,
|
||||||
.field_ptr_named,
|
.field_ptr_named,
|
||||||
@ -1273,6 +1278,7 @@ pub const Inst = struct {
|
|||||||
.error_union_type = .pl_node,
|
.error_union_type = .pl_node,
|
||||||
.error_value = .str_tok,
|
.error_value = .str_tok,
|
||||||
.@"export" = .pl_node,
|
.@"export" = .pl_node,
|
||||||
|
.export_value = .pl_node,
|
||||||
.field_ptr = .pl_node,
|
.field_ptr = .pl_node,
|
||||||
.field_val = .pl_node,
|
.field_val = .pl_node,
|
||||||
.field_ptr_named = .pl_node,
|
.field_ptr_named = .pl_node,
|
||||||
@ -2843,6 +2849,12 @@ pub const Inst = struct {
|
|||||||
options: Ref,
|
options: Ref,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ExportValue = struct {
|
||||||
|
/// The comptime value to export.
|
||||||
|
operand: Ref,
|
||||||
|
options: Ref,
|
||||||
|
};
|
||||||
|
|
||||||
/// Trailing: `CompileErrors.Item` for each `items_len`.
|
/// Trailing: `CompileErrors.Item` for each `items_len`.
|
||||||
pub const CompileErrors = struct {
|
pub const CompileErrors = struct {
|
||||||
items_len: u32,
|
items_len: u32,
|
||||||
|
|||||||
@ -859,7 +859,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
|||||||
.call => try self.airCall(inst),
|
.call => try self.airCall(inst),
|
||||||
.cond_br => try self.airCondBr(inst),
|
.cond_br => try self.airCondBr(inst),
|
||||||
.dbg_stmt => try self.airDbgStmt(inst),
|
.dbg_stmt => try self.airDbgStmt(inst),
|
||||||
.floatcast => try self.airFloatCast(inst),
|
.fptrunc => try self.airFptrunc(inst),
|
||||||
|
.fpext => try self.airFpext(inst),
|
||||||
.intcast => try self.airIntCast(inst),
|
.intcast => try self.airIntCast(inst),
|
||||||
.trunc => try self.airTrunc(inst),
|
.trunc => try self.airTrunc(inst),
|
||||||
.bool_to_int => try self.airBoolToInt(inst),
|
.bool_to_int => try self.airBoolToInt(inst),
|
||||||
@ -1172,10 +1173,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
|||||||
return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
|
return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn airFloatCast(self: *Self, inst: Air.Inst.Index) !void {
|
fn airFptrunc(self: *Self, inst: Air.Inst.Index) !void {
|
||||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||||
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
|
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
|
||||||
else => return self.fail("TODO implement floatCast for {}", .{self.target.cpu.arch}),
|
else => return self.fail("TODO implement airFptrunc for {}", .{self.target.cpu.arch}),
|
||||||
|
};
|
||||||
|
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn airFpext(self: *Self, inst: Air.Inst.Index) !void {
|
||||||
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||||
|
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
|
||||||
|
else => return self.fail("TODO implement airFpext for {}", .{self.target.cpu.arch}),
|
||||||
};
|
};
|
||||||
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
|
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -954,7 +954,12 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
|
|||||||
.atomic_rmw => try airAtomicRmw(f, inst),
|
.atomic_rmw => try airAtomicRmw(f, inst),
|
||||||
.atomic_load => try airAtomicLoad(f, inst),
|
.atomic_load => try airAtomicLoad(f, inst),
|
||||||
|
|
||||||
.int_to_float, .float_to_int => try airSimpleCast(f, inst),
|
.int_to_float,
|
||||||
|
.float_to_int,
|
||||||
|
.fptrunc,
|
||||||
|
.fpext,
|
||||||
|
.ptrtoint,
|
||||||
|
=> try airSimpleCast(f, inst),
|
||||||
|
|
||||||
.atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.Unordered)),
|
.atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.Unordered)),
|
||||||
.atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.Monotonic)),
|
.atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.Monotonic)),
|
||||||
@ -982,9 +987,6 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
|
|||||||
.unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst),
|
.unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst),
|
||||||
.wrap_errunion_payload => try airWrapErrUnionPay(f, inst),
|
.wrap_errunion_payload => try airWrapErrUnionPay(f, inst),
|
||||||
.wrap_errunion_err => try airWrapErrUnionErr(f, inst),
|
.wrap_errunion_err => try airWrapErrUnionErr(f, inst),
|
||||||
|
|
||||||
.ptrtoint => return f.fail("TODO: C backend: implement codegen for ptrtoint", .{}),
|
|
||||||
.floatcast => return f.fail("TODO: C backend: implement codegen for floatcast", .{}),
|
|
||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
};
|
};
|
||||||
switch (result_value) {
|
switch (result_value) {
|
||||||
|
|||||||
@ -472,7 +472,18 @@ pub const Object = struct {
|
|||||||
alias.setAliasee(llvm_fn);
|
alias.setAliasee(llvm_fn);
|
||||||
} else {
|
} else {
|
||||||
const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z);
|
const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z);
|
||||||
_ = alias;
|
switch (exp.options.linkage) {
|
||||||
|
.Internal => alias.setLinkage(.Internal),
|
||||||
|
.Strong => alias.setLinkage(.External),
|
||||||
|
.Weak => {
|
||||||
|
if (is_extern) {
|
||||||
|
alias.setLinkage(.ExternalWeak);
|
||||||
|
} else {
|
||||||
|
alias.setLinkage(.WeakODR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.LinkOnce => alias.setLinkage(.LinkOnceODR),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1137,7 +1148,8 @@ pub const FuncGen = struct {
|
|||||||
.cond_br => try self.airCondBr(inst),
|
.cond_br => try self.airCondBr(inst),
|
||||||
.intcast => try self.airIntCast(inst),
|
.intcast => try self.airIntCast(inst),
|
||||||
.trunc => try self.airTrunc(inst),
|
.trunc => try self.airTrunc(inst),
|
||||||
.floatcast => try self.airFloatCast(inst),
|
.fptrunc => try self.airFptrunc(inst),
|
||||||
|
.fpext => try self.airFpext(inst),
|
||||||
.ptrtoint => try self.airPtrToInt(inst),
|
.ptrtoint => try self.airPtrToInt(inst),
|
||||||
.load => try self.airLoad(inst),
|
.load => try self.airLoad(inst),
|
||||||
.loop => try self.airLoop(inst),
|
.loop => try self.airLoop(inst),
|
||||||
@ -2060,12 +2072,26 @@ pub const FuncGen = struct {
|
|||||||
return self.builder.buildTrunc(operand, dest_llvm_ty, "");
|
return self.builder.buildTrunc(operand, dest_llvm_ty, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
fn airFptrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||||
if (self.liveness.isUnused(inst))
|
if (self.liveness.isUnused(inst))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// TODO split floatcast AIR into float_widen and float_shorten
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||||
return self.todo("implement 'airFloatCast'", .{});
|
const operand = try self.resolveInst(ty_op.operand);
|
||||||
|
const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
|
||||||
|
|
||||||
|
return self.builder.buildFPTrunc(operand, dest_llvm_ty, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn airFpext(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||||
|
if (self.liveness.isUnused(inst))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||||
|
const operand = try self.resolveInst(ty_op.operand);
|
||||||
|
const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
|
||||||
|
|
||||||
|
return self.builder.buildFPExt(operand, dest_llvm_ty, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn airPtrToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
fn airPtrToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||||
|
|||||||
@ -601,6 +601,22 @@ pub const Builder = opaque {
|
|||||||
DestTy: *const Type,
|
DestTy: *const Type,
|
||||||
Name: [*:0]const u8,
|
Name: [*:0]const u8,
|
||||||
) *const Value;
|
) *const Value;
|
||||||
|
|
||||||
|
pub const buildFPTrunc = LLVMBuildFPTrunc;
|
||||||
|
extern fn LLVMBuildFPTrunc(
|
||||||
|
*const Builder,
|
||||||
|
Val: *const Value,
|
||||||
|
DestTy: *const Type,
|
||||||
|
Name: [*:0]const u8,
|
||||||
|
) *const Value;
|
||||||
|
|
||||||
|
pub const buildFPExt = LLVMBuildFPExt;
|
||||||
|
extern fn LLVMBuildFPExt(
|
||||||
|
*const Builder,
|
||||||
|
Val: *const Value,
|
||||||
|
DestTy: *const Type,
|
||||||
|
Name: [*:0]const u8,
|
||||||
|
) *const Value;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const IntPredicate = enum(c_uint) {
|
pub const IntPredicate = enum(c_uint) {
|
||||||
|
|||||||
@ -156,7 +156,8 @@ const Writer = struct {
|
|||||||
.not,
|
.not,
|
||||||
.bitcast,
|
.bitcast,
|
||||||
.load,
|
.load,
|
||||||
.floatcast,
|
.fptrunc,
|
||||||
|
.fpext,
|
||||||
.intcast,
|
.intcast,
|
||||||
.trunc,
|
.trunc,
|
||||||
.optional_payload,
|
.optional_payload,
|
||||||
|
|||||||
@ -285,6 +285,7 @@ const Writer = struct {
|
|||||||
=> try self.writePlNodeBin(stream, inst),
|
=> try self.writePlNodeBin(stream, inst),
|
||||||
|
|
||||||
.@"export" => try self.writePlNodeExport(stream, inst),
|
.@"export" => try self.writePlNodeExport(stream, inst),
|
||||||
|
.export_value => try self.writePlNodeExportValue(stream, inst),
|
||||||
|
|
||||||
.call,
|
.call,
|
||||||
.call_chkused,
|
.call_chkused,
|
||||||
@ -611,6 +612,17 @@ const Writer = struct {
|
|||||||
try self.writeSrc(stream, inst_data.src());
|
try self.writeSrc(stream, inst_data.src());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn writePlNodeExportValue(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||||
|
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
|
||||||
|
const extra = self.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data;
|
||||||
|
|
||||||
|
try self.writeInstRef(stream, extra.operand);
|
||||||
|
try stream.writeAll(", ");
|
||||||
|
try self.writeInstRef(stream, extra.options);
|
||||||
|
try stream.writeAll(") ");
|
||||||
|
try self.writeSrc(stream, inst_data.src());
|
||||||
|
}
|
||||||
|
|
||||||
fn writeStructInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
fn writeStructInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||||
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
|
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
|
||||||
const extra = self.code.extraData(Zir.Inst.StructInit, inst_data.payload_index);
|
const extra = self.code.extraData(Zir.Inst.StructInit, inst_data.payload_index);
|
||||||
|
|||||||
@ -9325,6 +9325,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
|
|||||||
buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded));
|
buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded));
|
||||||
buf_appendf(contents, "pub const abi = std.Target.Abi.%s;\n", cur_abi);
|
buf_appendf(contents, "pub const abi = std.Target.Abi.%s;\n", cur_abi);
|
||||||
buf_appendf(contents, "pub const cpu = std.Target.Cpu.baseline(.%s);\n", cur_arch);
|
buf_appendf(contents, "pub const cpu = std.Target.Cpu.baseline(.%s);\n", cur_arch);
|
||||||
|
buf_appendf(contents, "pub const stage2_arch: std.Target.Cpu.Arch = .%s;\n", cur_arch);
|
||||||
buf_appendf(contents, "pub const os = std.Target.Os.Tag.defaultVersionRange(.%s);\n", cur_os);
|
buf_appendf(contents, "pub const os = std.Target.Os.Tag.defaultVersionRange(.%s);\n", cur_os);
|
||||||
buf_appendf(contents,
|
buf_appendf(contents,
|
||||||
"pub const target = std.Target{\n"
|
"pub const target = std.Target{\n"
|
||||||
|
|||||||
@ -1041,30 +1041,15 @@ pub const Value = extern union {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an integer or a float to a float.
|
/// Converts an integer or a float to a float. May result in a loss of information.
|
||||||
/// Returns `error.Overflow` if the value does not fit in the new type.
|
/// Caller can find out by equality checking the result against the operand.
|
||||||
pub fn floatCast(self: Value, allocator: *Allocator, dest_ty: Type) !Value {
|
pub fn floatCast(self: Value, arena: *Allocator, dest_ty: Type) !Value {
|
||||||
switch (dest_ty.tag()) {
|
switch (dest_ty.tag()) {
|
||||||
.f16 => {
|
.f16 => return Value.Tag.float_16.create(arena, self.toFloat(f16)),
|
||||||
const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16));
|
.f32 => return Value.Tag.float_32.create(arena, self.toFloat(f32)),
|
||||||
if (!self.eql(res, dest_ty))
|
.f64 => return Value.Tag.float_64.create(arena, self.toFloat(f64)),
|
||||||
return error.Overflow;
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
.f32 => {
|
|
||||||
const res = try Value.Tag.float_32.create(allocator, self.toFloat(f32));
|
|
||||||
if (!self.eql(res, dest_ty))
|
|
||||||
return error.Overflow;
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
.f64 => {
|
|
||||||
const res = try Value.Tag.float_64.create(allocator, self.toFloat(f64));
|
|
||||||
if (!self.eql(res, dest_ty))
|
|
||||||
return error.Overflow;
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
.f128, .comptime_float, .c_longdouble => {
|
.f128, .comptime_float, .c_longdouble => {
|
||||||
return Value.Tag.float_128.create(allocator, self.toFloat(f128));
|
return Value.Tag.float_128.create(arena, self.toFloat(f128));
|
||||||
},
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ test {
|
|||||||
_ = @import("behavior/atomics.zig");
|
_ = @import("behavior/atomics.zig");
|
||||||
_ = @import("behavior/sizeof_and_typeof.zig");
|
_ = @import("behavior/sizeof_and_typeof.zig");
|
||||||
_ = @import("behavior/translate_c_macros.zig");
|
_ = @import("behavior/translate_c_macros.zig");
|
||||||
|
_ = @import("behavior/union.zig");
|
||||||
_ = @import("behavior/widening.zig");
|
_ = @import("behavior/widening.zig");
|
||||||
|
|
||||||
if (builtin.zig_is_stage2) {
|
if (builtin.zig_is_stage2) {
|
||||||
@ -149,7 +150,7 @@ test {
|
|||||||
_ = @import("behavior/typename.zig");
|
_ = @import("behavior/typename.zig");
|
||||||
_ = @import("behavior/undefined.zig");
|
_ = @import("behavior/undefined.zig");
|
||||||
_ = @import("behavior/underscore.zig");
|
_ = @import("behavior/underscore.zig");
|
||||||
_ = @import("behavior/union.zig");
|
_ = @import("behavior/union_stage1.zig");
|
||||||
_ = @import("behavior/usingnamespace_stage1.zig");
|
_ = @import("behavior/usingnamespace_stage1.zig");
|
||||||
_ = @import("behavior/var_args.zig");
|
_ = @import("behavior/var_args.zig");
|
||||||
_ = @import("behavior/vector.zig");
|
_ = @import("behavior/vector.zig");
|
||||||
@ -158,7 +159,6 @@ test {
|
|||||||
_ = @import("behavior/wasm.zig");
|
_ = @import("behavior/wasm.zig");
|
||||||
}
|
}
|
||||||
_ = @import("behavior/while.zig");
|
_ = @import("behavior/while.zig");
|
||||||
_ = @import("behavior/widening_stage1.zig");
|
|
||||||
_ = @import("behavior/src.zig");
|
_ = @import("behavior/src.zig");
|
||||||
_ = @import("behavior/translate_c_macros_stage1.zig");
|
_ = @import("behavior/translate_c_macros_stage1.zig");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,816 +2,3 @@ const std = @import("std");
|
|||||||
const expect = std.testing.expect;
|
const expect = std.testing.expect;
|
||||||
const expectEqual = std.testing.expectEqual;
|
const expectEqual = std.testing.expectEqual;
|
||||||
const Tag = std.meta.Tag;
|
const Tag = std.meta.Tag;
|
||||||
|
|
||||||
const Value = union(enum) {
|
|
||||||
Int: u64,
|
|
||||||
Array: [9]u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Agg = struct {
|
|
||||||
val1: Value,
|
|
||||||
val2: Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
const v1 = Value{ .Int = 1234 };
|
|
||||||
const v2 = Value{ .Array = [_]u8{3} ** 9 };
|
|
||||||
|
|
||||||
const err = @as(anyerror!Agg, Agg{
|
|
||||||
.val1 = v1,
|
|
||||||
.val2 = v2,
|
|
||||||
});
|
|
||||||
|
|
||||||
const array = [_]Value{
|
|
||||||
v1,
|
|
||||||
v2,
|
|
||||||
v1,
|
|
||||||
v2,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "unions embedded in aggregate types" {
|
|
||||||
switch (array[1]) {
|
|
||||||
Value.Array => |arr| try expect(arr[4] == 3),
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
switch ((err catch unreachable).val1) {
|
|
||||||
Value.Int => |x| try expect(x == 1234),
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Foo = union {
|
|
||||||
float: f64,
|
|
||||||
int: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "basic unions" {
|
|
||||||
var foo = Foo{ .int = 1 };
|
|
||||||
try expect(foo.int == 1);
|
|
||||||
foo = Foo{ .float = 12.34 };
|
|
||||||
try expect(foo.float == 12.34);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "comptime union field access" {
|
|
||||||
comptime {
|
|
||||||
var foo = Foo{ .int = 0 };
|
|
||||||
try expect(foo.int == 0);
|
|
||||||
|
|
||||||
foo = Foo{ .float = 42.42 };
|
|
||||||
try expect(foo.float == 42.42);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "init union with runtime value" {
|
|
||||||
var foo: Foo = undefined;
|
|
||||||
|
|
||||||
setFloat(&foo, 12.34);
|
|
||||||
try expect(foo.float == 12.34);
|
|
||||||
|
|
||||||
setInt(&foo, 42);
|
|
||||||
try expect(foo.int == 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setFloat(foo: *Foo, x: f64) void {
|
|
||||||
foo.* = Foo{ .float = x };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setInt(foo: *Foo, x: i32) void {
|
|
||||||
foo.* = Foo{ .int = x };
|
|
||||||
}
|
|
||||||
|
|
||||||
const FooExtern = extern union {
|
|
||||||
float: f64,
|
|
||||||
int: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "basic extern unions" {
|
|
||||||
var foo = FooExtern{ .int = 1 };
|
|
||||||
try expect(foo.int == 1);
|
|
||||||
foo.float = 12.34;
|
|
||||||
try expect(foo.float == 12.34);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Letter = enum {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
};
|
|
||||||
const Payload = union(Letter) {
|
|
||||||
A: i32,
|
|
||||||
B: f64,
|
|
||||||
C: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "union with specified enum tag" {
|
|
||||||
try doTest();
|
|
||||||
comptime try doTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn doTest() error{TestUnexpectedResult}!void {
|
|
||||||
try expect((try bar(Payload{ .A = 1234 })) == -10);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bar(value: Payload) error{TestUnexpectedResult}!i32 {
|
|
||||||
try expect(@as(Letter, value) == Letter.A);
|
|
||||||
return switch (value) {
|
|
||||||
Payload.A => |x| return x - 1244,
|
|
||||||
Payload.B => |x| if (x == 12.34) @as(i32, 20) else 21,
|
|
||||||
Payload.C => |x| if (x) @as(i32, 30) else 31,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const MultipleChoice = union(enum(u32)) {
|
|
||||||
A = 20,
|
|
||||||
B = 40,
|
|
||||||
C = 60,
|
|
||||||
D = 1000,
|
|
||||||
};
|
|
||||||
test "simple union(enum(u32))" {
|
|
||||||
var x = MultipleChoice.C;
|
|
||||||
try expect(x == MultipleChoice.C);
|
|
||||||
try expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
const MultipleChoice2 = union(enum(u32)) {
|
|
||||||
Unspecified1: i32,
|
|
||||||
A: f32 = 20,
|
|
||||||
Unspecified2: void,
|
|
||||||
B: bool = 40,
|
|
||||||
Unspecified3: i32,
|
|
||||||
C: i8 = 60,
|
|
||||||
Unspecified4: void,
|
|
||||||
D: void = 1000,
|
|
||||||
Unspecified5: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "union(enum(u32)) with specified and unspecified tag values" {
|
|
||||||
comptime try expect(Tag(Tag(MultipleChoice2)) == u32);
|
|
||||||
try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
|
||||||
comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
|
|
||||||
try expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60);
|
|
||||||
try expect(1123 == switch (x) {
|
|
||||||
MultipleChoice2.A => 1,
|
|
||||||
MultipleChoice2.B => 2,
|
|
||||||
MultipleChoice2.C => |v| @as(i32, 1000) + v,
|
|
||||||
MultipleChoice2.D => 4,
|
|
||||||
MultipleChoice2.Unspecified1 => 5,
|
|
||||||
MultipleChoice2.Unspecified2 => 6,
|
|
||||||
MultipleChoice2.Unspecified3 => 7,
|
|
||||||
MultipleChoice2.Unspecified4 => 8,
|
|
||||||
MultipleChoice2.Unspecified5 => 9,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExternPtrOrInt = extern union {
|
|
||||||
ptr: *u8,
|
|
||||||
int: u64,
|
|
||||||
};
|
|
||||||
test "extern union size" {
|
|
||||||
comptime try expect(@sizeOf(ExternPtrOrInt) == 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
const PackedPtrOrInt = packed union {
|
|
||||||
ptr: *u8,
|
|
||||||
int: u64,
|
|
||||||
};
|
|
||||||
test "extern union size" {
|
|
||||||
comptime try expect(@sizeOf(PackedPtrOrInt) == 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZeroBits = union {
|
|
||||||
OnlyField: void,
|
|
||||||
};
|
|
||||||
test "union with only 1 field which is void should be zero bits" {
|
|
||||||
comptime try expect(@sizeOf(ZeroBits) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const TheTag = enum {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
};
|
|
||||||
const TheUnion = union(TheTag) {
|
|
||||||
A: i32,
|
|
||||||
B: i32,
|
|
||||||
C: i32,
|
|
||||||
};
|
|
||||||
test "union field access gives the enum values" {
|
|
||||||
try expect(TheUnion.A == TheTag.A);
|
|
||||||
try expect(TheUnion.B == TheTag.B);
|
|
||||||
try expect(TheUnion.C == TheTag.C);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "cast union to tag type of union" {
|
|
||||||
try testCastUnionToTag(TheUnion{ .B = 1234 });
|
|
||||||
comptime try testCastUnionToTag(TheUnion{ .B = 1234 });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testCastUnionToTag(x: TheUnion) !void {
|
|
||||||
try expect(@as(TheTag, x) == TheTag.B);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "cast tag type of union to union" {
|
|
||||||
var x: Value2 = Letter2.B;
|
|
||||||
try expect(@as(Letter2, x) == Letter2.B);
|
|
||||||
}
|
|
||||||
const Letter2 = enum {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
};
|
|
||||||
const Value2 = union(Letter2) {
|
|
||||||
A: i32,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "implicit cast union to its tag type" {
|
|
||||||
var x: Value2 = Letter2.B;
|
|
||||||
try expect(x == Letter2.B);
|
|
||||||
try giveMeLetterB(x);
|
|
||||||
}
|
|
||||||
fn giveMeLetterB(x: Letter2) !void {
|
|
||||||
try expect(x == Value2.B);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const PackThis = union(enum) {
|
|
||||||
Invalid: bool,
|
|
||||||
StringLiteral: u2,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "constant packed union" {
|
|
||||||
try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testConstPackedUnion(expected_tokens: []const PackThis) !void {
|
|
||||||
try expect(expected_tokens[0].StringLiteral == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "switch on union with only 1 field" {
|
|
||||||
var r: PartialInst = undefined;
|
|
||||||
r = PartialInst.Compiled;
|
|
||||||
switch (r) {
|
|
||||||
PartialInst.Compiled => {
|
|
||||||
var z: PartialInstWithPayload = undefined;
|
|
||||||
z = PartialInstWithPayload{ .Compiled = 1234 };
|
|
||||||
switch (z) {
|
|
||||||
PartialInstWithPayload.Compiled => |x| {
|
|
||||||
try expect(x == 1234);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PartialInst = union(enum) {
|
|
||||||
Compiled,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PartialInstWithPayload = union(enum) {
|
|
||||||
Compiled: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "access a member of tagged union with conflicting enum tag name" {
|
|
||||||
const Bar = union(enum) {
|
|
||||||
A: A,
|
|
||||||
B: B,
|
|
||||||
|
|
||||||
const A = u8;
|
|
||||||
const B = void;
|
|
||||||
};
|
|
||||||
|
|
||||||
comptime try expect(Bar.A == u8);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "tagged union initialization with runtime void" {
|
|
||||||
try expect(testTaggedUnionInit({}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const TaggedUnionWithAVoid = union(enum) {
|
|
||||||
A,
|
|
||||||
B: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn testTaggedUnionInit(x: anytype) bool {
|
|
||||||
const y = TaggedUnionWithAVoid{ .A = x };
|
|
||||||
return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const UnionEnumNoPayloads = union(enum) {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "tagged union with no payloads" {
|
|
||||||
const a = UnionEnumNoPayloads{ .B = {} };
|
|
||||||
switch (a) {
|
|
||||||
Tag(UnionEnumNoPayloads).A => @panic("wrong"),
|
|
||||||
Tag(UnionEnumNoPayloads).B => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "union with only 1 field casted to its enum type" {
|
|
||||||
const Literal = union(enum) {
|
|
||||||
Number: f64,
|
|
||||||
Bool: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Expr = union(enum) {
|
|
||||||
Literal: Literal,
|
|
||||||
};
|
|
||||||
|
|
||||||
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
|
||||||
const ExprTag = Tag(Expr);
|
|
||||||
comptime try expect(Tag(ExprTag) == u0);
|
|
||||||
var t = @as(ExprTag, e);
|
|
||||||
try expect(t == Expr.Literal);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "union with only 1 field casted to its enum type which has enum value specified" {
|
|
||||||
const Literal = union(enum) {
|
|
||||||
Number: f64,
|
|
||||||
Bool: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ExprTag = enum(comptime_int) {
|
|
||||||
Literal = 33,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Expr = union(ExprTag) {
|
|
||||||
Literal: Literal,
|
|
||||||
};
|
|
||||||
|
|
||||||
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
|
||||||
comptime try expect(Tag(ExprTag) == comptime_int);
|
|
||||||
var t = @as(ExprTag, e);
|
|
||||||
try expect(t == Expr.Literal);
|
|
||||||
try expect(@enumToInt(t) == 33);
|
|
||||||
comptime try expect(@enumToInt(t) == 33);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "@enumToInt works on unions" {
|
|
||||||
const Bar = union(enum) {
|
|
||||||
A: bool,
|
|
||||||
B: u8,
|
|
||||||
C,
|
|
||||||
};
|
|
||||||
|
|
||||||
const a = Bar{ .A = true };
|
|
||||||
var b = Bar{ .B = undefined };
|
|
||||||
var c = Bar.C;
|
|
||||||
try expect(@enumToInt(a) == 0);
|
|
||||||
try expect(@enumToInt(b) == 1);
|
|
||||||
try expect(@enumToInt(c) == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Attribute = union(enum) {
|
|
||||||
A: bool,
|
|
||||||
B: u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn setAttribute(attr: Attribute) void {
|
|
||||||
_ = attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Setter(attr: Attribute) type {
|
|
||||||
return struct {
|
|
||||||
fn set() void {
|
|
||||||
setAttribute(attr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "comptime union field value equality" {
|
|
||||||
const a0 = Setter(Attribute{ .A = false });
|
|
||||||
const a1 = Setter(Attribute{ .A = true });
|
|
||||||
const a2 = Setter(Attribute{ .A = false });
|
|
||||||
|
|
||||||
const b0 = Setter(Attribute{ .B = 5 });
|
|
||||||
const b1 = Setter(Attribute{ .B = 9 });
|
|
||||||
const b2 = Setter(Attribute{ .B = 5 });
|
|
||||||
|
|
||||||
try expect(a0 == a0);
|
|
||||||
try expect(a1 == a1);
|
|
||||||
try expect(a0 == a2);
|
|
||||||
|
|
||||||
try expect(b0 == b0);
|
|
||||||
try expect(b1 == b1);
|
|
||||||
try expect(b0 == b2);
|
|
||||||
|
|
||||||
try expect(a0 != b0);
|
|
||||||
try expect(a0 != a1);
|
|
||||||
try expect(b0 != b1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "return union init with void payload" {
|
|
||||||
const S = struct {
|
|
||||||
fn entry() !void {
|
|
||||||
try expect(func().state == State.one);
|
|
||||||
}
|
|
||||||
const Outer = union(enum) {
|
|
||||||
state: State,
|
|
||||||
};
|
|
||||||
const State = union(enum) {
|
|
||||||
one: void,
|
|
||||||
two: u32,
|
|
||||||
};
|
|
||||||
fn func() Outer {
|
|
||||||
return Outer{ .state = State{ .one = {} } };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try S.entry();
|
|
||||||
comptime try S.entry();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "@unionInit can modify a union type" {
|
|
||||||
const UnionInitEnum = union(enum) {
|
|
||||||
Boolean: bool,
|
|
||||||
Byte: u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
var value: UnionInitEnum = undefined;
|
|
||||||
|
|
||||||
value = @unionInit(UnionInitEnum, "Boolean", true);
|
|
||||||
try expect(value.Boolean == true);
|
|
||||||
value.Boolean = false;
|
|
||||||
try expect(value.Boolean == false);
|
|
||||||
|
|
||||||
value = @unionInit(UnionInitEnum, "Byte", 2);
|
|
||||||
try expect(value.Byte == 2);
|
|
||||||
value.Byte = 3;
|
|
||||||
try expect(value.Byte == 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "@unionInit can modify a pointer value" {
|
|
||||||
const UnionInitEnum = union(enum) {
|
|
||||||
Boolean: bool,
|
|
||||||
Byte: u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
var value: UnionInitEnum = undefined;
|
|
||||||
var value_ptr = &value;
|
|
||||||
|
|
||||||
value_ptr.* = @unionInit(UnionInitEnum, "Boolean", true);
|
|
||||||
try expect(value.Boolean == true);
|
|
||||||
|
|
||||||
value_ptr.* = @unionInit(UnionInitEnum, "Byte", 2);
|
|
||||||
try expect(value.Byte == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "union no tag with struct member" {
|
|
||||||
const Struct = struct {};
|
|
||||||
const Union = union {
|
|
||||||
s: Struct,
|
|
||||||
pub fn foo(self: *@This()) void {
|
|
||||||
_ = self;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var u = Union{ .s = Struct{} };
|
|
||||||
u.foo();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testComparison() !void {
|
|
||||||
var x = Payload{ .A = 42 };
|
|
||||||
try expect(x == .A);
|
|
||||||
try expect(x != .B);
|
|
||||||
try expect(x != .C);
|
|
||||||
try expect((x == .B) == false);
|
|
||||||
try expect((x == .C) == false);
|
|
||||||
try expect((x != .A) == false);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "comparison between union and enum literal" {
|
|
||||||
try testComparison();
|
|
||||||
comptime try testComparison();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "packed union generates correctly aligned LLVM type" {
|
|
||||||
const U = packed union {
|
|
||||||
f1: fn () error{TestUnexpectedResult}!void,
|
|
||||||
f2: u32,
|
|
||||||
};
|
|
||||||
var foo = [_]U{
|
|
||||||
U{ .f1 = doTest },
|
|
||||||
U{ .f2 = 0 },
|
|
||||||
};
|
|
||||||
try foo[0].f1();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "union with one member defaults to u0 tag type" {
|
|
||||||
const U0 = union(enum) {
|
|
||||||
X: u32,
|
|
||||||
};
|
|
||||||
comptime try expect(Tag(Tag(U0)) == u0);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "union with comptime_int tag" {
|
|
||||||
const Union = union(enum(comptime_int)) {
|
|
||||||
X: u32,
|
|
||||||
Y: u16,
|
|
||||||
Z: u8,
|
|
||||||
};
|
|
||||||
comptime try expect(Tag(Tag(Union)) == comptime_int);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "extern union doesn't trigger field check at comptime" {
|
|
||||||
const U = extern union {
|
|
||||||
x: u32,
|
|
||||||
y: u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const x = U{ .x = 0x55AAAA55 };
|
|
||||||
comptime try expect(x.y == 0x55);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Foo1 = union(enum) {
|
|
||||||
f: struct {
|
|
||||||
x: usize,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var glbl: Foo1 = undefined;
|
|
||||||
|
|
||||||
test "global union with single field is correctly initialized" {
|
|
||||||
glbl = Foo1{
|
|
||||||
.f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 },
|
|
||||||
};
|
|
||||||
try expect(glbl.f.x == 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const FooUnion = union(enum) {
|
|
||||||
U0: usize,
|
|
||||||
U1: u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
var glbl_array: [2]FooUnion = undefined;
|
|
||||||
|
|
||||||
test "initialize global array of union" {
|
|
||||||
glbl_array[1] = FooUnion{ .U1 = 2 };
|
|
||||||
glbl_array[0] = FooUnion{ .U0 = 1 };
|
|
||||||
try expect(glbl_array[0].U0 == 1);
|
|
||||||
try expect(glbl_array[1].U1 == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "anonymous union literal syntax" {
|
|
||||||
const S = struct {
|
|
||||||
const Number = union {
|
|
||||||
int: i32,
|
|
||||||
float: f64,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var i: Number = .{ .int = 42 };
|
|
||||||
var f = makeNumber();
|
|
||||||
try expect(i.int == 42);
|
|
||||||
try expect(f.float == 12.34);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn makeNumber() Number {
|
|
||||||
return .{ .float = 12.34 };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "update the tag value for zero-sized unions" {
|
|
||||||
const S = union(enum) {
|
|
||||||
U0: void,
|
|
||||||
U1: void,
|
|
||||||
};
|
|
||||||
var x = S{ .U0 = {} };
|
|
||||||
try expect(x == .U0);
|
|
||||||
x = S{ .U1 = {} };
|
|
||||||
try expect(x == .U1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "function call result coerces from tagged union to the tag" {
|
|
||||||
const S = struct {
|
|
||||||
const Arch = union(enum) {
|
|
||||||
One,
|
|
||||||
Two: usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ArchTag = Tag(Arch);
|
|
||||||
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var x: ArchTag = getArch1();
|
|
||||||
try expect(x == .One);
|
|
||||||
|
|
||||||
var y: ArchTag = getArch2();
|
|
||||||
try expect(y == .Two);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getArch1() Arch {
|
|
||||||
return .One;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getArch2() Arch {
|
|
||||||
return .{ .Two = 99 };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "0-sized extern union definition" {
|
|
||||||
const U = extern union {
|
|
||||||
a: void,
|
|
||||||
const f = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
try expect(U.f == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "union initializer generates padding only if needed" {
|
|
||||||
const U = union(enum) {
|
|
||||||
A: u24,
|
|
||||||
};
|
|
||||||
|
|
||||||
var v = U{ .A = 532 };
|
|
||||||
try expect(v.A == 532);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "runtime tag name with single field" {
|
|
||||||
const U = union(enum) {
|
|
||||||
A: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
var v = U{ .A = 42 };
|
|
||||||
try expect(std.mem.eql(u8, @tagName(v), "A"));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "cast from anonymous struct to union" {
|
|
||||||
const S = struct {
|
|
||||||
const U = union(enum) {
|
|
||||||
A: u32,
|
|
||||||
B: []const u8,
|
|
||||||
C: void,
|
|
||||||
};
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var y: u32 = 42;
|
|
||||||
const t0 = .{ .A = 123 };
|
|
||||||
const t1 = .{ .B = "foo" };
|
|
||||||
const t2 = .{ .C = {} };
|
|
||||||
const t3 = .{ .A = y };
|
|
||||||
const x0: U = t0;
|
|
||||||
var x1: U = t1;
|
|
||||||
const x2: U = t2;
|
|
||||||
var x3: U = t3;
|
|
||||||
try expect(x0.A == 123);
|
|
||||||
try expect(std.mem.eql(u8, x1.B, "foo"));
|
|
||||||
try expect(x2 == .C);
|
|
||||||
try expect(x3.A == y);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "cast from pointer to anonymous struct to pointer to union" {
|
|
||||||
const S = struct {
|
|
||||||
const U = union(enum) {
|
|
||||||
A: u32,
|
|
||||||
B: []const u8,
|
|
||||||
C: void,
|
|
||||||
};
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var y: u32 = 42;
|
|
||||||
const t0 = &.{ .A = 123 };
|
|
||||||
const t1 = &.{ .B = "foo" };
|
|
||||||
const t2 = &.{ .C = {} };
|
|
||||||
const t3 = &.{ .A = y };
|
|
||||||
const x0: *const U = t0;
|
|
||||||
var x1: *const U = t1;
|
|
||||||
const x2: *const U = t2;
|
|
||||||
var x3: *const U = t3;
|
|
||||||
try expect(x0.A == 123);
|
|
||||||
try expect(std.mem.eql(u8, x1.B, "foo"));
|
|
||||||
try expect(x2.* == .C);
|
|
||||||
try expect(x3.A == y);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "method call on an empty union" {
|
|
||||||
const S = struct {
|
|
||||||
const MyUnion = union(MyUnionTag) {
|
|
||||||
pub const MyUnionTag = enum { X1, X2 };
|
|
||||||
X1: [0]u8,
|
|
||||||
X2: [0]u8,
|
|
||||||
|
|
||||||
pub fn useIt(self: *@This()) bool {
|
|
||||||
_ = self;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var u = MyUnion{ .X1 = [0]u8{} };
|
|
||||||
try expect(u.useIt());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "switching on non exhaustive union" {
|
|
||||||
const S = struct {
|
|
||||||
const E = enum(u8) {
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
_,
|
|
||||||
};
|
|
||||||
const U = union(E) {
|
|
||||||
a: i32,
|
|
||||||
b: u32,
|
|
||||||
};
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var a = U{ .a = 2 };
|
|
||||||
switch (a) {
|
|
||||||
.a => |val| try expect(val == 2),
|
|
||||||
.b => unreachable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "containers with single-field enums" {
|
|
||||||
const S = struct {
|
|
||||||
const A = union(enum) { f1 };
|
|
||||||
const B = union(enum) { f1: void };
|
|
||||||
const C = struct { a: A };
|
|
||||||
const D = struct { a: B };
|
|
||||||
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var array1 = [1]A{A{ .f1 = {} }};
|
|
||||||
var array2 = [1]B{B{ .f1 = {} }};
|
|
||||||
try expect(array1[0] == .f1);
|
|
||||||
try expect(array2[0] == .f1);
|
|
||||||
|
|
||||||
var struct1 = C{ .a = A{ .f1 = {} } };
|
|
||||||
var struct2 = D{ .a = B{ .f1 = {} } };
|
|
||||||
try expect(struct1.a == .f1);
|
|
||||||
try expect(struct2.a == .f1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "@unionInit on union w/ tag but no fields" {
|
|
||||||
const S = struct {
|
|
||||||
const Type = enum(u8) { no_op = 105 };
|
|
||||||
|
|
||||||
const Data = union(Type) {
|
|
||||||
no_op: void,
|
|
||||||
|
|
||||||
pub fn decode(buf: []const u8) Data {
|
|
||||||
_ = buf;
|
|
||||||
return @unionInit(Data, "no_op", {});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
comptime {
|
|
||||||
std.debug.assert(@sizeOf(Data) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn doTheTest() !void {
|
|
||||||
var data: Data = .{ .no_op = .{} };
|
|
||||||
_ = data;
|
|
||||||
var o = Data.decode(&[_]u8{});
|
|
||||||
try expectEqual(Type.no_op, o);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try S.doTheTest();
|
|
||||||
comptime try S.doTheTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "union enum type gets a separate scope" {
|
|
||||||
const S = struct {
|
|
||||||
const U = union(enum) {
|
|
||||||
a: u8,
|
|
||||||
const foo = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn doTheTest() !void {
|
|
||||||
try expect(!@hasDecl(Tag(U), "foo"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try S.doTheTest();
|
|
||||||
}
|
|
||||||
test "anytype union field: issue #9233" {
|
|
||||||
const Baz = union(enum) { bar: anytype };
|
|
||||||
_ = Baz;
|
|
||||||
}
|
|
||||||
|
|||||||
799
test/behavior/union_stage1.zig
Normal file
799
test/behavior/union_stage1.zig
Normal file
@ -0,0 +1,799 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
const expectEqual = std.testing.expectEqual;
|
||||||
|
const Tag = std.meta.Tag;
|
||||||
|
|
||||||
|
const Value = union(enum) {
|
||||||
|
Int: u64,
|
||||||
|
Array: [9]u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Agg = struct {
|
||||||
|
val1: Value,
|
||||||
|
val2: Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
const v1 = Value{ .Int = 1234 };
|
||||||
|
const v2 = Value{ .Array = [_]u8{3} ** 9 };
|
||||||
|
|
||||||
|
const err = @as(anyerror!Agg, Agg{
|
||||||
|
.val1 = v1,
|
||||||
|
.val2 = v2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const array = [_]Value{ v1, v2, v1, v2 };
|
||||||
|
|
||||||
|
test "unions embedded in aggregate types" {
|
||||||
|
switch (array[1]) {
|
||||||
|
Value.Array => |arr| try expect(arr[4] == 3),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
switch ((err catch unreachable).val1) {
|
||||||
|
Value.Int => |x| try expect(x == 1234),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Foo = union {
|
||||||
|
float: f64,
|
||||||
|
int: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "basic unions" {
|
||||||
|
var foo = Foo{ .int = 1 };
|
||||||
|
try expect(foo.int == 1);
|
||||||
|
foo = Foo{ .float = 12.34 };
|
||||||
|
try expect(foo.float == 12.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "comptime union field access" {
|
||||||
|
comptime {
|
||||||
|
var foo = Foo{ .int = 0 };
|
||||||
|
try expect(foo.int == 0);
|
||||||
|
|
||||||
|
foo = Foo{ .float = 42.42 };
|
||||||
|
try expect(foo.float == 42.42);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "init union with runtime value" {
|
||||||
|
var foo: Foo = undefined;
|
||||||
|
|
||||||
|
setFloat(&foo, 12.34);
|
||||||
|
try expect(foo.float == 12.34);
|
||||||
|
|
||||||
|
setInt(&foo, 42);
|
||||||
|
try expect(foo.int == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setFloat(foo: *Foo, x: f64) void {
|
||||||
|
foo.* = Foo{ .float = x };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setInt(foo: *Foo, x: i32) void {
|
||||||
|
foo.* = Foo{ .int = x };
|
||||||
|
}
|
||||||
|
|
||||||
|
const FooExtern = extern union {
|
||||||
|
float: f64,
|
||||||
|
int: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "basic extern unions" {
|
||||||
|
var foo = FooExtern{ .int = 1 };
|
||||||
|
try expect(foo.int == 1);
|
||||||
|
foo.float = 12.34;
|
||||||
|
try expect(foo.float == 12.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Letter = enum { A, B, C };
|
||||||
|
const Payload = union(Letter) {
|
||||||
|
A: i32,
|
||||||
|
B: f64,
|
||||||
|
C: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "union with specified enum tag" {
|
||||||
|
try doTest();
|
||||||
|
comptime try doTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doTest() error{TestUnexpectedResult}!void {
|
||||||
|
try expect((try bar(Payload{ .A = 1234 })) == -10);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(value: Payload) error{TestUnexpectedResult}!i32 {
|
||||||
|
try expect(@as(Letter, value) == Letter.A);
|
||||||
|
return switch (value) {
|
||||||
|
Payload.A => |x| return x - 1244,
|
||||||
|
Payload.B => |x| if (x == 12.34) @as(i32, 20) else 21,
|
||||||
|
Payload.C => |x| if (x) @as(i32, 30) else 31,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const MultipleChoice = union(enum(u32)) {
|
||||||
|
A = 20,
|
||||||
|
B = 40,
|
||||||
|
C = 60,
|
||||||
|
D = 1000,
|
||||||
|
};
|
||||||
|
test "simple union(enum(u32))" {
|
||||||
|
var x = MultipleChoice.C;
|
||||||
|
try expect(x == MultipleChoice.C);
|
||||||
|
try expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MultipleChoice2 = union(enum(u32)) {
|
||||||
|
Unspecified1: i32,
|
||||||
|
A: f32 = 20,
|
||||||
|
Unspecified2: void,
|
||||||
|
B: bool = 40,
|
||||||
|
Unspecified3: i32,
|
||||||
|
C: i8 = 60,
|
||||||
|
Unspecified4: void,
|
||||||
|
D: void = 1000,
|
||||||
|
Unspecified5: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "union(enum(u32)) with specified and unspecified tag values" {
|
||||||
|
comptime try expect(Tag(Tag(MultipleChoice2)) == u32);
|
||||||
|
try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
||||||
|
comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
|
||||||
|
try expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60);
|
||||||
|
try expect(1123 == switch (x) {
|
||||||
|
MultipleChoice2.A => 1,
|
||||||
|
MultipleChoice2.B => 2,
|
||||||
|
MultipleChoice2.C => |v| @as(i32, 1000) + v,
|
||||||
|
MultipleChoice2.D => 4,
|
||||||
|
MultipleChoice2.Unspecified1 => 5,
|
||||||
|
MultipleChoice2.Unspecified2 => 6,
|
||||||
|
MultipleChoice2.Unspecified3 => 7,
|
||||||
|
MultipleChoice2.Unspecified4 => 8,
|
||||||
|
MultipleChoice2.Unspecified5 => 9,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExternPtrOrInt = extern union {
|
||||||
|
ptr: *u8,
|
||||||
|
int: u64,
|
||||||
|
};
|
||||||
|
test "extern union size" {
|
||||||
|
comptime try expect(@sizeOf(ExternPtrOrInt) == 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PackedPtrOrInt = packed union {
|
||||||
|
ptr: *u8,
|
||||||
|
int: u64,
|
||||||
|
};
|
||||||
|
test "extern union size" {
|
||||||
|
comptime try expect(@sizeOf(PackedPtrOrInt) == 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ZeroBits = union {
|
||||||
|
OnlyField: void,
|
||||||
|
};
|
||||||
|
test "union with only 1 field which is void should be zero bits" {
|
||||||
|
comptime try expect(@sizeOf(ZeroBits) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TheTag = enum { A, B, C };
|
||||||
|
const TheUnion = union(TheTag) {
|
||||||
|
A: i32,
|
||||||
|
B: i32,
|
||||||
|
C: i32,
|
||||||
|
};
|
||||||
|
test "union field access gives the enum values" {
|
||||||
|
try expect(TheUnion.A == TheTag.A);
|
||||||
|
try expect(TheUnion.B == TheTag.B);
|
||||||
|
try expect(TheUnion.C == TheTag.C);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cast union to tag type of union" {
|
||||||
|
try testCastUnionToTag(TheUnion{ .B = 1234 });
|
||||||
|
comptime try testCastUnionToTag(TheUnion{ .B = 1234 });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testCastUnionToTag(x: TheUnion) !void {
|
||||||
|
try expect(@as(TheTag, x) == TheTag.B);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cast tag type of union to union" {
|
||||||
|
var x: Value2 = Letter2.B;
|
||||||
|
try expect(@as(Letter2, x) == Letter2.B);
|
||||||
|
}
|
||||||
|
const Letter2 = enum { A, B, C };
|
||||||
|
const Value2 = union(Letter2) {
|
||||||
|
A: i32,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "implicit cast union to its tag type" {
|
||||||
|
var x: Value2 = Letter2.B;
|
||||||
|
try expect(x == Letter2.B);
|
||||||
|
try giveMeLetterB(x);
|
||||||
|
}
|
||||||
|
fn giveMeLetterB(x: Letter2) !void {
|
||||||
|
try expect(x == Value2.B);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO it looks like this test intended to test packed unions, but this is not a packed
|
||||||
|
// union. go through git history and find out what happened.
|
||||||
|
pub const PackThis = union(enum) {
|
||||||
|
Invalid: bool,
|
||||||
|
StringLiteral: u2,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "constant packed union" {
|
||||||
|
try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testConstPackedUnion(expected_tokens: []const PackThis) !void {
|
||||||
|
try expect(expected_tokens[0].StringLiteral == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "switch on union with only 1 field" {
|
||||||
|
var r: PartialInst = undefined;
|
||||||
|
r = PartialInst.Compiled;
|
||||||
|
switch (r) {
|
||||||
|
PartialInst.Compiled => {
|
||||||
|
var z: PartialInstWithPayload = undefined;
|
||||||
|
z = PartialInstWithPayload{ .Compiled = 1234 };
|
||||||
|
switch (z) {
|
||||||
|
PartialInstWithPayload.Compiled => |x| {
|
||||||
|
try expect(x == 1234);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PartialInst = union(enum) {
|
||||||
|
Compiled,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PartialInstWithPayload = union(enum) {
|
||||||
|
Compiled: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "access a member of tagged union with conflicting enum tag name" {
|
||||||
|
const Bar = union(enum) {
|
||||||
|
A: A,
|
||||||
|
B: B,
|
||||||
|
|
||||||
|
const A = u8;
|
||||||
|
const B = void;
|
||||||
|
};
|
||||||
|
|
||||||
|
comptime try expect(Bar.A == u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "tagged union initialization with runtime void" {
|
||||||
|
try expect(testTaggedUnionInit({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaggedUnionWithAVoid = union(enum) {
|
||||||
|
A,
|
||||||
|
B: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn testTaggedUnionInit(x: anytype) bool {
|
||||||
|
const y = TaggedUnionWithAVoid{ .A = x };
|
||||||
|
return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const UnionEnumNoPayloads = union(enum) { A, B };
|
||||||
|
|
||||||
|
test "tagged union with no payloads" {
|
||||||
|
const a = UnionEnumNoPayloads{ .B = {} };
|
||||||
|
switch (a) {
|
||||||
|
Tag(UnionEnumNoPayloads).A => @panic("wrong"),
|
||||||
|
Tag(UnionEnumNoPayloads).B => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union with only 1 field casted to its enum type" {
|
||||||
|
const Literal = union(enum) {
|
||||||
|
Number: f64,
|
||||||
|
Bool: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Expr = union(enum) {
|
||||||
|
Literal: Literal,
|
||||||
|
};
|
||||||
|
|
||||||
|
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
||||||
|
const ExprTag = Tag(Expr);
|
||||||
|
comptime try expect(Tag(ExprTag) == u0);
|
||||||
|
var t = @as(ExprTag, e);
|
||||||
|
try expect(t == Expr.Literal);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union with only 1 field casted to its enum type which has enum value specified" {
|
||||||
|
const Literal = union(enum) {
|
||||||
|
Number: f64,
|
||||||
|
Bool: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExprTag = enum(comptime_int) {
|
||||||
|
Literal = 33,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Expr = union(ExprTag) {
|
||||||
|
Literal: Literal,
|
||||||
|
};
|
||||||
|
|
||||||
|
var e = Expr{ .Literal = Literal{ .Bool = true } };
|
||||||
|
comptime try expect(Tag(ExprTag) == comptime_int);
|
||||||
|
var t = @as(ExprTag, e);
|
||||||
|
try expect(t == Expr.Literal);
|
||||||
|
try expect(@enumToInt(t) == 33);
|
||||||
|
comptime try expect(@enumToInt(t) == 33);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "@enumToInt works on unions" {
|
||||||
|
const Bar = union(enum) {
|
||||||
|
A: bool,
|
||||||
|
B: u8,
|
||||||
|
C,
|
||||||
|
};
|
||||||
|
|
||||||
|
const a = Bar{ .A = true };
|
||||||
|
var b = Bar{ .B = undefined };
|
||||||
|
var c = Bar.C;
|
||||||
|
try expect(@enumToInt(a) == 0);
|
||||||
|
try expect(@enumToInt(b) == 1);
|
||||||
|
try expect(@enumToInt(c) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Attribute = union(enum) {
|
||||||
|
A: bool,
|
||||||
|
B: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn setAttribute(attr: Attribute) void {
|
||||||
|
_ = attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Setter(attr: Attribute) type {
|
||||||
|
return struct {
|
||||||
|
fn set() void {
|
||||||
|
setAttribute(attr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "comptime union field value equality" {
|
||||||
|
const a0 = Setter(Attribute{ .A = false });
|
||||||
|
const a1 = Setter(Attribute{ .A = true });
|
||||||
|
const a2 = Setter(Attribute{ .A = false });
|
||||||
|
|
||||||
|
const b0 = Setter(Attribute{ .B = 5 });
|
||||||
|
const b1 = Setter(Attribute{ .B = 9 });
|
||||||
|
const b2 = Setter(Attribute{ .B = 5 });
|
||||||
|
|
||||||
|
try expect(a0 == a0);
|
||||||
|
try expect(a1 == a1);
|
||||||
|
try expect(a0 == a2);
|
||||||
|
|
||||||
|
try expect(b0 == b0);
|
||||||
|
try expect(b1 == b1);
|
||||||
|
try expect(b0 == b2);
|
||||||
|
|
||||||
|
try expect(a0 != b0);
|
||||||
|
try expect(a0 != a1);
|
||||||
|
try expect(b0 != b1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "return union init with void payload" {
|
||||||
|
const S = struct {
|
||||||
|
fn entry() !void {
|
||||||
|
try expect(func().state == State.one);
|
||||||
|
}
|
||||||
|
const Outer = union(enum) {
|
||||||
|
state: State,
|
||||||
|
};
|
||||||
|
const State = union(enum) {
|
||||||
|
one: void,
|
||||||
|
two: u32,
|
||||||
|
};
|
||||||
|
fn func() Outer {
|
||||||
|
return Outer{ .state = State{ .one = {} } };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.entry();
|
||||||
|
comptime try S.entry();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "@unionInit can modify a union type" {
|
||||||
|
const UnionInitEnum = union(enum) {
|
||||||
|
Boolean: bool,
|
||||||
|
Byte: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
var value: UnionInitEnum = undefined;
|
||||||
|
|
||||||
|
value = @unionInit(UnionInitEnum, "Boolean", true);
|
||||||
|
try expect(value.Boolean == true);
|
||||||
|
value.Boolean = false;
|
||||||
|
try expect(value.Boolean == false);
|
||||||
|
|
||||||
|
value = @unionInit(UnionInitEnum, "Byte", 2);
|
||||||
|
try expect(value.Byte == 2);
|
||||||
|
value.Byte = 3;
|
||||||
|
try expect(value.Byte == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "@unionInit can modify a pointer value" {
|
||||||
|
const UnionInitEnum = union(enum) {
|
||||||
|
Boolean: bool,
|
||||||
|
Byte: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
var value: UnionInitEnum = undefined;
|
||||||
|
var value_ptr = &value;
|
||||||
|
|
||||||
|
value_ptr.* = @unionInit(UnionInitEnum, "Boolean", true);
|
||||||
|
try expect(value.Boolean == true);
|
||||||
|
|
||||||
|
value_ptr.* = @unionInit(UnionInitEnum, "Byte", 2);
|
||||||
|
try expect(value.Byte == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union no tag with struct member" {
|
||||||
|
const Struct = struct {};
|
||||||
|
const Union = union {
|
||||||
|
s: Struct,
|
||||||
|
pub fn foo(self: *@This()) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var u = Union{ .s = Struct{} };
|
||||||
|
u.foo();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testComparison() !void {
|
||||||
|
var x = Payload{ .A = 42 };
|
||||||
|
try expect(x == .A);
|
||||||
|
try expect(x != .B);
|
||||||
|
try expect(x != .C);
|
||||||
|
try expect((x == .B) == false);
|
||||||
|
try expect((x == .C) == false);
|
||||||
|
try expect((x != .A) == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "comparison between union and enum literal" {
|
||||||
|
try testComparison();
|
||||||
|
comptime try testComparison();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "packed union generates correctly aligned LLVM type" {
|
||||||
|
const U = packed union {
|
||||||
|
f1: fn () error{TestUnexpectedResult}!void,
|
||||||
|
f2: u32,
|
||||||
|
};
|
||||||
|
var foo = [_]U{
|
||||||
|
U{ .f1 = doTest },
|
||||||
|
U{ .f2 = 0 },
|
||||||
|
};
|
||||||
|
try foo[0].f1();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union with one member defaults to u0 tag type" {
|
||||||
|
const U0 = union(enum) {
|
||||||
|
X: u32,
|
||||||
|
};
|
||||||
|
comptime try expect(Tag(Tag(U0)) == u0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union with comptime_int tag" {
|
||||||
|
const Union = union(enum(comptime_int)) {
|
||||||
|
X: u32,
|
||||||
|
Y: u16,
|
||||||
|
Z: u8,
|
||||||
|
};
|
||||||
|
comptime try expect(Tag(Tag(Union)) == comptime_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "extern union doesn't trigger field check at comptime" {
|
||||||
|
const U = extern union {
|
||||||
|
x: u32,
|
||||||
|
y: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const x = U{ .x = 0x55AAAA55 };
|
||||||
|
comptime try expect(x.y == 0x55);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Foo1 = union(enum) {
|
||||||
|
f: struct {
|
||||||
|
x: usize,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var glbl: Foo1 = undefined;
|
||||||
|
|
||||||
|
test "global union with single field is correctly initialized" {
|
||||||
|
glbl = Foo1{
|
||||||
|
.f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 },
|
||||||
|
};
|
||||||
|
try expect(glbl.f.x == 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const FooUnion = union(enum) {
|
||||||
|
U0: usize,
|
||||||
|
U1: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
var glbl_array: [2]FooUnion = undefined;
|
||||||
|
|
||||||
|
test "initialize global array of union" {
|
||||||
|
glbl_array[1] = FooUnion{ .U1 = 2 };
|
||||||
|
glbl_array[0] = FooUnion{ .U0 = 1 };
|
||||||
|
try expect(glbl_array[0].U0 == 1);
|
||||||
|
try expect(glbl_array[1].U1 == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "anonymous union literal syntax" {
|
||||||
|
const S = struct {
|
||||||
|
const Number = union {
|
||||||
|
int: i32,
|
||||||
|
float: f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var i: Number = .{ .int = 42 };
|
||||||
|
var f = makeNumber();
|
||||||
|
try expect(i.int == 42);
|
||||||
|
try expect(f.float == 12.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeNumber() Number {
|
||||||
|
return .{ .float = 12.34 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "update the tag value for zero-sized unions" {
|
||||||
|
const S = union(enum) {
|
||||||
|
U0: void,
|
||||||
|
U1: void,
|
||||||
|
};
|
||||||
|
var x = S{ .U0 = {} };
|
||||||
|
try expect(x == .U0);
|
||||||
|
x = S{ .U1 = {} };
|
||||||
|
try expect(x == .U1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "function call result coerces from tagged union to the tag" {
|
||||||
|
const S = struct {
|
||||||
|
const Arch = union(enum) {
|
||||||
|
One,
|
||||||
|
Two: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArchTag = Tag(Arch);
|
||||||
|
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var x: ArchTag = getArch1();
|
||||||
|
try expect(x == .One);
|
||||||
|
|
||||||
|
var y: ArchTag = getArch2();
|
||||||
|
try expect(y == .Two);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getArch1() Arch {
|
||||||
|
return .One;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getArch2() Arch {
|
||||||
|
return .{ .Two = 99 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "0-sized extern union definition" {
|
||||||
|
const U = extern union {
|
||||||
|
a: void,
|
||||||
|
const f = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
try expect(U.f == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union initializer generates padding only if needed" {
|
||||||
|
const U = union(enum) {
|
||||||
|
A: u24,
|
||||||
|
};
|
||||||
|
|
||||||
|
var v = U{ .A = 532 };
|
||||||
|
try expect(v.A == 532);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "runtime tag name with single field" {
|
||||||
|
const U = union(enum) {
|
||||||
|
A: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
var v = U{ .A = 42 };
|
||||||
|
try expect(std.mem.eql(u8, @tagName(v), "A"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cast from anonymous struct to union" {
|
||||||
|
const S = struct {
|
||||||
|
const U = union(enum) {
|
||||||
|
A: u32,
|
||||||
|
B: []const u8,
|
||||||
|
C: void,
|
||||||
|
};
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var y: u32 = 42;
|
||||||
|
const t0 = .{ .A = 123 };
|
||||||
|
const t1 = .{ .B = "foo" };
|
||||||
|
const t2 = .{ .C = {} };
|
||||||
|
const t3 = .{ .A = y };
|
||||||
|
const x0: U = t0;
|
||||||
|
var x1: U = t1;
|
||||||
|
const x2: U = t2;
|
||||||
|
var x3: U = t3;
|
||||||
|
try expect(x0.A == 123);
|
||||||
|
try expect(std.mem.eql(u8, x1.B, "foo"));
|
||||||
|
try expect(x2 == .C);
|
||||||
|
try expect(x3.A == y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cast from pointer to anonymous struct to pointer to union" {
|
||||||
|
const S = struct {
|
||||||
|
const U = union(enum) {
|
||||||
|
A: u32,
|
||||||
|
B: []const u8,
|
||||||
|
C: void,
|
||||||
|
};
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var y: u32 = 42;
|
||||||
|
const t0 = &.{ .A = 123 };
|
||||||
|
const t1 = &.{ .B = "foo" };
|
||||||
|
const t2 = &.{ .C = {} };
|
||||||
|
const t3 = &.{ .A = y };
|
||||||
|
const x0: *const U = t0;
|
||||||
|
var x1: *const U = t1;
|
||||||
|
const x2: *const U = t2;
|
||||||
|
var x3: *const U = t3;
|
||||||
|
try expect(x0.A == 123);
|
||||||
|
try expect(std.mem.eql(u8, x1.B, "foo"));
|
||||||
|
try expect(x2.* == .C);
|
||||||
|
try expect(x3.A == y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "method call on an empty union" {
|
||||||
|
const S = struct {
|
||||||
|
const MyUnion = union(MyUnionTag) {
|
||||||
|
pub const MyUnionTag = enum { X1, X2 };
|
||||||
|
X1: [0]u8,
|
||||||
|
X2: [0]u8,
|
||||||
|
|
||||||
|
pub fn useIt(self: *@This()) bool {
|
||||||
|
_ = self;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var u = MyUnion{ .X1 = [0]u8{} };
|
||||||
|
try expect(u.useIt());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "switching on non exhaustive union" {
|
||||||
|
const S = struct {
|
||||||
|
const E = enum(u8) {
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
const U = union(E) {
|
||||||
|
a: i32,
|
||||||
|
b: u32,
|
||||||
|
};
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var a = U{ .a = 2 };
|
||||||
|
switch (a) {
|
||||||
|
.a => |val| try expect(val == 2),
|
||||||
|
.b => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "containers with single-field enums" {
|
||||||
|
const S = struct {
|
||||||
|
const A = union(enum) { f1 };
|
||||||
|
const B = union(enum) { f1: void };
|
||||||
|
const C = struct { a: A };
|
||||||
|
const D = struct { a: B };
|
||||||
|
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var array1 = [1]A{A{ .f1 = {} }};
|
||||||
|
var array2 = [1]B{B{ .f1 = {} }};
|
||||||
|
try expect(array1[0] == .f1);
|
||||||
|
try expect(array2[0] == .f1);
|
||||||
|
|
||||||
|
var struct1 = C{ .a = A{ .f1 = {} } };
|
||||||
|
var struct2 = D{ .a = B{ .f1 = {} } };
|
||||||
|
try expect(struct1.a == .f1);
|
||||||
|
try expect(struct2.a == .f1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "@unionInit on union w/ tag but no fields" {
|
||||||
|
const S = struct {
|
||||||
|
const Type = enum(u8) { no_op = 105 };
|
||||||
|
|
||||||
|
const Data = union(Type) {
|
||||||
|
no_op: void,
|
||||||
|
|
||||||
|
pub fn decode(buf: []const u8) Data {
|
||||||
|
_ = buf;
|
||||||
|
return @unionInit(Data, "no_op", {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
std.debug.assert(@sizeOf(Data) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doTheTest() !void {
|
||||||
|
var data: Data = .{ .no_op = .{} };
|
||||||
|
_ = data;
|
||||||
|
var o = Data.decode(&[_]u8{});
|
||||||
|
try expectEqual(Type.no_op, o);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try S.doTheTest();
|
||||||
|
comptime try S.doTheTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union enum type gets a separate scope" {
|
||||||
|
const S = struct {
|
||||||
|
const U = union(enum) {
|
||||||
|
a: u8,
|
||||||
|
const foo = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn doTheTest() !void {
|
||||||
|
try expect(!@hasDecl(Tag(U), "foo"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try S.doTheTest();
|
||||||
|
}
|
||||||
|
test "anytype union field: issue #9233" {
|
||||||
|
const Baz = union(enum) { bar: anytype };
|
||||||
|
_ = Baz;
|
||||||
|
}
|
||||||
@ -17,3 +17,46 @@ test "implicit unsigned integer to signed integer" {
|
|||||||
var b: i16 = a;
|
var b: i16 = a;
|
||||||
try expect(b == 250);
|
try expect(b == 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "float widening" {
|
||||||
|
if (@import("builtin").zig_is_stage2) {
|
||||||
|
// This test is passing but it depends on compiler-rt symbols, which
|
||||||
|
// cannot yet be built with stage2 due to
|
||||||
|
// "TODO implement equality comparison between a union's tag value and an enum literal"
|
||||||
|
return error.SkipZigTest;
|
||||||
|
}
|
||||||
|
var a: f16 = 12.34;
|
||||||
|
var b: f32 = a;
|
||||||
|
var c: f64 = b;
|
||||||
|
var d: f128 = c;
|
||||||
|
try expect(a == b);
|
||||||
|
try expect(b == c);
|
||||||
|
try expect(c == d);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "float widening f16 to f128" {
|
||||||
|
if (@import("builtin").zig_is_stage2) {
|
||||||
|
// This test is passing but it depends on compiler-rt symbols, which
|
||||||
|
// cannot yet be built with stage2 due to
|
||||||
|
// "TODO implement equality comparison between a union's tag value and an enum literal"
|
||||||
|
return error.SkipZigTest;
|
||||||
|
}
|
||||||
|
// TODO https://github.com/ziglang/zig/issues/3282
|
||||||
|
if (@import("builtin").stage2_arch == .aarch64) return error.SkipZigTest;
|
||||||
|
if (@import("builtin").stage2_arch == .powerpc64le) return error.SkipZigTest;
|
||||||
|
|
||||||
|
var x: f16 = 12.34;
|
||||||
|
var y: f128 = x;
|
||||||
|
try expect(x == y);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cast small unsigned to larger signed" {
|
||||||
|
try expect(castSmallUnsignedToLargerSigned1(200) == @as(i16, 200));
|
||||||
|
try expect(castSmallUnsignedToLargerSigned2(9999) == @as(i64, 9999));
|
||||||
|
}
|
||||||
|
fn castSmallUnsignedToLargerSigned1(x: u8) i16 {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
fn castSmallUnsignedToLargerSigned2(x: u16) i64 {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const expect = std.testing.expect;
|
|
||||||
const mem = std.mem;
|
|
||||||
|
|
||||||
test "float widening" {
|
|
||||||
var a: f16 = 12.34;
|
|
||||||
var b: f32 = a;
|
|
||||||
var c: f64 = b;
|
|
||||||
var d: f128 = c;
|
|
||||||
try expect(a == b);
|
|
||||||
try expect(b == c);
|
|
||||||
try expect(c == d);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "float widening f16 to f128" {
|
|
||||||
// TODO https://github.com/ziglang/zig/issues/3282
|
|
||||||
if (@import("builtin").stage2_arch == .aarch64) return error.SkipZigTest;
|
|
||||||
if (@import("builtin").stage2_arch == .powerpc64le) return error.SkipZigTest;
|
|
||||||
|
|
||||||
var x: f16 = 12.34;
|
|
||||||
var y: f128 = x;
|
|
||||||
try expect(x == y);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "cast small unsigned to larger signed" {
|
|
||||||
try expect(castSmallUnsignedToLargerSigned1(200) == @as(i16, 200));
|
|
||||||
try expect(castSmallUnsignedToLargerSigned2(9999) == @as(i64, 9999));
|
|
||||||
}
|
|
||||||
fn castSmallUnsignedToLargerSigned1(x: u8) i16 {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
fn castSmallUnsignedToLargerSigned2(x: u16) i64 {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user