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:
Andrew Kelley 2021-09-21 22:33:00 -07:00
parent 0e2b9ac777
commit aecebf38ac
21 changed files with 1734 additions and 1558 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -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,32 +7096,55 @@ 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;
while (true) switch (s.tag) { var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
.local_val => { while (true) switch (s.tag) {
const local_val = s.cast(Scope.LocalVal).?; .local_val => {
if (local_val.name == decl_name) { const local_val = s.cast(Scope.LocalVal).?;
local_val.used = true; if (local_val.name == decl_name) {
break; local_val.used = true;
_ = 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;
},
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (local_ptr.name == decl_name) {
if (!local_ptr.maybe_comptime)
return astgen.failNode(params[0], "unable to export runtime-known value", .{});
local_ptr.used = true;
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 => {
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", .{}),
});
} }
s = local_val.parent; // We found a match but must continue looking for ambiguous references to decls.
}, found_already = i;
.local_ptr => { }
const local_ptr = s.cast(Scope.LocalPtr).?; s = ns.parent;
if (local_ptr.name == decl_name) { },
if (!local_ptr.maybe_comptime) .top => break,
return astgen.failNode(params[0], "unable to export runtime-known value", .{}); };
local_ptr.used = true;
break;
}
s = local_ptr.parent;
},
.gen_zir => s = s.cast(GenZir).?.parent,
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
.namespace, .top => break,
};
}
}, },
.field_access => { .field_access => {
const namespace_node = node_datas[params[0]].lhs; const namespace_node = node_datas[params[0]].lhs;

View File

@ -274,7 +274,8 @@ fn analyzeInst(
.not, .not,
.bitcast, .bitcast,
.load, .load,
.floatcast, .fpext,
.fptrunc,
.intcast, .intcast,
.trunc, .trunc,
.optional_payload, .optional_payload,

View File

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

View File

@ -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);
} },
else => {},
}, },
.ComptimeFloat, .Float => { .ComptimeFloat, .Float => switch (src_zig_tag) {
if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { .ComptimeFloat => {
const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) { const result_val = try val.floatCast(sema.arena, dest_type);
error.Overflow => return sema.mod.fail( 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 => {},
} }

View File

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

View File

@ -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 });
} }

View File

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

View File

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

View File

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

View File

@ -156,7 +156,8 @@ const Writer = struct {
.not, .not,
.bitcast, .bitcast,
.load, .load,
.floatcast, .fptrunc,
.fpext,
.intcast, .intcast,
.trunc, .trunc,
.optional_payload, .optional_payload,

View File

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

View File

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

View File

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

View File

@ -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");
} }

View File

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

View 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;
}

View File

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

View File

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