Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 45 additions & 13 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3885,9 +3885,49 @@ pub const Type = struct {
if (entries.len == 1)
return entries[0].type;

// Mirror Zig's peer type resolution by ignoring branches that never
// produce a value (`noreturn`, `@compileError`) or that coerce to
// any of the other peers (`undefined`).
var filtered: std.ArrayList(TypeWithDescriptor) = try .initCapacity(arena, entries.len);
for (entries) |entry| {
switch (entry.type.data) {
.compile_error => continue,
.ip_index => |payload| switch (payload.type) {
.noreturn_type, .undefined_type => continue,
else => {},
},
else => {},
}
filtered.appendAssumeCapacity(entry);
}
const remaining = filtered.items;

if (remaining.len == 0) {
// Every branch is `noreturn`, `undefined` or `@compileError`.
// An `undefined` branch determines the result; otherwise prefer
// `noreturn` over `@compileError` so that the result does not
// depend on branch order.
for (entries) |entry| {
switch (entry.type.data) {
.ip_index => |payload| if (payload.type == .undefined_type) return entry.type,
else => {},
}
}
for (entries) |entry| {
switch (entry.type.data) {
.ip_index => |payload| if (payload.type == .noreturn_type) return entry.type,
else => {},
}
}
return entries[0].type;
}

if (remaining.len == 1)
return remaining[0].type;

peer_type_resolution: {
var chosen = entries[0].type;
for (entries[1..]) |entry| {
var chosen = remaining[0].type;
for (remaining[1..]) |entry| {
const candidate = entry.type;
chosen = try analyser.resolvePeerTypes(chosen, candidate) orelse break :peer_type_resolution;
}
Expand Down Expand Up @@ -3917,29 +3957,21 @@ pub const Type = struct {
var deduplicator: Deduplicator = .empty;
defer deduplicator.deinit(arena);

const has_type_val = for (entries) |entry| {
if (entry.type.data == .compile_error) {
continue;
}
break entry.type.is_type_val;
} else entries[0].type.is_type_val;
const has_type_val = remaining[0].type.is_type_val;

for (entries) |entry| {
for (remaining) |entry| {
try deduplicator.put(
arena,
.{ .type_data = entry.type.data, .descriptor = entry.descriptor },
{},
);
if (entry.type.data == .compile_error) {
continue;
}
if (entry.type.is_type_val != has_type_val) {
return null;
}
}

if (deduplicator.count() == 1)
return entries[0].type;
return remaining[0].type;

return .{
.data = .{ .either = try arena.dupe(Type.Data.EitherEntry, deduplicator.keys()) },
Expand Down
11 changes: 8 additions & 3 deletions tests/analysis/either.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ const UnknownType: type = undefined;
const unknown_value: UnknownType = undefined;

const non_type_and_type: if (true) undefined else i32 = unknown_value;
// ^^^^^^^^^^^^^^^^^ (unknown)()
// ^^^^^^^^^^^^^^^^^ (i32)()

const type_and_non_type: if (true) i32 else undefined = unknown_value;
// ^^^^^^^^^^^^^^^^^ (unknown)()
// ^^^^^^^^^^^^^^^^^ (i32)()

const compile_error_and_type: if (true) @compileError("Foo") else i32 = 1;
// ^^^^^^^^^^^^^^^^^^^^^^ (either type)()
// ^^^^^^^^^^^^^^^^^^^^^^ (i32)()

const type_and_compile_error: if (true) i32 else @compileError("Foo") = 1;
// ^^^^^^^^^^^^^^^^^^^^^^ (i32)()

// The literal type values `noreturn`/`undefined` are valid peers and must
// not be mistaken for value-level branches that never produce a value.
const type_and_noreturn_type: if (true) u8 else noreturn = unknown_value;
// ^^^^^^^^^^^^^^^^^^^^^^ (either type)()

const StructU32 = struct {
Expand Down
8 changes: 8 additions & 0 deletions tests/analysis/peer_type_resolution.zig
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ pub fn main() !void {
_ = noreturn_1;
// ^^^^^^^^^^ (S)()

const undefined_0 = if (runtime_bool) s else undefined;
_ = undefined_0;
// ^^^^^^^^^^^ (S)()

const undefined_1 = if (runtime_bool) undefined else s;
_ = undefined_1;
// ^^^^^^^^^^^ (S)()

// Use @compileLog to verify the expected type with the compiler:
// @compileLog(error_union_0);

Expand Down
37 changes: 37 additions & 0 deletions tests/analysis/switch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,41 @@ const labeled_switch_2 = blk: switch (undefined) {
2 => B{},
else => unreachable,
};

// https://github.com/zigtools/zls/issues/3210
const switch_with_unreachable = switch (some_u8) {
// ^^^^^^^^^^^^^^^^^^^^^^^ (type)(A)
1 => A,
else => unreachable,
};

const switch_with_undefined = switch (some_u8) {
// ^^^^^^^^^^^^^^^^^^^^^ (type)(A)
1 => A,
else => undefined,
};

const switch_with_compile_error = switch (some_u8) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^ (type)(A)
1 => A,
else => @compileError(""),
};

const switch_only_noreturn = switch (some_u8) {
// ^^^^^^^^^^^^^^^^^^^^ (noreturn)()
1 => unreachable,
else => @compileError(""),
};

const switch_only_noreturn_2 = switch (some_u8) {
// ^^^^^^^^^^^^^^^^^^^^^^ (noreturn)()
1 => @compileError(""),
else => unreachable,
};

const switch_undefined_and_noreturn = switch (some_u8) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (@TypeOf(undefined))(undefined)
1 => unreachable,
else => undefined,
};
// zig fmt: on