Sema: rework peer type logic for pointers

Now it's centered around a switch on the chosen type tag which gives us
easy access to pointer data.

The logic is simplied and in some cases logic is removed when it is
sufficient to choose the type that is a better coercion target without
knowing whether such coercion will succeed ahead of time.

A bug is fixed at the bottom of the function; we were doing the opposite
of what we were supposed to with `seen_const`.

Also the bottom of the function has a more complete handling of the
possible combinations of `any_are_null`, `convert_to_slice`, and
`err_set_ty`.

In the behavior tests, not as many backends needed to be skipped.
This commit is contained in:
Andrew Kelley 2022-03-03 23:12:18 -07:00
parent 26be5bb8b1
commit 63c5c510b1
2 changed files with 139 additions and 157 deletions

View File

@ -18279,146 +18279,126 @@ fn resolvePeerTypes(
},
},
.Pointer => {
if (candidate_ty.ptrSize() == .C) {
// *T to [*c]T
if (chosen_ty_tag == .Pointer) {
const chosen_elem_ty = chosen_ty.childType();
const candidate_elem_ty = candidate_ty.childType();
if ((try sema.coerceInMemoryAllowed(block, chosen_elem_ty, candidate_elem_ty, false, target, src, src)) == .ok) {
const cand_info = candidate_ty.ptrInfo().data;
switch (chosen_ty_tag) {
.Pointer => {
const chosen_info = chosen_ty.ptrInfo().data;
seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable;
// *[N]T to [*]T
// *[N]T to []T
if ((cand_info.size == .Many or cand_info.size == .Slice) and
chosen_info.size == .One and
chosen_info.pointee_type.zigTypeTag() == .Array)
{
// In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T`
convert_to_slice = false;
chosen = candidate;
chosen_i = candidate_i + 1;
continue;
}
}
if (cand_info.size == .One and
cand_info.pointee_type.zigTypeTag() == .Array and
(chosen_info.size == .Many or chosen_info.size == .Slice))
{
// In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T`
convert_to_slice = false;
continue;
}
if (chosen_ty_tag == .Int or chosen_ty_tag == .ComptimeInt) {
chosen = candidate;
chosen_i = candidate_i + 1;
continue;
}
if (chosen_ty_tag == .Pointer and chosen_ty.ptrSize() != .Slice) {
continue;
}
}
// *[N]T and *[M]T
// Verify both are single-pointers to arrays.
// Keep the one whose element type can be coerced into.
if (chosen_info.size == .One and
cand_info.size == .One and
chosen_info.pointee_type.zigTypeTag() == .Array and
cand_info.pointee_type.zigTypeTag() == .Array)
{
const chosen_elem_ty = chosen_info.pointee_type.childType();
const cand_elem_ty = cand_info.pointee_type.childType();
// [*c]T and *T
if (chosen_ty_tag == .Pointer and chosen_ty.ptrSize() == .C) {
const chosen_elem_ty = chosen_ty.childType();
const candidate_elem_ty = candidate_ty.childType();
if ((try sema.coerceInMemoryAllowed(block, chosen_elem_ty, candidate_elem_ty, false, target, src, src)) == .ok) {
continue;
}
}
// *[N]T to [*]T
if (candidate_ty.ptrSize() == .Many and
chosen_ty_tag == .Pointer and
chosen_ty.ptrSize() == .One and
chosen_ty.childType().zigTypeTag() == .Array)
{
chosen = candidate;
chosen_i = candidate_i + 1;
convert_to_slice = false;
if (chosen_ty.isConstPtr() and !candidate_ty.isConstPtr())
seen_const = true;
continue;
}
// *[N]T to [*]T (prev is many pointer)
if (candidate_ty.ptrSize() == .One and
candidate_ty.childType().zigTypeTag() == .Array and
chosen_ty_tag == .Pointer and
chosen_ty.ptrSize() == .Many)
{
if (candidate_ty.isConstPtr() and !chosen_ty.isConstPtr())
seen_const = true;
continue;
}
// *[N]T to []T (prev is slice)
// *[N]T to E![]T
if ((chosen_ty.isSlice() or (chosen_ty_tag == .ErrorUnion and chosen_ty.errorUnionPayload().isSlice())) and
candidate_ty.ptrSize() == .One and
candidate_ty.childType().zigTypeTag() == .Array)
{
const chosen_elem_ty = switch (chosen_ty_tag) {
.ErrorUnion => chosen_ty.errorUnionPayload().elemType2(),
else => chosen_ty.elemType2(),
};
const candidate_elem_ty = candidate_ty.childType().elemType2();
if ((try sema.coerceInMemoryAllowed(block, candidate_elem_ty, chosen_elem_ty, false, target, src, src)) == .ok) {
convert_to_slice = false; // it already is a slice
// If the pointer is const then we need to const
if (candidate_ty.isConstPtr())
seen_const = true;
continue;
}
}
// *[N]T to []T (current is slice)
if (chosen_ty_tag == .Pointer and
chosen_ty.ptrSize() == .One and
chosen_ty.childType().zigTypeTag() == .Array and
candidate_ty.isSlice())
{
const chosen_child_ty = chosen_ty.childType();
const chosen_elem_ty = chosen_child_ty.elemType2();
const candidate_elem_ty = candidate_ty.elemType2();
if ((try sema.coerceInMemoryAllowed(block, candidate_elem_ty, chosen_elem_ty, false, target, src, src)) == .ok) {
chosen = candidate;
chosen_i = candidate_i + 1;
convert_to_slice = false; // it already is a slice
// If the prev pointer is const then we need to const
if (chosen_ty.isConstPtr())
seen_const = true;
continue;
}
}
// *[N]T and *[M]T
// verify both are pointers to known lengths
if (chosen_ty_tag == .Pointer and
chosen_ty.ptrSize() == .One and
candidate_ty.ptrSize() == .One)
{
// verify both pointers are two arrays
const chosen_child_ty = chosen_ty.childType();
const candidate_child_ty = candidate_ty.childType();
if (chosen_child_ty.zigTypeTag() == .Array and candidate_child_ty.zigTypeTag() == .Array) {
// If we can cerce the element types, then we can do this.
const chosen_elem_ty = chosen_child_ty.elemType2();
const candidate_elem_ty = candidate_child_ty.elemType2();
if ((try sema.coerceInMemoryAllowed(block, candidate_elem_ty, chosen_elem_ty, false, target, src, src)) == .ok) {
// If there is a sentinel, it must match
if (chosen_child_ty.sentinel()) |chosen_sentinel| {
if (candidate_child_ty.sentinel()) |candidate_sentinel| {
if (!chosen_sentinel.eql(candidate_sentinel, chosen_elem_ty))
continue;
} else continue;
const chosen_ok = .ok == try sema.coerceInMemoryAllowed(block, chosen_elem_ty, cand_elem_ty, chosen_info.mutable, target, src, src);
if (chosen_ok) {
convert_to_slice = true;
continue;
}
const cand_ok = .ok == try sema.coerceInMemoryAllowed(block, cand_elem_ty, chosen_elem_ty, cand_info.mutable, target, src, src);
if (cand_ok) {
convert_to_slice = true;
chosen = candidate;
chosen_i = candidate_i + 1;
continue;
}
// They're both bad. Report error.
// In the future we probably want to use the
// coerceInMemoryAllowed error reporting mechanism,
// however, for now we just fall through for the
// "incompatible types" error below.
}
// [*c]T and any other pointer size
// Whichever element type can coerce to the other one, is
// the one we will keep. If they're both OK then we keep the
// C pointer since it matches both single and many pointers.
if (cand_info.size == .C or chosen_info.size == .C) {
const cand_ok = .ok == try sema.coerceInMemoryAllowed(block, cand_info.pointee_type, chosen_info.pointee_type, cand_info.mutable, target, src, src);
const chosen_ok = .ok == try sema.coerceInMemoryAllowed(block, chosen_info.pointee_type, cand_info.pointee_type, chosen_info.mutable, target, src, src);
if (cand_ok) {
if (chosen_ok) {
if (chosen_info.size == .C) {
continue;
} else {
chosen = candidate;
chosen_i = candidate_i + 1;
continue;
}
} else {
chosen = candidate;
chosen_i = candidate_i + 1;
continue;
}
} else {
if (chosen_ok) {
continue;
} else {
// They're both bad. Report error.
// In the future we probably want to use the
// coerceInMemoryAllowed error reporting mechanism,
// however, for now we just fall through for the
// "incompatible types" error below.
}
}
}
},
.Int, .ComptimeInt => {
if (cand_info.size == .C) {
chosen = candidate;
chosen_i = candidate_i + 1;
convert_to_slice = true;
// If one of the pointers is to const data, the slice
// must also be const.
if (candidate_child_ty.isConstPtr() or chosen_child_ty.isConstPtr())
seen_const = true;
continue;
}
}
},
.ErrorUnion => {
const chosen_ptr_ty = chosen_ty.errorUnionPayload();
if (chosen_ptr_ty.zigTypeTag() == .Pointer) {
const chosen_info = chosen_ptr_ty.ptrInfo().data;
seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable;
// *[N]T to E![*]T
// *[N]T to E![]T
if (cand_info.size == .One and
cand_info.pointee_type.zigTypeTag() == .Array and
(chosen_info.size == .Many or chosen_info.size == .Slice))
{
continue;
}
}
},
else => {},
}
},
.Optional => {
@ -18512,20 +18492,13 @@ fn resolvePeerTypes(
const chosen_ty = sema.typeOf(chosen);
if (any_are_null) {
switch (chosen_ty.zigTypeTag()) {
.Null, .Optional => return chosen_ty,
else => return Type.optional(sema.arena, chosen_ty),
}
}
if (convert_to_slice) {
// turn *[N]T => []T
const chosen_child_ty = chosen_ty.childType();
var info = chosen_ty.ptrInfo();
info.data.sentinel = chosen_child_ty.sentinel();
info.data.size = .Slice;
info.data.mutable = seen_const or chosen_child_ty.isConstPtr();
info.data.mutable = !(seen_const or chosen_child_ty.isConstPtr());
info.data.pointee_type = switch (chosen_child_ty.tag()) {
.array => chosen_child_ty.elemType2(),
.array_u8, .array_u8_sentinel_0 => Type.initTag(.u8),
@ -18533,8 +18506,12 @@ fn resolvePeerTypes(
};
const new_ptr_ty = try Type.ptr(sema.arena, target, info.data);
const set_ty = err_set_ty orelse return new_ptr_ty;
return try Module.errorUnionType(sema.arena, set_ty, new_ptr_ty);
const opt_ptr_ty = if (any_are_null)
try Type.optional(sema.arena, new_ptr_ty)
else
new_ptr_ty;
const set_ty = err_set_ty orelse return opt_ptr_ty;
return try Module.errorUnionType(sema.arena, set_ty, opt_ptr_ty);
}
if (seen_const) {
@ -18545,20 +18522,37 @@ fn resolvePeerTypes(
var info = ptr_ty.ptrInfo();
info.data.mutable = false;
const new_ptr_ty = try Type.ptr(sema.arena, target, info.data);
const opt_ptr_ty = if (any_are_null)
try Type.optional(sema.arena, new_ptr_ty)
else
new_ptr_ty;
const set_ty = err_set_ty orelse chosen_ty.errorUnionSet();
return try Module.errorUnionType(sema.arena, set_ty, new_ptr_ty);
return try Module.errorUnionType(sema.arena, set_ty, opt_ptr_ty);
},
.Pointer => {
var info = chosen_ty.ptrInfo();
info.data.mutable = false;
const new_ptr_ty = try Type.ptr(sema.arena, target, info.data);
const set_ty = err_set_ty orelse return new_ptr_ty;
return try Module.errorUnionType(sema.arena, set_ty, new_ptr_ty);
const opt_ptr_ty = if (any_are_null)
try Type.optional(sema.arena, new_ptr_ty)
else
new_ptr_ty;
const set_ty = err_set_ty orelse return opt_ptr_ty;
return try Module.errorUnionType(sema.arena, set_ty, opt_ptr_ty);
},
else => return chosen_ty,
}
}
if (any_are_null) {
const opt_ty = switch (chosen_ty.zigTypeTag()) {
.Null, .Optional => chosen_ty,
else => try Type.optional(sema.arena, chosen_ty),
};
const set_ty = err_set_ty orelse return opt_ty;
return try Module.errorUnionType(sema.arena, set_ty, opt_ty);
}
if (err_set_ty) |ty| switch (chosen_ty.zigTypeTag()) {
.ErrorSet => return ty,
.ErrorUnion => {

View File

@ -128,12 +128,6 @@ fn testDerefPtrOneVal() !void {
}
test "peer type resolution with C pointers" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
var ptr_one: *u8 = undefined;
var ptr_many: [*]u8 = undefined;
var ptr_c: [*c]u8 = undefined;
@ -163,11 +157,11 @@ test "implicit casting between C pointer and optional non-C pointer" {
}
test "implicit cast error unions with non-optional to optional pointer" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
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 {
@ -384,12 +378,6 @@ test "pointer arithmetic affects the alignment" {
}
test "@ptrToInt on null optional at comptime" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
{
const pointer = @intToPtr(?*u8, 0x000);
const x = @ptrToInt(pointer);