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 builtin = std.builtin;
|
||||
const builtin = @import("builtin");
|
||||
const is_test = builtin.is_test;
|
||||
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 is_gnu = abi.isGnu();
|
||||
const is_mingw = os_tag == .windows and is_gnu;
|
||||
|
||||
comptime {
|
||||
const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak;
|
||||
const strong_linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong;
|
||||
const linkage = if (is_test)
|
||||
std.builtin.GlobalLinkage.Internal
|
||||
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) {
|
||||
.i386,
|
||||
.x86_64,
|
||||
@ -72,10 +89,14 @@ comptime {
|
||||
@export(__gtdf2, .{ .name = "__gtdf2", .linkage = linkage });
|
||||
@export(__getf2, .{ .name = "__gttf2", .linkage = linkage });
|
||||
|
||||
const __extendhfsf2 = @import("compiler_rt/extendXfYf2.zig").__extendhfsf2;
|
||||
@export(__extendhfsf2, .{ .name = "__gnu_h2f_ieee", .linkage = linkage });
|
||||
const __truncsfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncsfhf2;
|
||||
@export(__truncsfhf2, .{ .name = "__gnu_f2h_ieee", .linkage = linkage });
|
||||
@export(@import("compiler_rt/extendXfYf2.zig").__extendhfsf2, .{
|
||||
.name = "__gnu_h2f_ieee",
|
||||
.linkage = linkage,
|
||||
});
|
||||
@export(@import("compiler_rt/truncXfYf2.zig").__truncsfhf2, .{
|
||||
.name = "__gnu_f2h_ieee",
|
||||
.linkage = linkage,
|
||||
});
|
||||
}
|
||||
|
||||
const __unordsf2 = @import("compiler_rt/compareXf2.zig").__unordsf2;
|
||||
@ -166,15 +187,6 @@ comptime {
|
||||
const __floatuntisf = @import("compiler_rt/floatuntisf.zig").__floatuntisf;
|
||||
@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;
|
||||
@export(__truncsfhf2, .{ .name = "__truncsfhf2", .linkage = linkage });
|
||||
const __truncdfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncdfhf2;
|
||||
@ -609,12 +621,18 @@ comptime {
|
||||
|
||||
_ = @import("compiler_rt/atomics.zig");
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid dragging in the runtime safety mechanisms into this .o 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;
|
||||
@setCold(true);
|
||||
if (builtin.zig_is_stage2) {
|
||||
while (true) {
|
||||
@breakpoint();
|
||||
}
|
||||
}
|
||||
if (is_test) {
|
||||
std.debug.panic("{s}", .{msg});
|
||||
} else {
|
||||
|
||||
@ -3,23 +3,23 @@ const builtin = @import("builtin");
|
||||
const is_test = builtin.is_test;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f32, f16, a });
|
||||
return extendXfYf2(f32, f16, a);
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -34,7 +34,7 @@ pub fn __aeabi_f2d(arg: f32) callconv(.AAPCS) f64 {
|
||||
|
||||
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);
|
||||
|
||||
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.
|
||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||
unreach,
|
||||
/// Convert from one float type to another.
|
||||
/// Convert from a float type to a smaller one.
|
||||
/// 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
|
||||
/// fewer, the same, or more bits than the operand type. However, the instruction
|
||||
/// 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,
|
||||
.bitcast,
|
||||
.load,
|
||||
.floatcast,
|
||||
.fpext,
|
||||
.fptrunc,
|
||||
.intcast,
|
||||
.trunc,
|
||||
.optional_payload,
|
||||
|
||||
@ -2166,6 +2166,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
|
||||
.ensure_result_used,
|
||||
.ensure_result_non_error,
|
||||
.@"export",
|
||||
.export_value,
|
||||
.set_eval_branch_quota,
|
||||
.ensure_err_payload_void,
|
||||
.atomic_store,
|
||||
@ -7095,14 +7096,19 @@ fn builtinCall(
|
||||
.identifier => {
|
||||
const ident_token = main_tokens[params[0]];
|
||||
decl_name = try astgen.identAsString(ident_token);
|
||||
{
|
||||
|
||||
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) {
|
||||
.local_val => {
|
||||
const local_val = s.cast(Scope.LocalVal).?;
|
||||
if (local_val.name == decl_name) {
|
||||
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;
|
||||
},
|
||||
@ -7112,15 +7118,33 @@ fn builtinCall(
|
||||
if (!local_ptr.maybe_comptime)
|
||||
return astgen.failNode(params[0], "unable to export runtime-known value", .{});
|
||||
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;
|
||||
},
|
||||
.gen_zir => s = s.cast(GenZir).?.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 => {
|
||||
const namespace_node = node_datas[params[0]].lhs;
|
||||
|
||||
@ -274,7 +274,8 @@ fn analyzeInst(
|
||||
.not,
|
||||
.bitcast,
|
||||
.load,
|
||||
.floatcast,
|
||||
.fpext,
|
||||
.fptrunc,
|
||||
.intcast,
|
||||
.trunc,
|
||||
.optional_payload,
|
||||
|
||||
@ -2389,6 +2389,7 @@ pub fn deinit(mod: *Module) void {
|
||||
fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
|
||||
for (export_list) |exp| {
|
||||
gpa.free(exp.options.name);
|
||||
if (exp.options.section) |s| gpa.free(s);
|
||||
gpa.destroy(exp);
|
||||
}
|
||||
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", .{});
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
@ -3376,7 +3378,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
|
||||
if (decl.is_exported) {
|
||||
const export_src = src; // TODO point to the export token
|
||||
// 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;
|
||||
@ -4119,7 +4122,7 @@ pub fn analyzeExport(
|
||||
mod: *Module,
|
||||
scope: *Scope,
|
||||
src: LazySrcLoc,
|
||||
borrowed_symbol_name: []const u8,
|
||||
borrowed_options: std.builtin.ExportOptions,
|
||||
exported_decl: *Decl,
|
||||
) !void {
|
||||
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}),
|
||||
}
|
||||
|
||||
try mod.decl_exports.ensureUnusedCapacity(mod.gpa, 1);
|
||||
try mod.export_owners.ensureUnusedCapacity(mod.gpa, 1);
|
||||
const gpa = mod.gpa;
|
||||
|
||||
const new_export = try mod.gpa.create(Export);
|
||||
errdefer mod.gpa.destroy(new_export);
|
||||
try mod.decl_exports.ensureUnusedCapacity(gpa, 1);
|
||||
try mod.export_owners.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
const symbol_name = try mod.gpa.dupe(u8, borrowed_symbol_name);
|
||||
errdefer mod.gpa.free(symbol_name);
|
||||
const new_export = try gpa.create(Export);
|
||||
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().?;
|
||||
|
||||
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.* = .{
|
||||
.options = .{ .name = symbol_name },
|
||||
.options = .{
|
||||
.name = symbol_name,
|
||||
.linkage = borrowed_options.linkage,
|
||||
.section = section,
|
||||
},
|
||||
.src = src,
|
||||
.link = switch (mod.comp.bin_file.tag) {
|
||||
.coff => .{ .coff = {} },
|
||||
@ -4165,18 +4177,18 @@ pub fn analyzeExport(
|
||||
if (!eo_gop.found_existing) {
|
||||
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;
|
||||
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.
|
||||
const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl);
|
||||
if (!de_gop.found_existing) {
|
||||
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;
|
||||
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.
|
||||
|
||||
138
src/Sema.zig
138
src/Sema.zig
@ -458,6 +458,11 @@ pub fn analyzeBody(
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
.export_value => {
|
||||
try sema.zirExportValue(block, inst);
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
.set_align_stack => {
|
||||
try sema.zirSetAlignStack(block, inst);
|
||||
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 extra = sema.code.extraData(Zir.Inst.Export, inst_data.payload_index).data;
|
||||
const src = inst_data.src();
|
||||
const lhs_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 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 decl_name = sema.code.nullTerminatedString(extra.decl_name);
|
||||
if (extra.namespace != .none) {
|
||||
return sema.mod.fail(&block.base, src, "TODO: implement exporting with field access", .{});
|
||||
}
|
||||
const decl = try sema.lookupIdentifier(block, lhs_src, decl_name);
|
||||
const options = try sema.resolveInstConst(block, rhs_src, extra.options);
|
||||
const struct_obj = options.ty.castTag(.@"struct").?.data;
|
||||
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) {
|
||||
return sema.mod.fail(&block.base, src, "TODO: implement exporting with non-strong linkage", .{});
|
||||
}
|
||||
if (!fields[section_index].isNull()) {
|
||||
return sema.mod.fail(&block.base, src, "TODO: implement exporting with linksection", .{});
|
||||
const decl = try sema.lookupIdentifier(block, operand_src, decl_name);
|
||||
const options = try sema.resolveExportOptions(block, options_src, extra.options);
|
||||
try sema.mod.analyzeExport(&block.base, src, options, decl);
|
||||
}
|
||||
|
||||
try sema.mod.analyzeExport(&block.base, src, export_name, decl);
|
||||
fn zirExportValue(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
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 {
|
||||
@ -4516,11 +4524,18 @@ fn zirFloatCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
|
||||
|
||||
if (try sema.isComptimeKnown(block, operand_src, operand)) {
|
||||
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, "TODO implement analyze widen or shorten float", .{});
|
||||
const target = sema.mod.getTarget();
|
||||
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 {
|
||||
@ -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(
|
||||
sema: *Sema,
|
||||
block: *Scope.Block,
|
||||
@ -9581,7 +9621,7 @@ fn coerce(
|
||||
const dst_bits = dest_type.floatBits(target);
|
||||
if (dst_bits >= src_bits) {
|
||||
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();
|
||||
|
||||
switch (dst_zig_tag) {
|
||||
.ComptimeInt, .Int => {
|
||||
if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
|
||||
.ComptimeInt, .Int => switch (src_zig_tag) {
|
||||
.Float, .ComptimeFloat => {
|
||||
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", .{});
|
||||
} else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
|
||||
},
|
||||
.Int, .ComptimeInt => {
|
||||
if (!val.intFitsInType(dest_type, target)) {
|
||||
return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ dest_type, val });
|
||||
}
|
||||
return try sema.addConstant(dest_type, val);
|
||||
}
|
||||
},
|
||||
.ComptimeFloat, .Float => {
|
||||
if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
|
||||
const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) {
|
||||
error.Overflow => return sema.mod.fail(
|
||||
else => {},
|
||||
},
|
||||
.ComptimeFloat, .Float => switch (src_zig_tag) {
|
||||
.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,
|
||||
inst_src,
|
||||
"cast of value {} to type '{}' loses information",
|
||||
.{ val, dest_type },
|
||||
),
|
||||
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);
|
||||
"type {} cannot represent float value {}",
|
||||
.{ dest_type, 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 => {},
|
||||
}
|
||||
|
||||
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_value,
|
||||
/// 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`.
|
||||
@"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
|
||||
/// 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.
|
||||
@ -1010,6 +1014,7 @@ pub const Inst = struct {
|
||||
.ensure_result_used,
|
||||
.ensure_result_non_error,
|
||||
.@"export",
|
||||
.export_value,
|
||||
.field_ptr,
|
||||
.field_val,
|
||||
.field_ptr_named,
|
||||
@ -1273,6 +1278,7 @@ pub const Inst = struct {
|
||||
.error_union_type = .pl_node,
|
||||
.error_value = .str_tok,
|
||||
.@"export" = .pl_node,
|
||||
.export_value = .pl_node,
|
||||
.field_ptr = .pl_node,
|
||||
.field_val = .pl_node,
|
||||
.field_ptr_named = .pl_node,
|
||||
@ -2843,6 +2849,12 @@ pub const Inst = struct {
|
||||
options: Ref,
|
||||
};
|
||||
|
||||
pub const ExportValue = struct {
|
||||
/// The comptime value to export.
|
||||
operand: Ref,
|
||||
options: Ref,
|
||||
};
|
||||
|
||||
/// Trailing: `CompileErrors.Item` for each `items_len`.
|
||||
pub const CompileErrors = struct {
|
||||
items_len: u32,
|
||||
|
||||
@ -859,7 +859,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
.call => try self.airCall(inst),
|
||||
.cond_br => try self.airCondBr(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),
|
||||
.trunc => try self.airTrunc(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 });
|
||||
}
|
||||
|
||||
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 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 });
|
||||
}
|
||||
|
||||
@ -954,7 +954,12 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
|
||||
.atomic_rmw => try airAtomicRmw(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_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),
|
||||
.wrap_errunion_payload => try airWrapErrUnionPay(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
|
||||
};
|
||||
switch (result_value) {
|
||||
|
||||
@ -472,7 +472,18 @@ pub const Object = struct {
|
||||
alias.setAliasee(llvm_fn);
|
||||
} else {
|
||||
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),
|
||||
.intcast => try self.airIntCast(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),
|
||||
.load => try self.airLoad(inst),
|
||||
.loop => try self.airLoop(inst),
|
||||
@ -2060,12 +2072,26 @@ pub const FuncGen = struct {
|
||||
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))
|
||||
return null;
|
||||
|
||||
// TODO split floatcast AIR into float_widen and float_shorten
|
||||
return self.todo("implement 'airFloatCast'", .{});
|
||||
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.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 {
|
||||
|
||||
@ -601,6 +601,22 @@ pub const Builder = opaque {
|
||||
DestTy: *const Type,
|
||||
Name: [*:0]const u8,
|
||||
) *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) {
|
||||
|
||||
@ -156,7 +156,8 @@ const Writer = struct {
|
||||
.not,
|
||||
.bitcast,
|
||||
.load,
|
||||
.floatcast,
|
||||
.fptrunc,
|
||||
.fpext,
|
||||
.intcast,
|
||||
.trunc,
|
||||
.optional_payload,
|
||||
|
||||
@ -285,6 +285,7 @@ const Writer = struct {
|
||||
=> try self.writePlNodeBin(stream, inst),
|
||||
|
||||
.@"export" => try self.writePlNodeExport(stream, inst),
|
||||
.export_value => try self.writePlNodeExportValue(stream, inst),
|
||||
|
||||
.call,
|
||||
.call_chkused,
|
||||
@ -611,6 +612,17 @@ const Writer = struct {
|
||||
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 {
|
||||
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
|
||||
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 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 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 target = std.Target{\n"
|
||||
|
||||
@ -1041,30 +1041,15 @@ pub const Value = extern union {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an integer or a float to a float.
|
||||
/// Returns `error.Overflow` if the value does not fit in the new type.
|
||||
pub fn floatCast(self: Value, allocator: *Allocator, dest_ty: Type) !Value {
|
||||
/// Converts an integer or a float to a float. May result in a loss of information.
|
||||
/// Caller can find out by equality checking the result against the operand.
|
||||
pub fn floatCast(self: Value, arena: *Allocator, dest_ty: Type) !Value {
|
||||
switch (dest_ty.tag()) {
|
||||
.f16 => {
|
||||
const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16));
|
||||
if (!self.eql(res, dest_ty))
|
||||
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;
|
||||
},
|
||||
.f16 => return Value.Tag.float_16.create(arena, self.toFloat(f16)),
|
||||
.f32 => return Value.Tag.float_32.create(arena, self.toFloat(f32)),
|
||||
.f64 => return Value.Tag.float_64.create(arena, self.toFloat(f64)),
|
||||
.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,
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ test {
|
||||
_ = @import("behavior/atomics.zig");
|
||||
_ = @import("behavior/sizeof_and_typeof.zig");
|
||||
_ = @import("behavior/translate_c_macros.zig");
|
||||
_ = @import("behavior/union.zig");
|
||||
_ = @import("behavior/widening.zig");
|
||||
|
||||
if (builtin.zig_is_stage2) {
|
||||
@ -149,7 +150,7 @@ test {
|
||||
_ = @import("behavior/typename.zig");
|
||||
_ = @import("behavior/undefined.zig");
|
||||
_ = @import("behavior/underscore.zig");
|
||||
_ = @import("behavior/union.zig");
|
||||
_ = @import("behavior/union_stage1.zig");
|
||||
_ = @import("behavior/usingnamespace_stage1.zig");
|
||||
_ = @import("behavior/var_args.zig");
|
||||
_ = @import("behavior/vector.zig");
|
||||
@ -158,7 +159,6 @@ test {
|
||||
_ = @import("behavior/wasm.zig");
|
||||
}
|
||||
_ = @import("behavior/while.zig");
|
||||
_ = @import("behavior/widening_stage1.zig");
|
||||
_ = @import("behavior/src.zig");
|
||||
_ = @import("behavior/translate_c_macros_stage1.zig");
|
||||
}
|
||||
|
||||
@ -2,816 +2,3 @@ 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);
|
||||
}
|
||||
|
||||
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;
|
||||
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