stage2: field type expressions support referencing locals

The big change in this commit is making `semaDecl` resolve the fields if
the Decl ends up being a struct or union. It needs to do this while
the `Sema` is still in scope, because it will have the resolved AIR
instructions that the field type expressions possibly reference. We do
this after the decl is populated and set to `complete` so that a `Decl`
may reference itself.

Everything else is fixes and improvements to make the test suite pass
again after making this change.

 * New AIR instruction: `ptr_elem_ptr`
   - Implemented for LLVM backend
 * New Type tag: `type_info` which represents `std.builtin.TypeInfo`. It
   is used by AstGen for the operand type of `@Type`.
 * ZIR instruction `set_float_mode` uses `coerced_ty` to avoid
   superfluous `as` instruction on operand.
 * ZIR instruction `Type` uses `coerced_ty` to properly handle result
   location type of operand.

 * Fix two instances of `enum_nonexhaustive` Value Tag not handled
   properly - it should generally be handled the same as `enum_full`.
 * Fix struct and union field resolution not copying Type and Value
   objects into its Decl arena.
 * Fix enum tag value resolution discarding the ZIR=>AIR instruction map
   for the child Sema, when they still needed to be accessed.
 * Fix `zirResolveInferredAlloc` use-after-free in the AIR instructions
   data array.
 * Fix `elemPtrArray` not respecting const/mutable attribute of pointer
   in the result type.
 * Fix LLVM backend crashing when `updateDeclExports` is called before
   `updateDecl`/`updateFunc` (which is, according to the API, perfectly
   legal for the frontend to do).
 * Fix LLVM backend handling element pointer of pointer-to-array. It
   needed another index in the GEP otherwise LLVM saw the wrong type.
 * Fix LLVM test cases not returning 0 from main, causing test failures.
   Fixes a regression introduced in
   6a5094872f10acc629543cc7f10533b438d0283a.

 * Implement comptime shift-right.
 * Implement `@Type` for integers and `@TypeInfo` for integers.
 * Implement union initialization syntax.
 * Implement `zirFieldType` for unions.
 * Implement `elemPtrArray` for a runtime-known operand.

 * Make `zirLog2IntType` support RHS of shift being `comptime_int`. In
   this case it returns `comptime_int`.

The motivating test case for this commit was originally:

```zig
test "example" {
    var l: List(10) = undefined;
    l.array[1] = 1;
}

fn List(comptime L: usize) type {
    var T = u8;
    return struct {
        array: [L]T,
    };
}
```

However I changed it to:

```zig
test "example" {
    var l: List = undefined;
    l.array[1] = 1;
}

const List = blk: {
    const T = [10]u8;
    break :blk struct {
        array: T,
    };
};
```

Which ended up being a similar, smaller problem. The former test case
will require a similar solution in the implementation of comptime
function calls - checking if the result of the function call is a struct
or union, and using the child `Sema` before it is destroyed to resolve
the fields.
This commit is contained in:
Andrew Kelley 2021-08-20 15:23:55 -07:00
parent 2f1abd919a
commit 0cd361219c
18 changed files with 1297 additions and 989 deletions

View File

@ -237,7 +237,7 @@ pub const TypeInfo = union(enum) {
/// This field is an optional type.
/// The type of the sentinel is the element type of the pointer, which is
/// the value of the `child` field in this struct. However there is no way
/// to refer to that type here, so we use `var`.
/// to refer to that type here, so we use `anytype`.
sentinel: anytype,
/// This data structure is used by the Zig language code generation and

View File

@ -286,6 +286,10 @@ pub const Inst = struct {
/// Result type is the element type of the pointer operand.
/// Uses the `bin_op` field.
ptr_elem_val,
/// Given a pointer value, and element index, return the element pointer at that index.
/// Result type is pointer to the element type of the pointer operand.
/// Uses the `ty_pl` field with payload `Bin`.
ptr_elem_ptr,
/// Given a pointer to a pointer, and element index, return the element value of the inner
/// pointer at that index.
/// Result type is the element type of the inner pointer operand.
@ -410,6 +414,11 @@ pub const StructField = struct {
field_index: u32,
};
pub const Bin = struct {
lhs: Inst.Ref,
rhs: Inst.Ref,
};
/// Trailing:
/// 0. `Inst.Ref` for every outputs_len
/// 1. `Inst.Ref` for every inputs_len
@ -482,6 +491,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.constant,
.struct_field_ptr,
.struct_field_val,
.ptr_elem_ptr,
=> return air.getRefType(datas[inst].ty_pl.ty),
.not,
@ -527,8 +537,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
},
.slice_elem_val, .ptr_elem_val => {
const slice_ty = air.typeOf(datas[inst].bin_op.lhs);
return slice_ty.elemType();
const ptr_ty = air.typeOf(datas[inst].bin_op.lhs);
return ptr_ty.elemType();
},
.ptr_slice_elem_val, .ptr_ptr_elem_val => {
const outer_ptr_ty = air.typeOf(datas[inst].bin_op.lhs);

View File

@ -7102,38 +7102,38 @@ fn builtinCall(
.bit_size_of => return simpleUnOpType(gz, scope, rl, node, params[0], .bit_size_of),
.align_of => return simpleUnOpType(gz, scope, rl, node, params[0], .align_of),
.ptr_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ptr_to_int),
.error_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .error_to_int),
.int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type }, params[0], .int_to_error),
.compile_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .compile_error),
.set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u32_type }, params[0], .set_eval_branch_quota),
.enum_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .enum_to_int),
.bool_to_int => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .bool_to_int),
.embed_file => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .embed_file),
.error_name => return simpleUnOp(gz, scope, rl, node, .{ .ty = .anyerror_type }, params[0], .error_name),
.panic => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .panic),
.set_align_stack => return simpleUnOp(gz, scope, rl, node, align_rl, params[0], .set_align_stack),
.set_cold => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_cold),
.set_float_mode => return simpleUnOp(gz, scope, rl, node, .{ .ty = .float_mode_type }, params[0], .set_float_mode),
.set_runtime_safety => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_runtime_safety),
.sqrt => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sqrt),
.sin => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sin),
.cos => return simpleUnOp(gz, scope, rl, node, .none, params[0], .cos),
.exp => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp),
.exp2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp2),
.log => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log),
.log2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log2),
.log10 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log10),
.fabs => return simpleUnOp(gz, scope, rl, node, .none, params[0], .fabs),
.floor => return simpleUnOp(gz, scope, rl, node, .none, params[0], .floor),
.ceil => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ceil),
.trunc => return simpleUnOp(gz, scope, rl, node, .none, params[0], .trunc),
.round => return simpleUnOp(gz, scope, rl, node, .none, params[0], .round),
.tag_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .tag_name),
.Type => return simpleUnOp(gz, scope, rl, node, .none, params[0], .reify),
.type_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .type_name),
.Frame => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_type),
.frame_size => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_size),
.ptr_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ptr_to_int),
.error_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .error_to_int),
.int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type }, params[0], .int_to_error),
.compile_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .compile_error),
.set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u32_type }, params[0], .set_eval_branch_quota),
.enum_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .enum_to_int),
.bool_to_int => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .bool_to_int),
.embed_file => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .embed_file),
.error_name => return simpleUnOp(gz, scope, rl, node, .{ .ty = .anyerror_type }, params[0], .error_name),
.panic => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .panic),
.set_align_stack => return simpleUnOp(gz, scope, rl, node, align_rl, params[0], .set_align_stack),
.set_cold => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_cold),
.set_float_mode => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .float_mode_type }, params[0], .set_float_mode),
.set_runtime_safety => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_runtime_safety),
.sqrt => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sqrt),
.sin => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sin),
.cos => return simpleUnOp(gz, scope, rl, node, .none, params[0], .cos),
.exp => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp),
.exp2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp2),
.log => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log),
.log2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log2),
.log10 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log10),
.fabs => return simpleUnOp(gz, scope, rl, node, .none, params[0], .fabs),
.floor => return simpleUnOp(gz, scope, rl, node, .none, params[0], .floor),
.ceil => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ceil),
.trunc => return simpleUnOp(gz, scope, rl, node, .none, params[0], .trunc),
.round => return simpleUnOp(gz, scope, rl, node, .none, params[0], .round),
.tag_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .tag_name),
.Type => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .type_info_type }, params[0], .reify),
.type_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .type_name),
.Frame => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_type),
.frame_size => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_size),
.float_to_int => return typeCast(gz, scope, rl, node, params[0], params[1], .float_to_int),
.int_to_float => return typeCast(gz, scope, rl, node, params[0], params[1], .int_to_float),

View File

@ -330,6 +330,10 @@ fn analyzeInst(
const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none });
},
.ptr_elem_ptr => {
const extra = a.air.extraData(Air.Bin, inst_datas[inst].ty_pl.payload).data;
return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none });
},
.br => {
const br = inst_datas[inst].br;
return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none });

View File

