diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-03-08 22:19:25 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2022-03-08 22:19:25 -0700 |
| commit | bb73775d40e04796b0dd60341303d567db0a8020 (patch) | |
| tree | cef4e263687d31c5255d6a2c22fb0ed006d18972 | |
| parent | 6f560c99094eec2210f2d76a2449b5f60191e11b (diff) | |
| download | zig-bb73775d40e04796b0dd60341303d567db0a8020.tar.gz zig-bb73775d40e04796b0dd60341303d567db0a8020.zip | |
Sema: implement coercion of tuples to structs
| -rw-r--r-- | src/Sema.zig | 107 | ||||
| -rw-r--r-- | src/type.zig | 12 | ||||
| -rw-r--r-- | test/behavior/struct.zig | 14 |
3 files changed, 124 insertions, 9 deletions
diff --git a/src/Sema.zig b/src/Sema.zig index 2602cdc21e..84e696527c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -15724,7 +15724,7 @@ fn tupleField( field_index_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { const tuple_ty = sema.typeOf(tuple); - const tuple_info = tuple_ty.castTag(.tuple).?.data; + const tuple_info = tuple_ty.tupleFields(); if (field_index > tuple_info.types.len) { return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{ @@ -16195,6 +16195,9 @@ fn coerce( if (inst == .empty_struct) { return structInitEmpty(sema, block, dest_ty, dest_ty_src, inst_src); } + if (inst_ty.isTupleOrAnonStruct()) { + return sema.coerceTupleToStruct(block, dest_ty, dest_ty_src, inst, inst_src); + } }, else => {}, } @@ -17442,6 +17445,94 @@ fn coerceTupleToArray( ); } +/// Handles both tuples and anon struct literals. Coerces field-wise. Reports +/// errors for both extra fields and missing fields. +fn coerceTupleToStruct( + sema: *Sema, + block: *Block, + dest_ty: Type, + dest_ty_src: LazySrcLoc, + inst: Air.Inst.Ref, + inst_src: LazySrcLoc, +) !Air.Inst.Ref { + if (dest_ty.isTupleOrAnonStruct()) { + return sema.fail(block, dest_ty_src, "TODO: implement coercion from tuples to tuples", .{}); + } + + const fields = dest_ty.structFields(); + const field_vals = try sema.arena.alloc(Value, fields.count()); + const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len); + mem.set(Air.Inst.Ref, field_refs, .none); + + const inst_ty = sema.typeOf(inst); + const tuple = inst_ty.tupleFields(); + var runtime_src: ?LazySrcLoc = null; + for (tuple.types) |_, i_usize| { + const i = @intCast(u32, i_usize); + const field_src = inst_src; // TODO better source location + const field_name = if (inst_ty.castTag(.anon_struct)) |payload| + payload.data.names[i] + else + try std.fmt.allocPrint(sema.arena, "{d}", .{i}); + const field_index = try sema.structFieldIndex(block, dest_ty, field_name, field_src); + const field = fields.values()[field_index]; + if (field.is_comptime) { + return sema.fail(block, dest_ty_src, "TODO: implement coercion from tuples to structs when one of the destination struct fields is comptime", .{}); + } + const elem_ref = try tupleField(sema, block, inst, i, inst_src, field_src); + const coerced = try sema.coerce(block, field.ty, elem_ref, field_src); + field_refs[field_index] = coerced; + if (runtime_src == null) { + if (try sema.resolveMaybeUndefVal(block, field_src, coerced)) |field_val| { + field_vals[field_index] = field_val; + } else { + runtime_src = field_src; + } + } + } + + // Populate default field values and report errors for missing fields. + var root_msg: ?*Module.ErrorMsg = null; + + for (field_refs) |*field_ref, i| { + if (field_ref.* != .none) continue; + + const field_name = fields.keys()[i]; + const field = fields.values()[i]; + const field_src = inst_src; // TODO better source location + if (field.default_val.tag() == .unreachable_value) { + const template = "missing struct field: {s}"; + const args = .{field_name}; + if (root_msg) |msg| { + try sema.errNote(block, field_src, msg, template, args); + } else { + root_msg = try sema.errMsg(block, field_src, template, args); + } + continue; + } + if (runtime_src == null) { + field_vals[i] = field.default_val; + } else { + field_ref.* = try sema.addConstant(field.ty, field.default_val); + } + } + + if (root_msg) |msg| { + try sema.addDeclaredHereNote(msg, dest_ty); + return sema.failWithOwnedErrorMsg(block, msg); + } + + if (runtime_src) |rs| { + try sema.requireRuntimeBlock(block, rs); + return block.addAggregateInit(dest_ty, field_refs); + } + + return sema.addConstant( + dest_ty, + try Value.Tag.@"struct".create(sema.arena, field_vals), + ); +} + fn analyzeDeclVal( sema: *Sema, block: *Block, @@ -20365,3 +20456,17 @@ fn unionFieldIndex( return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); return @intCast(u32, field_index_usize); } + +fn structFieldIndex( + sema: *Sema, + block: *Block, + unresolved_struct_ty: Type, + field_name: []const u8, + field_src: LazySrcLoc, +) !u32 { + const struct_ty = try sema.resolveTypeFields(block, field_src, unresolved_struct_ty); + const struct_obj = struct_ty.castTag(.@"struct").?.data; + const field_index_usize = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name); + return @intCast(u32, field_index_usize); +} diff --git a/src/type.zig b/src/type.zig index b6728b4be0..7b8dd1396b 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4519,21 +4519,23 @@ pub const Type = extern union { if (field.is_comptime) { return field.default_val; } else { - return null; + return field.ty.onePossibleValue(); } }, .tuple => { - const val = ty.castTag(.tuple).?.data.values[index]; + const tuple = ty.castTag(.tuple).?.data; + const val = tuple.values[index]; if (val.tag() == .unreachable_value) { - return null; + return tuple.types[index].onePossibleValue(); } else { return val; } }, .anon_struct => { - const val = ty.castTag(.anon_struct).?.data.values[index]; + const anon_struct = ty.castTag(.anon_struct).?.data; + const val = anon_struct.values[index]; if (val.tag() == .unreachable_value) { - return null; + return anon_struct.types[index].onePossibleValue(); } else { return val; } diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index d4190fcd21..a90f1c0609 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -995,7 +995,11 @@ test "comptime struct field" { } test "tuple element initialized with fn call" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const S = struct { fn doTheTest() !void { @@ -1011,7 +1015,7 @@ test "tuple element initialized with fn call" { } test "struct with union field" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const Value = struct { ref: u32 = 2, @@ -1029,7 +1033,11 @@ test "struct with union field" { } test "type coercion of anon struct literal to struct" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const S = struct { const S2 = struct { |