@ -554,8 +554,8 @@ pub const Decl = struct {
assert(struct_obj.owner_decl == decl);
return &struct_obj.namespace;
},
.enum_full => {
const enum_obj = ty.castTag(.enum_full).?.data;
.enum_full, .enum_nonexhaustive => {
const enum_obj = ty.cast(Type.Payload.EnumFull).?.data;
assert(enum_obj.owner_decl == decl);
return &enum_obj.namespace;
},
@ -660,6 +660,7 @@ pub const Struct = struct {
/// is necessary to determine whether it has bits at runtime.
known_has_bits: bool,
/// The `Type` and `Value` memory is owned by the arena of the Struct's owner_decl.
pub const Field = struct {
/// Uses `noreturn` to indicate `anytype`.
/// undefined until `status` is `have_field_types` or `have_layout`.
@ -3091,6 +3092,9 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
if (linksection_ref == .none) break :blk Value.initTag(.null_value);
break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val;
};
// Note this resolves the type of the Decl, not the value; if this Decl
// is a struct, for example, this resolves `type` (which needs no resolution),
// not the struct itself.
try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty);
// We need the memory for the Type to go into the arena for the Decl
@ -3193,6 +3197,15 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
if (type_changed and mod.emit_h != null) {
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
}
} else if (decl_tv.ty.zigTypeTag() == .Type) {
// In case this Decl is a struct or union, we need to resolve the fields
// while we still have the `Sema` in scope, so that the field type expressions
// can use the resolved AIR instructions that they possibly reference.
// We do this after the decl is populated and set to `complete` so that a `Decl`
// may reference itself.
var buffer: Value.ToTypeBuffer = undefined;
const ty = decl.val.toType(&buffer);
try sema.resolveDeclFields(&block_scope, src, ty);
}
if (decl.is_exported) {
@ -4450,309 +4463,6 @@ pub const PeerTypeCandidateSrc = union(enum) {
}
};
pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = mod.gpa;
const zir = struct_obj.owner_decl.namespace.file_scope.zir;
const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
assert(extended.opcode == .struct_decl);
const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
var extra_index: usize = extended.operand;
const src: LazySrcLoc = .{ .node_offset = struct_obj.node_offset };
extra_index += @boolToInt(small.has_src_node);
const body_len = if (small.has_body_len) blk: {
const body_len = zir.extra[extra_index];
extra_index += 1;
break :blk body_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = zir.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) decls_len: {
const decls_len = zir.extra[extra_index];
extra_index += 1;
break :decls_len decls_len;
} else 0;
// Skip over decls.
var decls_it = zir.declIteratorInner(extra_index, decls_len);
while (decls_it.next()) |_| {}
extra_index = decls_it.extra_index;
const body = zir.extra[extra_index..][0..body_len];
if (fields_len == 0) {
assert(body.len == 0);
return;
}
extra_index += body.len;
var decl_arena = struct_obj.owner_decl.value_arena.?.promote(gpa);
defer struct_obj.owner_decl.value_arena.?.* = decl_arena.state;
try struct_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
// We create a block for the field type instructions because they
// may need to reference Decls from inside the struct namespace.
// Within the field type, default value, and alignment expressions, the "owner decl"
// should be the struct itself. Thus we need a new Sema.
var sema: Sema = .{
.mod = mod,
.gpa = gpa,
.arena = &decl_arena.allocator,
.code = zir,
.owner_decl = struct_obj.owner_decl,
.namespace = &struct_obj.namespace,
.owner_func = null,
.func = null,
.fn_ret_ty = Type.initTag(.void),
};
defer sema.deinit();
var block: Scope.Block = .{
.parent = null,
.sema = &sema,
.src_decl = struct_obj.owner_decl,
.instructions = .{},
.inlining = null,
.is_comptime = true,
};
defer assert(block.instructions.items.len == 0); // should all be comptime instructions
if (body.len != 0) {
_ = try sema.analyzeBody(&block, body);
}
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
var bit_bag_index: usize = extra_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_align = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const has_default = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const is_comptime = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const unused = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
_ = unused;
const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
extra_index += 1;
const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
// This string needs to outlive the ZIR code.
const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
if (field_type_ref == .none) {
return mod.fail(&block.base, src, "TODO: implement anytype struct field", .{});
}
const field_ty: Type = if (field_type_ref == .none)
Type.initTag(.noreturn)
else
// TODO: if we need to report an error here, use a source location
// that points to this type expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
try sema.resolveType(&block, src, field_type_ref);
const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
assert(!gop.found_existing);
gop.value_ptr.* = .{
.ty = field_ty,
.abi_align = Value.initTag(.abi_align_default),
.default_val = Value.initTag(.unreachable_value),
.is_comptime = is_comptime,
.offset = undefined,
};
if (has_align) {
const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
// TODO: if we need to report an error here, use a source location
// that points to this alignment expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
gop.value_ptr.abi_align = (try sema.resolveInstConst(&block, src, align_ref)).val;
}
if (has_default) {
const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
// TODO: if we need to report an error here, use a source location
// that points to this default value expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
gop.value_ptr.default_val = (try sema.resolveInstConst(&block, src, default_ref)).val;
}
}
}
pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = mod.gpa;
const zir = union_obj.owner_decl.namespace.file_scope.zir;
const extended = zir.instructions.items(.data)[union_obj.zir_index].extended;
assert(extended.opcode == .union_decl);
const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small);
var extra_index: usize = extended.operand;
const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset };
extra_index += @boolToInt(small.has_src_node);
if (small.has_tag_type) {
extra_index += 1;
}
const body_len = if (small.has_body_len) blk: {
const body_len = zir.extra[extra_index];
extra_index += 1;
break :blk body_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = zir.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) decls_len: {
const decls_len = zir.extra[extra_index];
extra_index += 1;
break :decls_len decls_len;
} else 0;
// Skip over decls.
var decls_it = zir.declIteratorInner(extra_index, decls_len);
while (decls_it.next()) |_| {}
extra_index = decls_it.extra_index;
const body = zir.extra[extra_index..][0..body_len];
if (fields_len == 0) {
assert(body.len == 0);
return;
}
extra_index += body.len;
var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa);
defer union_obj.owner_decl.value_arena.?.* = decl_arena.state;
try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
// We create a block for the field type instructions because they
// may need to reference Decls from inside the struct namespace.
// Within the field type, default value, and alignment expressions, the "owner decl"
// should be the struct itself. Thus we need a new Sema.
var sema: Sema = .{
.mod = mod,
.gpa = gpa,
.arena = &decl_arena.allocator,
.code = zir,
.owner_decl = union_obj.owner_decl,
.namespace = &union_obj.namespace,
.owner_func = null,
.func = null,
.fn_ret_ty = Type.initTag(.void),
};
defer sema.deinit();
var block: Scope.Block = .{
.parent = null,
.sema = &sema,
.src_decl = union_obj.owner_decl,
.instructions = .{},
.inlining = null,
.is_comptime = true,
};
defer assert(block.instructions.items.len == 0); // should all be comptime instructions
if (body.len != 0) {
_ = try sema.analyzeBody(&block, body);
}
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
var bit_bag_index: usize = extra_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_type = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const has_align = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const has_tag = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const unused = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
_ = unused;
const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
extra_index += 1;
const field_type_ref: Zir.Inst.Ref = if (has_type) blk: {
const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
break :blk field_type_ref;
} else .none;
const align_ref: Zir.Inst.Ref = if (has_align) blk: {
const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
break :blk align_ref;
} else .none;
if (has_tag) {
extra_index += 1;
}
// This string needs to outlive the ZIR code.
const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
const field_ty: Type = if (field_type_ref == .none)
Type.initTag(.void)
else
// TODO: if we need to report an error here, use a source location
// that points to this type expression rather than the union.
// But only resolve the source location if we need to emit a compile error.
try sema.resolveType(&block, src, field_type_ref);
const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
assert(!gop.found_existing);
gop.value_ptr.* = .{
.ty = field_ty,
.abi_align = Value.initTag(.abi_align_default),
};
if (align_ref != .none) {
// TODO: if we need to report an error here, use a source location
// that points to this alignment expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
gop.value_ptr.abi_align = (try sema.resolveInstConst(&block, src, align_ref)).val;
}
}
// TODO resolve the union tag_type_ref
}
/// Called from `performAllTheWork`, after all AstGen workers have finished,
/// and before the main semantic analysis loop begins.
pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {

View File

@ -1032,25 +1032,27 @@ fn zirEnumDecl(
// We create a block for the field type instructions because they
// may need to reference Decls from inside the enum namespace.
// Within the field type, default value, and alignment expressions, the "owner decl"
// should be the enum itself. Thus we need a new Sema.
var enum_sema: Sema = .{
.mod = mod,
.gpa = gpa,
.arena = &new_decl_arena.allocator,
.code = sema.code,
.inst_map = sema.inst_map,
.owner_decl = new_decl,
.namespace = &enum_obj.namespace,
.owner_func = null,
.func = null,
.fn_ret_ty = Type.initTag(.void),
.branch_quota = sema.branch_quota,
.branch_count = sema.branch_count,
};
// should be the enum itself.
const prev_owner_decl = sema.owner_decl;
sema.owner_decl = new_decl;
defer sema.owner_decl = prev_owner_decl;
const prev_namespace = sema.namespace;
sema.namespace = &enum_obj.namespace;
defer sema.namespace = prev_namespace;
const prev_owner_func = sema.owner_func;
sema.owner_func = null;
defer sema.owner_func = prev_owner_func;
const prev_func = sema.func;
sema.func = null;
defer sema.func = prev_func;
var enum_block: Scope.Block = .{
.parent = null,
.sema = &enum_sema,
.sema = sema,
.src_decl = new_decl,
.instructions = .{},
.inlining = null,
@ -1059,11 +1061,8 @@ fn zirEnumDecl(
defer assert(enum_block.instructions.items.len == 0); // should all be comptime instructions
if (body.len != 0) {
_ = try enum_sema.analyzeBody(&enum_block, body);
_ = try sema.analyzeBody(&enum_block, body);
}
sema.branch_count = enum_sema.branch_count;
sema.branch_quota = enum_sema.branch_quota;
}
var bit_bag_index: usize = body_end;
var cur_bit_bag: u32 = undefined;
@ -1466,8 +1465,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
const ptr = sema.resolveInst(inst_data.operand);
const ptr_inst = Air.refToIndex(ptr).?;
assert(sema.air_instructions.items(.tag)[ptr_inst] == .constant);
const air_datas = sema.air_instructions.items(.data);
const value_index = air_datas[ptr_inst].ty_pl.payload;
const value_index = sema.air_instructions.items(.data)[ptr_inst].ty_pl.payload;
const ptr_val = sema.air_values.items[value_index];
const var_is_mut = switch (sema.typeOf(ptr).tag()) {
.inferred_alloc_const => false,
@ -1481,7 +1479,8 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
const final_elem_ty = try decl.ty.copy(sema.arena);
const final_ptr_ty = try Module.simplePtrType(sema.arena, final_elem_ty, true, .One);
air_datas[ptr_inst].ty_pl.ty = try sema.addType(final_ptr_ty);
const final_ptr_ty_inst = try sema.addType(final_ptr_ty);
sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst;
if (var_is_mut) {
sema.air_values.items[value_index] = try Value.Tag.decl_ref_mut.create(sema.arena, .{
@ -5329,10 +5328,16 @@ fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| {
if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
const lhs_ty = sema.typeOf(lhs);
if (lhs_val.isUndef() or rhs_val.isUndef()) {
return sema.addConstUndef(sema.typeOf(lhs));
return sema.addConstUndef(lhs_ty);
}
return sema.mod.fail(&block.base, src, "TODO implement comptime shr", .{});
// If rhs is 0, return lhs without doing any calculations.
if (rhs_val.compareWithZero(.eq)) {
return sema.addConstant(lhs_ty, lhs_val);
}
const val = try lhs_val.shr(rhs_val, sema.arena);
return sema.addConstant(lhs_ty, val);
}
}
@ -6008,6 +6013,28 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
}),
);
},
.Int => {
const info = ty.intInfo(target);
const field_values = try sema.arena.alloc(Value, 2);
// signedness: Signedness,
field_values[0] = try Value.Tag.enum_field_index.create(
sema.arena,
@enumToInt(info.signedness),
);
// bits: comptime_int,
field_values[1] = try Value.Tag.int_u64.create(sema.arena, info.bits);
return sema.addConstant(
type_info_ty,
try Value.Tag.@"union".create(sema.arena, .{
.tag = try Value.Tag.enum_field_index.create(
sema.arena,
@enumToInt(@typeInfo(std.builtin.TypeInfo).Union.tag_type.?.Int),
),
.val = try Value.Tag.@"struct".create(sema.arena, field_values.ptr),
}),
);
},
else => |t| return sema.mod.fail(&block.base, src, "TODO: implement zirTypeInfo for {s}", .{
@tagName(t),
}),
@ -6047,20 +6074,24 @@ fn zirLog2IntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compil
}
fn log2IntType(sema: *Sema, block: *Scope.Block, operand: Type, src: LazySrcLoc) CompileError!Air.Inst.Ref {
if (operand.zigTypeTag() != .Int) return sema.mod.fail(
&block.base,
src,
"bit shifting operation expected integer type, found '{}'",
.{operand},
);
var count: u16 = 0;
var s = operand.bitSize(sema.mod.getTarget()) - 1;
while (s != 0) : (s >>= 1) {
count += 1;
switch (operand.zigTypeTag()) {
.ComptimeInt => return Air.Inst.Ref.comptime_int_type,
.Int => {
var count: u16 = 0;
var s = operand.bitSize(sema.mod.getTarget()) - 1;
while (s != 0) : (s >>= 1) {
count += 1;
}
const res = try Module.makeIntType(sema.arena, .unsigned, count);
return sema.addType(res);
},
else => return sema.mod.fail(
&block.base,
src,
"bit shifting operation expected integer type, found '{}'",
.{operand},
),
}
const res = try Module.makeIntType(sema.arena, .unsigned, count);
return sema.addType(res);
}
fn zirTypeofPeer(
@ -6517,99 +6548,134 @@ fn zirStructInit(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref:
const first_field_type_data = zir_datas[first_item.field_type].pl_node;
const first_field_type_extra = sema.code.extraData(Zir.Inst.FieldType, first_field_type_data.payload_index).data;
const unresolved_struct_type = try sema.resolveType(block, src, first_field_type_extra.container_type);
const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type);
const struct_obj = struct_ty.castTag(.@"struct").?.data;
const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type);
// Maps field index to field_type index of where it was already initialized.
// For making sure all fields are accounted for and no fields are duplicated.
const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count());
defer gpa.free(found_fields);
mem.set(Zir.Inst.Index, found_fields, 0);
if (resolved_ty.castTag(.@"struct")) |struct_payload| {
const struct_obj = struct_payload.data;
// The init values to use for the struct instance.
const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count());
defer gpa.free(field_inits);
// Maps field index to field_type index of where it was already initialized.
// For making sure all fields are accounted for and no fields are duplicated.
const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count());
defer gpa.free(found_fields);
mem.set(Zir.Inst.Index, found_fields, 0);
var field_i: u32 = 0;
var extra_index = extra.end;
// The init values to use for the struct instance.
const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count());
defer gpa.free(field_inits);
while (field_i < extra.data.fields_len) : (field_i += 1) {
const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index);
extra_index = item.end;
var field_i: u32 = 0;
var extra_index = extra.end;
while (field_i < extra.data.fields_len) : (field_i += 1) {
const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index);
extra_index = item.end;
const field_type_data = zir_datas[item.data.field_type].pl_node;
const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
const field_index = struct_obj.fields.getIndex(field_name) orelse
return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name);
if (found_fields[field_index] != 0) {
const other_field_type = found_fields[field_index];
const other_field_type_data = zir_datas[other_field_type].pl_node;
const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_type_data.src_node };
const msg = msg: {
const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{});
errdefer msg.destroy(gpa);
try mod.errNote(&block.base, other_field_src, msg, "other field here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(&block.base, msg);
}
found_fields[field_index] = item.data.field_type;
field_inits[field_index] = sema.resolveInst(item.data.init);
}
var root_msg: ?*Module.ErrorMsg = null;
for (found_fields) |field_type_inst, i| {
if (field_type_inst != 0) continue;
// Check if the field has a default init.
const field = struct_obj.fields.values()[i];
if (field.default_val.tag() == .unreachable_value) {
const field_name = struct_obj.fields.keys()[i];
const template = "missing struct field: {s}";
const args = .{field_name};
if (root_msg) |msg| {
try mod.errNote(&block.base, src, msg, template, args);
} else {
root_msg = try mod.errMsg(&block.base, src, template, args);
}
} else {
field_inits[i] = try sema.addConstant(field.ty, field.default_val);
}
}
if (root_msg) |msg| {
const fqn = try struct_obj.getFullyQualifiedName(gpa);
defer gpa.free(fqn);
try mod.errNoteNonLazy(
struct_obj.srcLoc(),
msg,
"struct '{s}' declared here",
.{fqn},
);
return mod.failWithOwnedErrorMsg(&block.base, msg);
}
if (is_ref) {
return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true", .{});
}
const is_comptime = for (field_inits) |field_init| {
if (!(try sema.isComptimeKnown(block, src, field_init))) {
break false;
}
} else true;
if (is_comptime) {
const values = try sema.arena.alloc(Value, field_inits.len);
for (field_inits) |field_init, i| {
values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?;
}
return sema.addConstant(resolved_ty, try Value.Tag.@"struct".create(sema.arena, values.ptr));
}
return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known struct values", .{});
} else if (resolved_ty.cast(Type.Payload.Union)) |union_payload| {
const union_obj = union_payload.data;
if (extra.data.fields_len != 1) {
return sema.mod.fail(&block.base, src, "union initialization expects exactly one field", .{});
}
const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end);
const field_type_data = zir_datas[item.data.field_type].pl_node;
const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
const field_index = struct_obj.fields.getIndex(field_name) orelse
return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name);
if (found_fields[field_index] != 0) {
const other_field_type = found_fields[field_index];
const other_field_type_data = zir_datas[other_field_type].pl_node;
const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_type_data.src_node };
const msg = msg: {
const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{});
errdefer msg.destroy(gpa);
try mod.errNote(&block.base, other_field_src, msg, "other field here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(&block.base, msg);
const field_index = union_obj.fields.getIndex(field_name) orelse
return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);
if (is_ref) {
return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true union", .{});
}
found_fields[field_index] = item.data.field_type;
field_inits[field_index] = sema.resolveInst(item.data.init);
}
var root_msg: ?*Module.ErrorMsg = null;
for (found_fields) |field_type_inst, i| {
if (field_type_inst != 0) continue;
// Check if the field has a default init.
const field = struct_obj.fields.values()[i];
if (field.default_val.tag() == .unreachable_value) {
const field_name = struct_obj.fields.keys()[i];
const template = "missing struct field: {s}";
const args = .{field_name};
if (root_msg) |msg| {
try mod.errNote(&block.base, src, msg, template, args);
} else {
root_msg = try mod.errMsg(&block.base, src, template, args);
}
} else {
field_inits[i] = try sema.addConstant(field.ty, field.default_val);
const init_inst = sema.resolveInst(item.data.init);
if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| {
return sema.addConstant(
resolved_ty,
try Value.Tag.@"union".create(sema.arena, .{
.tag = try Value.Tag.int_u64.create(sema.arena, field_index),
.val = val,
}),
);
}
return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known union values", .{});
}
if (root_msg) |msg| {
const fqn = try struct_obj.getFullyQualifiedName(gpa);
defer gpa.free(fqn);
try mod.errNoteNonLazy(
struct_obj.srcLoc(),
msg,
"struct '{s}' declared here",
.{fqn},
);
return mod.failWithOwnedErrorMsg(&block.base, msg);
}
if (is_ref) {
return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true", .{});
}
const is_comptime = for (field_inits) |field_init| {
if (!(try sema.isComptimeKnown(block, src, field_init))) {
break false;
}
} else true;
if (is_comptime) {
const values = try sema.arena.alloc(Value, field_inits.len);
for (field_inits) |field_init, i| {
values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?;
}
return sema.addConstant(struct_ty, try Value.Tag.@"struct".create(sema.arena, values.ptr));
}
return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known struct values", .{});
unreachable;
}
fn zirStructInitAnon(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref {
@ -6647,17 +6713,25 @@ fn zirFieldType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data;
const src = inst_data.src();
const field_name = sema.code.nullTerminatedString(extra.name_start);
const unresolved_struct_type = try sema.resolveType(block, src, extra.container_type);
if (unresolved_struct_type.zigTypeTag() != .Struct) {
return sema.mod.fail(&block.base, src, "expected struct; found '{}'", .{
unresolved_struct_type,
});
const unresolved_ty = try sema.resolveType(block, src, extra.container_type);
const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_ty);
switch (resolved_ty.zigTypeTag()) {
.Struct => {
const struct_obj = resolved_ty.castTag(.@"struct").?.data;
const field = struct_obj.fields.get(field_name) orelse
return sema.failWithBadFieldAccess(block, struct_obj, src, field_name);
return sema.addType(field.ty);
},
.Union => {
const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;
const field = union_obj.fields.get(field_name) orelse
return sema.failWithBadUnionFieldAccess(block, union_obj, src, field_name);
return sema.addType(field.ty);
},
else => return sema.mod.fail(&block.base, src, "expected struct or union; found '{}'", .{
resolved_ty,
}),
}
const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type);
const struct_obj = struct_ty.castTag(.@"struct").?.data;
const field = struct_obj.fields.get(field_name) orelse
return sema.failWithBadFieldAccess(block, struct_obj, src, field_name);
return sema.addType(field.ty);
}
fn zirErrorReturnTrace(
@ -6732,7 +6806,54 @@ fn zirTagName(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
fn zirReify(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify", .{});
const type_info_ty = try sema.getBuiltinType(block, src, "TypeInfo");
const uncasted_operand = sema.resolveInst(inst_data.operand);
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src);
const val = try sema.resolveConstValue(block, operand_src, type_info);
const union_val = val.cast(Value.Payload.Union).?.data;
const TypeInfoTag = std.meta.Tag(std.builtin.TypeInfo);
const tag_index = @intCast(std.meta.Tag(TypeInfoTag), union_val.tag.toUnsignedInt());
switch (@intToEnum(std.builtin.TypeId, tag_index)) {
.Type => return Air.Inst.Ref.type_type,
.Void => return Air.Inst.Ref.void_type,
.Bool => return Air.Inst.Ref.bool_type,
.NoReturn => return Air.Inst.Ref.noreturn_type,
.Int => {
const struct_val = union_val.val.castTag(.@"struct").?.data;
// TODO use reflection instead of magic numbers here
const signedness_val = struct_val[0];
const bits_val = struct_val[1];
const signedness = signedness_val.toEnum(std.builtin.Signedness);
const bits = @intCast(u16, bits_val.toUnsignedInt());
const ty = switch (signedness) {
.signed => try Type.Tag.int_signed.create(sema.arena, bits),
.unsigned => try Type.Tag.int_unsigned.create(sema.arena, bits),
};
return sema.addType(ty);
},
.Float => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Float", .{}),
.Pointer => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Pointer", .{}),
.Array => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Array", .{}),
.Struct => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Struct", .{}),
.ComptimeFloat => return Air.Inst.Ref.comptime_float_type,
.ComptimeInt => return Air.Inst.Ref.comptime_int_type,
.Undefined => return Air.Inst.Ref.undefined_type,
.Null => return Air.Inst.Ref.null_type,
.Optional => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Optional", .{}),
.ErrorUnion => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for ErrorUnion", .{}),
.ErrorSet => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for ErrorSet", .{}),
.Enum => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Enum", .{}),
.Union => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Union", .{}),
.Fn => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Fn", .{}),
.BoundFn => @panic("TODO delete BoundFn from the language"),
.Opaque => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Opaque", .{}),
.Frame => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Frame", .{}),
.AnyFrame => return Air.Inst.Ref.anyframe_type,
.Vector => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Vector", .{}),
.EnumLiteral => return Air.Inst.Ref.enum_literal_type,
}
}
fn zirTypeName(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@ -8152,24 +8273,35 @@ fn elemPtrArray(
elem_index: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const array_ptr_ty = sema.typeOf(array_ptr);
const pointee_type = array_ptr_ty.elemType().elemType();
const result_ty = if (array_ptr_ty.ptrIsMutable())
try Type.Tag.single_mut_pointer.create(sema.arena, pointee_type)
else
try Type.Tag.single_const_pointer.create(sema.arena, pointee_type);
if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| {
if (try sema.resolveDefinedValue(block, src, elem_index)) |index_val| {
if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| {
// Both array pointer and index are compile-time known.
const index_u64 = index_val.toUnsignedInt();
// @intCast here because it would have been impossible to construct a value that
// required a larger index.
const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64));
const pointee_type = sema.typeOf(array_ptr).elemType().elemType();
return sema.addConstant(
try Type.Tag.single_const_pointer.create(sema.arena, pointee_type),
elem_ptr,
);
return sema.addConstant(result_ty, elem_ptr);
}
}
_ = elem_index;
_ = elem_index_src;
return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr for arrays", .{});
// TODO safety check for array bounds
try sema.requireRuntimeBlock(block, src);
return block.addInst(.{
.tag = .ptr_elem_ptr,
.data = .{ .ty_pl = .{
.ty = try sema.addType(result_ty),
.payload = try sema.addExtra(Air.Bin{
.lhs = array_ptr,
.rhs = elem_index,
}),
} },
});
}
fn coerce(
@ -9177,22 +9309,62 @@ pub fn resolveTypeLayout(
}
}
fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type {
/// `sema` and `block` are expected to be the same ones used for the `Decl`.
pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void {
switch (ty.tag()) {
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
if (struct_obj.owner_decl.namespace != sema.owner_decl.namespace) return;
switch (struct_obj.status) {
.none => {},
.field_types_wip => {
return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty});
},
.have_field_types, .have_layout, .layout_wip => return,
}
const prev_namespace = sema.namespace;
sema.namespace = &struct_obj.namespace;
defer sema.namespace = prev_namespace;
struct_obj.status = .field_types_wip;
try sema.analyzeStructFields(block, struct_obj);
struct_obj.status = .have_field_types;
},
.@"union", .union_tagged => {
const union_obj = ty.cast(Type.Payload.Union).?.data;
if (union_obj.owner_decl.namespace != sema.owner_decl.namespace) return;
switch (union_obj.status) {
.none => {},
.field_types_wip => {
return sema.mod.fail(&block.base, src, "union {} depends on itself", .{ty});
},
.have_field_types, .have_layout, .layout_wip => return,
}
const prev_namespace = sema.namespace;
sema.namespace = &union_obj.namespace;
defer sema.namespace = prev_namespace;
union_obj.status = .field_types_wip;
try sema.analyzeUnionFields(block, union_obj);
union_obj.status = .have_field_types;
},
else => return,
}
}
fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type {
switch (ty.tag()) {
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
switch (struct_obj.status) {
.none => unreachable,
.field_types_wip => {
return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty});
},
.have_field_types, .have_layout, .layout_wip => return ty,
}
struct_obj.status = .field_types_wip;
try sema.mod.analyzeStructFields(struct_obj);
struct_obj.status = .have_field_types;
return ty;
},
.type_info => return sema.resolveBuiltinTypeFields(block, src, "TypeInfo"),
.extern_options => return sema.resolveBuiltinTypeFields(block, src, "ExternOptions"),
.export_options => return sema.resolveBuiltinTypeFields(block, src, "ExportOptions"),
.atomic_ordering => return sema.resolveBuiltinTypeFields(block, src, "AtomicOrdering"),
@ -9205,18 +9377,12 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type
.@"union", .union_tagged => {
const union_obj = ty.cast(Type.Payload.Union).?.data;
switch (union_obj.status) {
.none => {},
.none => unreachable,
.field_types_wip => {
return sema.mod.fail(&block.base, src, "union {} depends on itself", .{
ty,
});
return sema.mod.fail(&block.base, src, "union {} depends on itself", .{ty});
},
.have_field_types, .have_layout, .layout_wip => return ty,
}
union_obj.status = .field_types_wip;
try sema.mod.analyzeUnionFields(union_obj);
union_obj.status = .have_field_types;
return ty;
},
else => return ty,
}
@ -9232,6 +9398,265 @@ fn resolveBuiltinTypeFields(
return sema.resolveTypeFields(block, src, resolved_ty);
}
fn analyzeStructFields(
sema: *Sema,
block: *Scope.Block,
struct_obj: *Module.Struct,
) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = sema.gpa;
const zir = sema.code;
const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
assert(extended.opcode == .struct_decl);
const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
var extra_index: usize = extended.operand;
const src: LazySrcLoc = .{ .node_offset = struct_obj.node_offset };
extra_index += @boolToInt(small.has_src_node);
const body_len = if (small.has_body_len) blk: {
const body_len = zir.extra[extra_index];
extra_index += 1;
break :blk body_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = zir.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) decls_len: {
const decls_len = zir.extra[extra_index];
extra_index += 1;
break :decls_len decls_len;
} else 0;
// Skip over decls.
var decls_it = zir.declIteratorInner(extra_index, decls_len);
while (decls_it.next()) |_| {}
extra_index = decls_it.extra_index;
const body = zir.extra[extra_index..][0..body_len];
if (fields_len == 0) {
assert(body.len == 0);
return;
}
extra_index += body.len;
var decl_arena = struct_obj.owner_decl.value_arena.?.promote(gpa);
defer struct_obj.owner_decl.value_arena.?.* = decl_arena.state;
try struct_obj.fields.ensureTotalCapacity(&decl_arena.allocator, fields_len);
if (body.len != 0) {
_ = try sema.analyzeBody(block, body);
}
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
var bit_bag_index: usize = extra_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_align = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const has_default = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const is_comptime = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const unused = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
_ = unused;
const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
extra_index += 1;
const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
// This string needs to outlive the ZIR code.
const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
const field_ty: Type = if (field_type_ref == .none)
Type.initTag(.noreturn)
else
// TODO: if we need to report an error here, use a source location
// that points to this type expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
try sema.resolveType(block, src, field_type_ref);
const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
assert(!gop.found_existing);
gop.value_ptr.* = .{
.ty = try field_ty.copy(&decl_arena.allocator),
.abi_align = Value.initTag(.abi_align_default),
.default_val = Value.initTag(.unreachable_value),
.is_comptime = is_comptime,
.offset = undefined,
};
if (has_align) {
const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
// TODO: if we need to report an error here, use a source location
// that points to this alignment expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val;
gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator);
}
if (has_default) {
const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
const default_inst = sema.resolveInst(default_ref);
// TODO: if we need to report an error here, use a source location
// that points to this default value expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
const default_val = (try sema.resolveMaybeUndefVal(block, src, default_inst)) orelse
return sema.failWithNeededComptime(block, src);
gop.value_ptr.default_val = try default_val.copy(&decl_arena.allocator);
}
}
}
fn analyzeUnionFields(
sema: *Sema,
block: *Scope.Block,
union_obj: *Module.Union,
) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = sema.gpa;
const zir = sema.code;
const extended = zir.instructions.items(.data)[union_obj.zir_index].extended;
assert(extended.opcode == .union_decl);
const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small);
var extra_index: usize = extended.operand;
const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset };
extra_index += @boolToInt(small.has_src_node);
if (small.has_tag_type) {
extra_index += 1;
}
const body_len = if (small.has_body_len) blk: {
const body_len = zir.extra[extra_index];
extra_index += 1;
break :blk body_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = zir.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) decls_len: {
const decls_len = zir.extra[extra_index];
extra_index += 1;
break :decls_len decls_len;
} else 0;
// Skip over decls.
var decls_it = zir.declIteratorInner(extra_index, decls_len);
while (decls_it.next()) |_| {}
extra_index = decls_it.extra_index;
const body = zir.extra[extra_index..][0..body_len];
if (fields_len == 0) {
assert(body.len == 0);
return;
}
extra_index += body.len;
var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa);
defer union_obj.owner_decl.value_arena.?.* = decl_arena.state;
try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
if (body.len != 0) {
_ = try sema.analyzeBody(block, body);
}
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
var bit_bag_index: usize = extra_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_type = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const has_align = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const has_tag = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const unused = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
_ = unused;
const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
extra_index += 1;
const field_type_ref: Zir.Inst.Ref = if (has_type) blk: {
const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
break :blk field_type_ref;
} else .none;
const align_ref: Zir.Inst.Ref = if (has_align) blk: {
const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
break :blk align_ref;
} else .none;
if (has_tag) {
extra_index += 1;
}
// This string needs to outlive the ZIR code.
const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
const field_ty: Type = if (field_type_ref == .none)
Type.initTag(.void)
else
// TODO: if we need to report an error here, use a source location
// that points to this type expression rather than the union.
// But only resolve the source location if we need to emit a compile error.
try sema.resolveType(block, src, field_type_ref);
const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
assert(!gop.found_existing);
gop.value_ptr.* = .{
.ty = try field_ty.copy(&decl_arena.allocator),
.abi_align = Value.initTag(.abi_align_default),
};
if (align_ref != .none) {
// TODO: if we need to report an error here, use a source location
// that points to this alignment expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val;
gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator);
}
}
// TODO resolve the union tag_type_ref
}
fn getBuiltin(
sema: *Sema,
block: *Scope.Block,
@ -9344,6 +9769,7 @@ fn typeHasOnePossibleValue(
.call_options,
.export_options,
.extern_options,
.type_info,
.@"anyframe",
.anyframe_T,
.many_const_pointer,
@ -9528,6 +9954,7 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref {
.call_options => return .call_options_type,
.export_options => return .export_options_type,
.extern_options => return .extern_options_type,
.type_info => return .type_info_type,
.manyptr_u8 => return .manyptr_u8_type,
.manyptr_const_u8 => return .manyptr_const_u8_type,
.fn_noreturn_no_args => return .fn_noreturn_no_args_type,

View File

@ -687,14 +687,14 @@ pub const Inst = struct {
/// A struct literal with a specified type, with no fields.
/// Uses the `un_node` field.
struct_init_empty,
/// Given a struct, union, or enum, and a field name as a string index,
/// Given a struct or union, and a field name as a string index,
/// returns the field type. Uses the `pl_node` field. Payload is `FieldType`.
field_type,
/// Given a struct, union, or enum, and a field name as a Ref,
/// Given a struct or union, and a field name as a Ref,
/// returns the field type. Uses the `pl_node` field. Payload is `FieldTypeRef`.
field_type_ref,
/// Finalizes a typed struct initialization, performs validation, and returns the
/// struct value.
/// Finalizes a typed struct or union initialization, performs validation, and returns the
/// struct or union value.
/// Uses the `pl_node` field. Payload is `StructInit`.
struct_init,
/// Struct initialization syntax, make the result a pointer.
@ -1703,6 +1703,7 @@ pub const Inst = struct {
call_options_type,
export_options_type,
extern_options_type,
type_info_type,
manyptr_u8_type,
manyptr_const_u8_type,
fn_noreturn_no_args_type,
@ -1973,6 +1974,10 @@ pub const Inst = struct {
.ty = Type.initTag(.type),
.val = Value.initTag(.extern_options_type),
},
.type_info_type = .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_info_type),
},
.undef = .{
.ty = Type.initTag(.@"undefined"),

View File

@ -862,6 +862,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.slice_elem_val => try self.airSliceElemVal(inst),
.ptr_slice_elem_val => try self.airPtrSliceElemVal(inst),
.ptr_elem_val => try self.airPtrElemVal(inst),
.ptr_elem_ptr => try self.airPtrElemPtr(inst),
.ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst),
.constant => unreachable, // excluded from function bodies
@ -1419,6 +1420,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
else => return self.fail("TODO implement ptr_elem_ptr for {}", .{self.target.cpu.arch}),
};
return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
}
fn airPtrPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
const is_volatile = false; // TODO
const bin_op = self.air.instructions.items(.data)[inst].bin_op;

View File

@ -913,6 +913,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
.ptr_elem_val => try airPtrElemVal(o, inst, "["),
.ptr_ptr_elem_val => try airPtrElemVal(o, inst, "[0]["),
.ptr_elem_ptr => try airPtrElemPtr(o, inst),
.slice_elem_val => try airSliceElemVal(o, inst, "["),
.ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["),
@ -960,6 +961,13 @@ fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
return o.dg.fail("TODO: C backend: airPtrElemVal", .{});
}
fn airPtrElemPtr(o: *Object, inst: Air.Inst.Index) !CValue {
if (o.liveness.isUnused(inst))
return CValue.none;
return o.dg.fail("TODO: C backend: airPtrElemPtr", .{});
}
fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
const is_volatile = false; // TODO
if (!is_volatile and o.liveness.isUnused(inst))

View File

@ -432,6 +432,8 @@ pub const Object = struct {
},
else => |e| return e,
};
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
try self.updateDeclExports(module, decl, decl_exports);
}
pub fn updateDeclExports(
@ -440,7 +442,9 @@ pub const Object = struct {
decl: *const Module.Decl,
exports: []const *Module.Export,
) !void {
const llvm_fn = self.llvm_module.getNamedFunction(decl.name).?;
// If the module does not already have the function, we ignore this function call
// because we call `updateDeclExports` at the end of `updateFunc` and `updateDecl`.
const llvm_fn = self.llvm_module.getNamedFunction(decl.name) orelse return;
const is_extern = decl.val.tag() == .extern_fn;
if (is_extern or exports.len != 0) {
llvm_fn.setLinkage(.External);
@ -1041,6 +1045,7 @@ pub const FuncGen = struct {
.slice_elem_val => try self.airSliceElemVal(inst),
.ptr_slice_elem_val => try self.airPtrSliceElemVal(inst),
.ptr_elem_val => try self.airPtrElemVal(inst),
.ptr_elem_ptr => try self.airPtrElemPtr(inst),
.ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst),
.optional_payload => try self.airOptionalPayload(inst, false),
@ -1296,11 +1301,35 @@ pub const FuncGen = struct {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const base_ptr = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
const indices: [1]*const llvm.Value = .{rhs};
const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
const ptr = if (self.air.typeOf(bin_op.lhs).isSinglePointer()) ptr: {
// If this is a single-item pointer to an array, we need another index in the GEP.
const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
} else ptr: {
const indices: [1]*const llvm.Value = .{rhs};
break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
};
return self.builder.buildLoad(ptr, "");
}
fn airPtrElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
const base_ptr = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
if (self.air.typeOf(bin_op.lhs).isSinglePointer()) {
// If this is a single-item pointer to an array, we need another index in the GEP.
const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
} else {
const indices: [1]*const llvm.Value = .{rhs};
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
}
}
fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const is_volatile = false; // TODO
if (!is_volatile and self.liveness.isUnused(inst))

View File

@ -175,6 +175,7 @@ const Writer = struct {
.loop,
=> try w.writeBlock(s, inst),
.ptr_elem_ptr => try w.writePtrElemPtr(s, inst),
.struct_field_ptr => try w.writeStructField(s, inst),
.struct_field_val => try w.writeStructField(s, inst),
.constant => try w.writeConstant(s, inst),
@ -239,10 +240,19 @@ const Writer = struct {
fn writeStructField(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
const extra = w.air.extraData(Air.StructField, ty_pl.payload);
const extra = w.air.extraData(Air.StructField, ty_pl.payload).data;
try w.writeOperand(s, inst, 0, extra.data.struct_operand);
try s.print(", {d}", .{extra.data.field_index});
try w.writeOperand(s, inst, 0, extra.struct_operand);
try s.print(", {d}", .{extra.field_index});
}
fn writePtrElemPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
const extra = w.air.extraData(Air.Bin, ty_pl.payload).data;
try w.writeOperand(s, inst, 0, extra.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 0, extra.rhs);
}
fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {

View File

@ -133,6 +133,7 @@ pub const Type = extern union {
.@"union",
.union_tagged,
.type_info,
=> return .Union,
.var_args_param => unreachable, // can be any type
@ -248,6 +249,30 @@ pub const Type = extern union {
};
}
pub fn ptrIsMutable(ty: Type) bool {
return switch (ty.tag()) {
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.single_const_pointer,
.many_const_pointer,
.manyptr_const_u8,
.c_const_pointer,
.const_slice,
=> false,
.single_mut_pointer,
.many_mut_pointer,
.manyptr_u8,
.c_mut_pointer,
.mut_slice,
=> true,
.pointer => ty.castTag(.pointer).?.data.mutable,
else => unreachable,
};
}
pub fn ptrInfo(self: Type) Payload.Pointer {
switch (self.tag()) {
.single_const_pointer_to_comptime_int => return .{ .data = .{
@ -717,6 +742,7 @@ pub const Type = extern union {
.call_options,
.export_options,
.extern_options,
.type_info,
.@"anyframe",
.generic_poison,
=> unreachable,
@ -928,6 +954,7 @@ pub const Type = extern union {
.call_options => return writer.writeAll("std.builtin.CallOptions"),
.export_options => return writer.writeAll("std.builtin.ExportOptions"),
.extern_options => return writer.writeAll("std.builtin.ExternOptions"),
.type_info => return writer.writeAll("std.builtin.TypeInfo"),
.function => {
const payload = ty.castTag(.function).?.data;
try writer.writeAll("fn(");
@ -1178,6 +1205,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.enum_literal,
.type_info,
=> true,
.var_args_param => unreachable,
@ -1269,6 +1297,7 @@ pub const Type = extern union {
.call_options => return Value.initTag(.call_options_type),
.export_options => return Value.initTag(.export_options_type),
.extern_options => return Value.initTag(.extern_options_type),
.type_info => return Value.initTag(.type_info_type),
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
else => return Value.Tag.ty.create(allocator, self),
@ -1409,6 +1438,7 @@ pub const Type = extern union {
.empty_struct,
.empty_struct_literal,
.@"opaque",
.type_info,
=> false,
.inferred_alloc_const => unreachable,
@ -1636,6 +1666,7 @@ pub const Type = extern union {
.inferred_alloc_mut,
.@"opaque",
.var_args_param,
.type_info,
=> unreachable,
.generic_poison => unreachable,
@ -1667,6 +1698,7 @@ pub const Type = extern union {
.@"opaque" => unreachable,
.var_args_param => unreachable,
.generic_poison => unreachable,
.type_info => unreachable,
.@"struct" => {
const s = self.castTag(.@"struct").?.data;
@ -1978,6 +2010,7 @@ pub const Type = extern union {
.call_options,
.export_options,
.extern_options,
.type_info,
=> @panic("TODO at some point we gotta resolve builtin types"),
};
}
@ -2691,6 +2724,7 @@ pub const Type = extern union {
.call_options,
.export_options,
.extern_options,
.type_info,
.@"anyframe",
.anyframe_T,
.many_const_pointer,
@ -2778,6 +2812,7 @@ pub const Type = extern union {
return switch (self.tag()) {
.@"struct" => &self.castTag(.@"struct").?.data.namespace,
.enum_full => &self.castTag(.enum_full).?.data.namespace,
.enum_nonexhaustive => &self.castTag(.enum_nonexhaustive).?.data.namespace,
.empty_struct => self.castTag(.empty_struct).?.data,
.@"opaque" => &self.castTag(.@"opaque").?.data,
.@"union" => &self.castTag(.@"union").?.data.namespace,
@ -3022,6 +3057,7 @@ pub const Type = extern union {
.call_options,
.export_options,
.extern_options,
.type_info,
=> @panic("TODO resolve std.builtin types"),
else => unreachable,
}
@ -3058,6 +3094,7 @@ pub const Type = extern union {
.call_options,
.export_options,
.extern_options,
.type_info,
=> @panic("TODO resolve std.builtin types"),
else => unreachable,
}
@ -3167,6 +3204,7 @@ pub const Type = extern union {
call_options,
export_options,
extern_options,
type_info,
manyptr_u8,
manyptr_const_u8,
fn_noreturn_no_args,
@ -3289,6 +3327,7 @@ pub const Type = extern union {
.call_options,
.export_options,
.extern_options,
.type_info,
.@"anyframe",
=> @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),

View File

@ -68,6 +68,7 @@ pub const Value = extern union {
call_options_type,
export_options_type,
extern_options_type,
type_info_type,
manyptr_u8_type,
manyptr_const_u8_type,
fn_noreturn_no_args_type,
@ -221,6 +222,7 @@ pub const Value = extern union {
.call_options_type,
.export_options_type,
.extern_options_type,
.type_info_type,
.generic_poison,
=> @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"),
@ -402,6 +404,7 @@ pub const Value = extern union {
.call_options_type,
.export_options_type,
.extern_options_type,
.type_info_type,
.generic_poison,
=> unreachable,
@ -585,6 +588,7 @@ pub const Value = extern union {
.call_options_type => return out_stream.writeAll("std.builtin.CallOptions"),
.export_options_type => return out_stream.writeAll("std.builtin.ExportOptions"),
.extern_options_type => return out_stream.writeAll("std.builtin.ExternOptions"),
.type_info_type => return out_stream.writeAll("std.builtin.TypeInfo"),
.abi_align_default => return out_stream.writeAll("(default ABI alignment)"),
.empty_struct_value => return out_stream.writeAll("struct {}{}"),
@ -743,6 +747,7 @@ pub const Value = extern union {
.call_options_type => Type.initTag(.call_options),
.export_options_type => Type.initTag(.export_options),
.extern_options_type => Type.initTag(.extern_options),
.type_info_type => Type.initTag(.type_info),
.int_type => {
const payload = self.castTag(.int_type).?.data;
@ -1514,6 +1519,31 @@ pub const Value = extern union {
return Tag.int_u64.create(arena, truncated);
}
pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
// TODO is this a performance issue? maybe we should try the operation without
// resorting to BigInt first.
var lhs_space: Value.BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space);
const shift = rhs.toUnsignedInt();
const limbs = try allocator.alloc(
std.math.big.Limb,
lhs_bigint.limbs.len - (shift / (@sizeOf(std.math.big.Limb) * 8)),
);
var result_bigint = BigIntMutable{
.limbs = limbs,
.positive = undefined,
.len = undefined,
};
result_bigint.shiftRight(lhs_bigint, shift);
const result_limbs = result_bigint.limbs[0..result_bigint.len];
if (result_bigint.positive) {
return Value.Tag.int_big_positive.create(allocator, result_limbs);
} else {
return Value.Tag.int_big_negative.create(allocator, result_limbs);
}
}
pub fn floatAdd(
lhs: Value,
rhs: Value,

View File

@ -9,12 +9,13 @@ test {
_ = @import("behavior/pointers.zig");
_ = @import("behavior/if.zig");
_ = @import("behavior/cast.zig");
_ = @import("behavior/array.zig");
if (!builtin.zig_is_stage2) {
// Tests that only pass for stage1.
_ = @import("behavior/align.zig");
_ = @import("behavior/alignof.zig");
_ = @import("behavior/array.zig");
_ = @import("behavior/array_stage1.zig");
if (builtin.os.tag != .wasi) {
_ = @import("behavior/asm.zig");
_ = @import("behavior/async_fn.zig");

View File

@ -3,487 +3,3 @@ const testing = std.testing;
const mem = std.mem;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
test "arrays" {
var array: [5]u32 = undefined;
var i: u32 = 0;
while (i < 5) {
array[i] = i + 1;
i = array[i];
}
i = 0;
var accumulator = @as(u32, 0);
while (i < 5) {
accumulator += array[i];
i += 1;
}
try expect(accumulator == 15);
try expect(getArrayLen(&array) == 5);
}
fn getArrayLen(a: []const u32) usize {
return a.len;
}
test "array with sentinels" {
const S = struct {
fn doTheTest(is_ct: bool) !void {
if (is_ct) {
var zero_sized: [0:0xde]u8 = [_:0xde]u8{};
// Disabled at runtime because of
// https://github.com/ziglang/zig/issues/4372
try expectEqual(@as(u8, 0xde), zero_sized[0]);
var reinterpreted = @ptrCast(*[1]u8, &zero_sized);
try expectEqual(@as(u8, 0xde), reinterpreted[0]);
}
var arr: [3:0x55]u8 = undefined;
// Make sure the sentinel pointer is pointing after the last element
if (!is_ct) {
const sentinel_ptr = @ptrToInt(&arr[3]);
const last_elem_ptr = @ptrToInt(&arr[2]);
try expectEqual(@as(usize, 1), sentinel_ptr - last_elem_ptr);
}
// Make sure the sentinel is writeable
arr[3] = 0x55;
}
};
try S.doTheTest(false);
comptime try S.doTheTest(true);
}
test "void arrays" {
var array: [4]void = undefined;
array[0] = void{};
array[1] = array[2];
try expect(@sizeOf(@TypeOf(array)) == 0);
try expect(array.len == 4);
}
test "array literal" {
const hex_mult = [_]u16{
4096,
256,
16,
1,
};
try expect(hex_mult.len == 4);
try expect(hex_mult[1] == 256);
}
test "array dot len const expr" {
try expect(comptime x: {
break :x some_array.len == 4;
});
}
const ArrayDotLenConstExpr = struct {
y: [some_array.len]u8,
};
const some_array = [_]u8{
0,
1,
2,
3,
};
test "nested arrays" {
const array_of_strings = [_][]const u8{
"hello",
"this",
"is",
"my",
"thing",
};
for (array_of_strings) |s, i| {
if (i == 0) try expect(mem.eql(u8, s, "hello"));
if (i == 1) try expect(mem.eql(u8, s, "this"));
if (i == 2) try expect(mem.eql(u8, s, "is"));
if (i == 3) try expect(mem.eql(u8, s, "my"));
if (i == 4) try expect(mem.eql(u8, s, "thing"));
}
}
var s_array: [8]Sub = undefined;
const Sub = struct {
b: u8,
};
const Str = struct {
a: []Sub,
};
test "set global var array via slice embedded in struct" {
var s = Str{ .a = s_array[0..] };
s.a[0].b = 1;
s.a[1].b = 2;
s.a[2].b = 3;
try expect(s_array[0].b == 1);
try expect(s_array[1].b == 2);
try expect(s_array[2].b == 3);
}
test "array literal with specified size" {
var array = [2]u8{
1,
2,
};
try expect(array[0] == 1);
try expect(array[1] == 2);
}
test "array len field" {
var arr = [4]u8{ 0, 0, 0, 0 };
var ptr = &arr;
try expect(arr.len == 4);
comptime try expect(arr.len == 4);
try expect(ptr.len == 4);
comptime try expect(ptr.len == 4);
}
test "single-item pointer to array indexing and slicing" {
try testSingleItemPtrArrayIndexSlice();
comptime try testSingleItemPtrArrayIndexSlice();
}
fn testSingleItemPtrArrayIndexSlice() !void {
{
var array: [4]u8 = "aaaa".*;
doSomeMangling(&array);
try expect(mem.eql(u8, "azya", &array));
}
{
var array = "aaaa".*;
doSomeMangling(&array);
try expect(mem.eql(u8, "azya", &array));
}
}
fn doSomeMangling(array: *[4]u8) void {
array[1] = 'z';
array[2..3][0] = 'y';
}
test "implicit cast single-item pointer" {
try testImplicitCastSingleItemPtr();
comptime try testImplicitCastSingleItemPtr();
}
fn testImplicitCastSingleItemPtr() !void {
var byte: u8 = 100;
const slice = @as(*[1]u8, &byte)[0..];
slice[0] += 1;
try expect(byte == 101);
}
fn testArrayByValAtComptime(b: [2]u8) u8 {
return b[0];
}
test "comptime evalutating function that takes array by value" {
const arr = [_]u8{ 0, 1 };
_ = comptime testArrayByValAtComptime(arr);
_ = comptime testArrayByValAtComptime(arr);
}
test "implicit comptime in array type size" {
var arr: [plusOne(10)]bool = undefined;
try expect(arr.len == 11);
}
fn plusOne(x: u32) u32 {
return x + 1;
}
test "runtime initialize array elem and then implicit cast to slice" {
var two: i32 = 2;
const x: []const i32 = &[_]i32{two};
try expect(x[0] == 2);
}
test "array literal as argument to function" {
const S = struct {
fn entry(two: i32) !void {
try foo(&[_]i32{
1,
2,
3,
});
try foo(&[_]i32{
1,
two,
3,
});
try foo2(true, &[_]i32{
1,
2,
3,
});
try foo2(true, &[_]i32{
1,
two,
3,
});
}
fn foo(x: []const i32) !void {
try expect(x[0] == 1);
try expect(x[1] == 2);
try expect(x[2] == 3);
}
fn foo2(trash: bool, x: []const i32) !void {
try expect(trash);
try expect(x[0] == 1);
try expect(x[1] == 2);
try expect(x[2] == 3);
}
};
try S.entry(2);
comptime try S.entry(2);
}
test "double nested array to const slice cast in array literal" {
const S = struct {
fn entry(two: i32) !void {
const cases = [_][]const []const i32{
&[_][]const i32{&[_]i32{1}},
&[_][]const i32{&[_]i32{ 2, 3 }},
&[_][]const i32{
&[_]i32{4},
&[_]i32{ 5, 6, 7 },
},
};
try check(&cases);
const cases2 = [_][]const i32{
&[_]i32{1},
&[_]i32{ two, 3 },
};
try expect(cases2.len == 2);
try expect(cases2[0].len == 1);
try expect(cases2[0][0] == 1);
try expect(cases2[1].len == 2);
try expect(cases2[1][0] == 2);
try expect(cases2[1][1] == 3);
const cases3 = [_][]const []const i32{
&[_][]const i32{&[_]i32{1}},
&[_][]const i32{&[_]i32{ two, 3 }},
&[_][]const i32{
&[_]i32{4},
&[_]i32{ 5, 6, 7 },
},
};
try check(&cases3);
}
fn check(cases: []const []const []const i32) !void {
try expect(cases.len == 3);
try expect(cases[0].len == 1);
try expect(cases[0][0].len == 1);
try expect(cases[0][0][0] == 1);
try expect(cases[1].len == 1);
try expect(cases[1][0].len == 2);
try expect(cases[1][0][0] == 2);
try expect(cases[1][0][1] == 3);
try expect(cases[2].len == 2);
try expect(cases[2][0].len == 1);
try expect(cases[2][0][0] == 4);
try expect(cases[2][1].len == 3);
try expect(cases[2][1][0] == 5);
try expect(cases[2][1][1] == 6);
try expect(cases[2][1][2] == 7);
}
};
try S.entry(2);
comptime try S.entry(2);
}
test "read/write through global variable array of struct fields initialized via array mult" {
const S = struct {
fn doTheTest() !void {
try expect(storage[0].term == 1);
storage[0] = MyStruct{ .term = 123 };
try expect(storage[0].term == 123);
}
pub const MyStruct = struct {
term: usize,
};
var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1;
};
try S.doTheTest();
}
test "implicit cast zero sized array ptr to slice" {
{
var b = "".*;
const c: []const u8 = &b;
try expect(c.len == 0);
}
{
var b: [0]u8 = "".*;
const c: []const u8 = &b;
try expect(c.len == 0);
}
}
test "anonymous list literal syntax" {
const S = struct {
fn doTheTest() !void {
var array: [4]u8 = .{ 1, 2, 3, 4 };
try expect(array[0] == 1);
try expect(array[1] == 2);
try expect(array[2] == 3);
try expect(array[3] == 4);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "anonymous literal in array" {
const S = struct {
const Foo = struct {
a: usize = 2,
b: usize = 4,
};
fn doTheTest() !void {
var array: [2]Foo = .{
.{ .a = 3 },
.{ .b = 3 },
};
try expect(array[0].a == 3);
try expect(array[0].b == 4);
try expect(array[1].a == 2);
try expect(array[1].b == 3);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "access the null element of a null terminated array" {
const S = struct {
fn doTheTest() !void {
var array: [4:0]u8 = .{ 'a', 'o', 'e', 'u' };
try expect(array[4] == 0);
var len: usize = 4;
try expect(array[len] == 0);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "type deduction for array subscript expression" {
const S = struct {
fn doTheTest() !void {
var array = [_]u8{ 0x55, 0xAA };
var v0 = true;
try expectEqual(@as(u8, 0xAA), array[if (v0) 1 else 0]);
var v1 = false;
try expectEqual(@as(u8, 0x55), array[if (v1) 1 else 0]);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "sentinel element count towards the ABI size calculation" {
const S = struct {
fn doTheTest() !void {
const T = packed struct {
fill_pre: u8 = 0x55,
data: [0:0]u8 = undefined,
fill_post: u8 = 0xAA,
};
var x = T{};
var as_slice = mem.asBytes(&x);
try expectEqual(@as(usize, 3), as_slice.len);
try expectEqual(@as(u8, 0x55), as_slice[0]);
try expectEqual(@as(u8, 0xAA), as_slice[2]);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "zero-sized array with recursive type definition" {
const U = struct {
fn foo(comptime T: type, comptime n: usize) type {
return struct {
s: [n]T,
x: usize = n,
};
}
};
const S = struct {
list: U.foo(@This(), 0),
};
var t: S = .{ .list = .{ .s = undefined } };
try expectEqual(@as(usize, 0), t.list.x);
}
test "type coercion of anon struct literal to array" {
const S = struct {
const U = union {
a: u32,
b: bool,
c: []const u8,
};
fn doTheTest() !void {
var x1: u8 = 42;
const t1 = .{ x1, 56, 54 };
var arr1: [3]u8 = t1;
try expect(arr1[0] == 42);
try expect(arr1[1] == 56);
try expect(arr1[2] == 54);
var x2: U = .{ .a = 42 };
const t2 = .{ x2, .{ .b = true }, .{ .c = "hello" } };
var arr2: [3]U = t2;
try expect(arr2[0].a == 42);
try expect(arr2[1].b == true);
try expect(mem.eql(u8, arr2[2].c, "hello"));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "type coercion of pointer to anon struct literal to pointer to array" {
const S = struct {
const U = union {
a: u32,
b: bool,
c: []const u8,
};
fn doTheTest() !void {
var x1: u8 = 42;
const t1 = &.{ x1, 56, 54 };
var arr1: *const [3]u8 = t1;
try expect(arr1[0] == 42);
try expect(arr1[1] == 56);
try expect(arr1[2] == 54);
var x2: U = .{ .a = 42 };
const t2 = &.{ x2, .{ .b = true }, .{ .c = "hello" } };
var arr2: *const [3]U = t2;
try expect(arr2[0].a == 42);
try expect(arr2[1].b == true);
try expect(mem.eql(u8, arr2[2].c, "hello"));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}

View File

@ -0,0 +1,489 @@
const std = @import("std");
const testing = std.testing;
const mem = std.mem;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
test "arrays" {
var array: [5]u32 = undefined;
var i: u32 = 0;
while (i < 5) {
array[i] = i + 1;
i = array[i];
}
i = 0;
var accumulator = @as(u32, 0);
while (i < 5) {
accumulator += array[i];
i += 1;
}
try expect(accumulator == 15);
try expect(getArrayLen(&array) == 5);
}
fn getArrayLen(a: []const u32) usize {
return a.len;
}
test "array with sentinels" {
const S = struct {
fn doTheTest(is_ct: bool) !void {
if (is_ct) {
var zero_sized: [0:0xde]u8 = [_:0xde]u8{};
// Disabled at runtime because of
// https://github.com/ziglang/zig/issues/4372
try expectEqual(@as(u8, 0xde), zero_sized[0]);
var reinterpreted = @ptrCast(*[1]u8, &zero_sized);
try expectEqual(@as(u8, 0xde), reinterpreted[0]);
}
var arr: [3:0x55]u8 = undefined;
// Make sure the sentinel pointer is pointing after the last element
if (!is_ct) {
const sentinel_ptr = @ptrToInt(&arr[3]);
const last_elem_ptr = @ptrToInt(&arr[2]);
try expectEqual(@as(usize, 1), sentinel_ptr - last_elem_ptr);
}
// Make sure the sentinel is writeable
arr[3] = 0x55;
}
};
try S.doTheTest(false);
comptime try S.doTheTest(true);
}
test "void arrays" {
var array: [4]void = undefined;
array[0] = void{};
array[1] = array[2];
try expect(@sizeOf(@TypeOf(array)) == 0);
try expect(array.len == 4);
}
test "array literal" {
const hex_mult = [_]u16{
4096,
256,
16,
1,
};
try expect(hex_mult.len == 4);
try expect(hex_mult[1] == 256);
}
test "array dot len const expr" {
try expect(comptime x: {
break :x some_array.len == 4;
});
}
const ArrayDotLenConstExpr = struct {
y: [some_array.len]u8,
};
const some_array = [_]u8{
0,
1,
2,
3,
};
test "nested arrays" {
const array_of_strings = [_][]const u8{
"hello",
"this",
"is",
"my",
"thing",
};
for (array_of_strings) |s, i| {
if (i == 0) try expect(mem.eql(u8, s, "hello"));
if (i == 1) try expect(mem.eql(u8, s, "this"));
if (i == 2) try expect(mem.eql(u8, s, "is"));
if (i == 3) try expect(mem.eql(u8, s, "my"));
if (i == 4) try expect(mem.eql(u8, s, "thing"));
}
}
var s_array: [8]Sub = undefined;
const Sub = struct {
b: u8,
};
const Str = struct {
a: []Sub,
};
test "set global var array via slice embedded in struct" {
var s = Str{ .a = s_array[0..] };
s.a[0].b = 1;
s.a[1].b = 2;
s.a[2].b = 3;
try expect(s_array[0].b == 1);
try expect(s_array[1].b == 2);
try expect(s_array[2].b == 3);
}
test "array literal with specified size" {
var array = [2]u8{
1,
2,
};
try expect(array[0] == 1);
try expect(array[1] == 2);
}
test "array len field" {
var arr = [4]u8{ 0, 0, 0, 0 };
var ptr = &arr;
try expect(arr.len == 4);
comptime try expect(arr.len == 4);
try expect(ptr.len == 4);
comptime try expect(ptr.len == 4);
}
test "single-item pointer to array indexing and slicing" {
try testSingleItemPtrArrayIndexSlice();
comptime try testSingleItemPtrArrayIndexSlice();
}
fn testSingleItemPtrArrayIndexSlice() !void {
{
var array: [4]u8 = "aaaa".*;
doSomeMangling(&array);
try expect(mem.eql(u8, "azya", &array));
}
{
var array = "aaaa".*;
doSomeMangling(&array);
try expect(mem.eql(u8, "azya", &array));
}
}
fn doSomeMangling(array: *[4]u8) void {
array[1] = 'z';
array[2..3][0] = 'y';
}
test "implicit cast single-item pointer" {
try testImplicitCastSingleItemPtr();
comptime try testImplicitCastSingleItemPtr();
}
fn testImplicitCastSingleItemPtr() !void {
var byte: u8 = 100;
const slice = @as(*[1]u8, &byte)[0..];
slice[0] += 1;
try expect(byte == 101);
}
fn testArrayByValAtComptime(b: [2]u8) u8 {
return b[0];
}
test "comptime evalutating function that takes array by value" {
const arr = [_]u8{ 0, 1 };
_ = comptime testArrayByValAtComptime(arr);
_ = comptime testArrayByValAtComptime(arr);
}
test "implicit comptime in array type size" {
var arr: [plusOne(10)]bool = undefined;
try expect(arr.len == 11);
}
fn plusOne(x: u32) u32 {
return x + 1;
}
test "runtime initialize array elem and then implicit cast to slice" {
var two: i32 = 2;
const x: []const i32 = &[_]i32{two};
try expect(x[0] == 2);
}
test "array literal as argument to function" {
const S = struct {
fn entry(two: i32) !void {
try foo(&[_]i32{
1,
2,
3,
});
try foo(&[_]i32{
1,
two,
3,
});
try foo2(true, &[_]i32{
1,
2,
3,
});
try foo2(true, &[_]i32{
1,
two,
3,
});
}
fn foo(x: []const i32) !void {
try expect(x[0] == 1);
try expect(x[1] == 2);
try expect(x[2] == 3);
}
fn foo2(trash: bool, x: []const i32) !void {
try expect(trash);
try expect(x[0] == 1);
try expect(x[1] == 2);
try expect(x[2] == 3);
}
};
try S.entry(2);
comptime try S.entry(2);
}
test "double nested array to const slice cast in array literal" {
const S = struct {
fn entry(two: i32) !void {
const cases = [_][]const []const i32{
&[_][]const i32{&[_]i32{1}},
&[_][]const i32{&[_]i32{ 2, 3 }},
&[_][]const i32{
&[_]i32{4},
&[_]i32{ 5, 6, 7 },
},
};
try check(&cases);
const cases2 = [_][]const i32{
&[_]i32{1},
&[_]i32{ two, 3 },
};
try expect(cases2.len == 2);
try expect(cases2[0].len == 1);
try expect(cases2[0][0] == 1);
try expect(cases2[1].len == 2);
try expect(cases2[1][0] == 2);
try expect(cases2[1][1] == 3);
const cases3 = [_][]const []const i32{
&[_][]const i32{&[_]i32{1}},
&[_][]const i32{&[_]i32{ two, 3 }},
&[_][]const i32{
&[_]i32{4},
&[_]i32{ 5, 6, 7 },
},
};
try check(&cases3);
}
fn check(cases: []const []const []const i32) !void {
try expect(cases.len == 3);
try expect(cases[0].len == 1);
try expect(cases[0][0].len == 1);
try expect(cases[0][0][0] == 1);
try expect(cases[1].len == 1);
try expect(cases[1][0].len == 2);
try expect(cases[1][0][0] == 2);
try expect(cases[1][0][1] == 3);
try expect(cases[2].len == 2);
try expect(cases[2][0].len == 1);
try expect(cases[2][0][0] == 4);
try expect(cases[2][1].len == 3);
try expect(cases[2][1][0] == 5);
try expect(cases[2][1][1] == 6);
try expect(cases[2][1][2] == 7);
}
};
try S.entry(2);
comptime try S.entry(2);
}
test "read/write through global variable array of struct fields initialized via array mult" {
const S = struct {
fn doTheTest() !void {
try expect(storage[0].term == 1);
storage[0] = MyStruct{ .term = 123 };
try expect(storage[0].term == 123);
}
pub const MyStruct = struct {
term: usize,
};
var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1;
};
try S.doTheTest();
}
test "implicit cast zero sized array ptr to slice" {
{
var b = "".*;
const c: []const u8 = &b;
try expect(c.len == 0);
}
{
var b: [0]u8 = "".*;
const c: []const u8 = &b;
try expect(c.len == 0);
}
}
test "anonymous list literal syntax" {
const S = struct {
fn doTheTest() !void {
var array: [4]u8 = .{ 1, 2, 3, 4 };
try expect(array[0] == 1);
try expect(array[1] == 2);
try expect(array[2] == 3);
try expect(array[3] == 4);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "anonymous literal in array" {
const S = struct {
const Foo = struct {
a: usize = 2,
b: usize = 4,
};
fn doTheTest() !void {
var array: [2]Foo = .{
.{ .a = 3 },
.{ .b = 3 },
};
try expect(array[0].a == 3);
try expect(array[0].b == 4);
try expect(array[1].a == 2);
try expect(array[1].b == 3);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "access the null element of a null terminated array" {
const S = struct {
fn doTheTest() !void {
var array: [4:0]u8 = .{ 'a', 'o', 'e', 'u' };
try expect(array[4] == 0);
var len: usize = 4;
try expect(array[len] == 0);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "type deduction for array subscript expression" {
const S = struct {
fn doTheTest() !void {
var array = [_]u8{ 0x55, 0xAA };
var v0 = true;
try expectEqual(@as(u8, 0xAA), array[if (v0) 1 else 0]);
var v1 = false;
try expectEqual(@as(u8, 0x55), array[if (v1) 1 else 0]);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "sentinel element count towards the ABI size calculation" {
const S = struct {
fn doTheTest() !void {
const T = packed struct {
fill_pre: u8 = 0x55,
data: [0:0]u8 = undefined,
fill_post: u8 = 0xAA,
};
var x = T{};
var as_slice = mem.asBytes(&x);
try expectEqual(@as(usize, 3), as_slice.len);
try expectEqual(@as(u8, 0x55), as_slice[0]);
try expectEqual(@as(u8, 0xAA), as_slice[2]);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "zero-sized array with recursive type definition" {
const U = struct {
fn foo(comptime T: type, comptime n: usize) type {
return struct {
s: [n]T,
x: usize = n,
};
}
};
const S = struct {
list: U.foo(@This(), 0),
};
var t: S = .{ .list = .{ .s = undefined } };
try expectEqual(@as(usize, 0), t.list.x);
}
test "type coercion of anon struct literal to array" {
const S = struct {
const U = union {
a: u32,
b: bool,
c: []const u8,
};
fn doTheTest() !void {
var x1: u8 = 42;
const t1 = .{ x1, 56, 54 };
var arr1: [3]u8 = t1;
try expect(arr1[0] == 42);
try expect(arr1[1] == 56);
try expect(arr1[2] == 54);
var x2: U = .{ .a = 42 };
const t2 = .{ x2, .{ .b = true }, .{ .c = "hello" } };
var arr2: [3]U = t2;
try expect(arr2[0].a == 42);
try expect(arr2[1].b == true);
try expect(mem.eql(u8, arr2[2].c, "hello"));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "type coercion of pointer to anon struct literal to pointer to array" {
const S = struct {
const U = union {
a: u32,
b: bool,
c: []const u8,
};
fn doTheTest() !void {
var x1: u8 = 42;
const t1 = &.{ x1, 56, 54 };
var arr1: *const [3]u8 = t1;
try expect(arr1[0] == 42);
try expect(arr1[1] == 56);
try expect(arr1[2] == 54);
var x2: U = .{ .a = 42 };
const t2 = &.{ x2, .{ .b = true }, .{ .c = "hello" } };
var arr2: *const [3]U = t2;
try expect(arr2[0].a == 42);
try expect(arr2[1].b == true);
try expect(mem.eql(u8, arr2[2].c, "hello"));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}

View File

@ -130,3 +130,21 @@ test "no undeclared identifier error in unanalyzed branches" {
lol_this_doesnt_exist = nonsense;
}
}
test "a type constructed in a global expression" {
var l: List = undefined;
l.array[0] = 10;
l.array[1] = 11;
l.array[2] = 12;
const ptr = @ptrCast([*]u8, &l.array);
try expect(ptr[0] == 10);
try expect(ptr[1] == 11);
try expect(ptr[2] == 12);
}
const List = blk: {
const T = [10]u8;
break :blk struct {
array: T,
};
};

View File

@ -32,18 +32,20 @@ pub fn addCases(ctx: *TestContext) !void {
var case = ctx.exeUsingLlvmBackend("shift right + left", linux_x64);
case.addCompareOutput(
\\pub export fn main() void {
\\pub export fn main() c_int {
\\ var i: u32 = 16;
\\ assert(i >> 1, 8);
\\ return 0;
\\}
\\fn assert(a: u32, b: u32) void {
\\ if (a != b) unreachable;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() void {
\\pub export fn main() c_int {
\\ var i: u32 = 16;
\\ assert(i << 1, 32);
\\ return 0;
\\}
\\fn assert(a: u32, b: u32) void {
\\ if (a != b) unreachable;