Merge pull request #22157 from mlugg/astgen-error-lazy

compiler: allow semantic analysis of files with AstGen errors
This commit is contained in:
Andrew Kelley
2024-12-09 18:32:23 -05:00
committed by GitHub
20 changed files with 585 additions and 325 deletions

View File

@@ -74,6 +74,12 @@ pub fn MultiArrayList(comptime T: type) type {
len: usize,
capacity: usize,
pub const empty: Slice = .{
.ptrs = undefined,
.len = 0,
.capacity = 0,
};
pub fn items(self: Slice, comptime field: Field) []FieldType(field) {
const F = FieldType(field);
if (self.capacity == 0) {

View File

@@ -172,9 +172,9 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
};
defer gz_instructions.deinit(gpa);
// The AST -> ZIR lowering process assumes an AST that does not have any
// parse errors.
if (tree.errors.len == 0) {
// The AST -> ZIR lowering process assumes an AST that does not have any parse errors.
// Parse errors, or AstGen errors in the root struct, are considered "fatal", so we emit no ZIR.
const fatal = if (tree.errors.len == 0) fatal: {
if (AstGen.structDeclInner(
&gen_scope,
&gen_scope.base,
@@ -184,13 +184,15 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
0,
)) |struct_decl_ref| {
assert(struct_decl_ref.toIndex().? == .main_struct_inst);
break :fatal false;
} else |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {}, // Handled via compile_errors below.
error.AnalysisFail => break :fatal true, // Handled via compile_errors below.
}
} else {
} else fatal: {
try lowerAstErrors(&astgen);
}
break :fatal true;
};
const err_index = @intFromEnum(Zir.ExtraIndex.compile_errors);
if (astgen.compile_errors.items.len == 0) {
@@ -228,8 +230,8 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
}
}
return Zir{
.instructions = astgen.instructions.toOwnedSlice(),
return .{
.instructions = if (fatal) .empty else astgen.instructions.toOwnedSlice(),
.string_bytes = try astgen.string_bytes.toOwnedSlice(gpa),
.extra = try astgen.extra.toOwnedSlice(gpa),
};
@@ -2110,7 +2112,7 @@ fn comptimeExprAst(
) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
if (gz.is_comptime) {
return astgen.failNode(node, "redundant comptime keyword in already comptime scope", .{});
try astgen.appendErrorNode(node, "redundant comptime keyword in already comptime scope", .{});
}
const tree = astgen.tree;
const node_datas = tree.nodes.items(.data);
@@ -3269,6 +3271,9 @@ fn varDecl(
try astgen.appendErrorTok(comptime_token, "'comptime const' is redundant; instead wrap the initialization expression with 'comptime'", .{});
}
// `comptime const` is a non-fatal error; treat it like the init was marked `comptime`.
const force_comptime = var_decl.comptime_token != null;
// Depending on the type of AST the initialization expression is, we may need an lvalue
// or an rvalue as a result location. If it is an rvalue, we can use the instruction as
// the variable, no memory location needed.
@@ -3282,7 +3287,7 @@ fn varDecl(
} else .{ .rl = .none, .ctx = .const_init };
const prev_anon_name_strategy = gz.anon_name_strategy;
gz.anon_name_strategy = .dbg_var;
const init_inst = try reachableExpr(gz, scope, result_info, var_decl.ast.init_node, node);
const init_inst = try reachableExprComptime(gz, scope, result_info, var_decl.ast.init_node, node, force_comptime);
gz.anon_name_strategy = prev_anon_name_strategy;
try gz.addDbgVar(.dbg_var_val, ident_name, init_inst);
@@ -3348,7 +3353,7 @@ fn varDecl(
const prev_anon_name_strategy = gz.anon_name_strategy;
gz.anon_name_strategy = .dbg_var;
defer gz.anon_name_strategy = prev_anon_name_strategy;
const init_inst = try reachableExpr(gz, scope, init_result_info, var_decl.ast.init_node, node);
const init_inst = try reachableExprComptime(gz, scope, init_result_info, var_decl.ast.init_node, node, force_comptime);
// The const init expression may have modified the error return trace, so signal
// to Sema that it should save the new index for restoring later.
@@ -3491,7 +3496,7 @@ fn assignDestructure(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerErro
const full = tree.assignDestructure(node);
if (full.comptime_token != null and gz.is_comptime) {
return astgen.failNode(node, "redundant comptime keyword in already comptime scope", .{});
return astgen.appendErrorNode(node, "redundant comptime keyword in already comptime scope", .{});
}
// If this expression is marked comptime, we must wrap the whole thing in a comptime block.
@@ -3550,7 +3555,7 @@ fn assignDestructureMaybeDecls(
const full = tree.assignDestructure(node);
if (full.comptime_token != null and gz.is_comptime) {
return astgen.failNode(node, "redundant comptime keyword in already comptime scope", .{});
try astgen.appendErrorNode(node, "redundant comptime keyword in already comptime scope", .{});
}
const is_comptime = full.comptime_token != null or gz.is_comptime;
@@ -3664,6 +3669,7 @@ fn assignDestructureMaybeDecls(
if (full.comptime_token != null and !any_non_const_variables) {
try astgen.appendErrorTok(full.comptime_token.?, "'comptime const' is redundant; instead wrap the initialization expression with 'comptime'", .{});
// Note that this is non-fatal; we will still evaluate at comptime.
}
// If this expression is marked comptime, we must wrap it in a comptime block.
@@ -4112,8 +4118,8 @@ fn fnDecl(
// The source slice is added towards the *end* of this function.
astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column));
// missing function name already happened in scanContainer()
const fn_name_token = fn_proto.name_token orelse return error.AnalysisFail;
// missing function name already checked in scanContainer()
const fn_name_token = fn_proto.name_token.?;
// We insert this at the beginning so that its instruction index marks the
// start of the top level declaration.
@@ -5182,8 +5188,7 @@ fn structDeclInner(
if (is_comptime) {
switch (layout) {
.@"packed" => return astgen.failTok(member.comptime_token.?, "packed struct fields cannot be marked comptime", .{}),
.@"extern" => return astgen.failTok(member.comptime_token.?, "extern struct fields cannot be marked comptime", .{}),
.@"packed", .@"extern" => return astgen.failTok(member.comptime_token.?, "{s} struct fields cannot be marked comptime", .{@tagName(layout)}),
.auto => any_comptime_fields = true,
}
} else {
@@ -5210,7 +5215,7 @@ fn structDeclInner(
if (have_align) {
if (layout == .@"packed") {
try astgen.appendErrorNode(member.ast.align_expr, "unable to override alignment of packed struct fields", .{});
return astgen.failNode(member.ast.align_expr, "unable to override alignment of packed struct fields", .{});
}
any_aligned_fields = true;
const align_ref = try expr(&block_scope, &namespace.base, coerced_align_ri, member.ast.align_expr);
@@ -5304,8 +5309,7 @@ fn tupleDecl(
switch (layout) {
.auto => {},
.@"extern" => return astgen.failNode(node, "extern tuples are not supported", .{}),
.@"packed" => return astgen.failNode(node, "packed tuples are not supported", .{}),
.@"extern", .@"packed" => return astgen.failNode(node, "{s} tuples are not supported", .{@tagName(layout)}),
}
if (backing_int_node != 0) {
@@ -5688,7 +5692,7 @@ fn containerDecl(
};
};
if (counts.nonexhaustive_node != 0 and container_decl.ast.arg == 0) {
try astgen.appendErrorNodeNotes(
return astgen.failNodeNotes(
node,
"non-exhaustive enum missing integer tag type",
.{},
@@ -5911,9 +5915,19 @@ fn containerMember(
const full = tree.fullFnProto(&buf, member_node).?;
const body = if (node_tags[member_node] == .fn_decl) node_datas[member_node].rhs else 0;
const prev_decl_index = wip_members.decl_index;
astgen.fnDecl(gz, scope, wip_members, member_node, body, full) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {},
error.AnalysisFail => {
wip_members.decl_index = prev_decl_index;
try addFailedDeclaration(
wip_members,
gz,
.{ .named = full.name_token.? },
full.ast.proto_node,
full.visib_token != null,
);
},
};
},
@@ -5922,28 +5936,77 @@ fn containerMember(
.simple_var_decl,
.aligned_var_decl,
=> {
astgen.globalVarDecl(gz, scope, wip_members, member_node, tree.fullVarDecl(member_node).?) catch |err| switch (err) {
const full = tree.fullVarDecl(member_node).?;
const prev_decl_index = wip_members.decl_index;
astgen.globalVarDecl(gz, scope, wip_members, member_node, full) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {},
error.AnalysisFail => {
wip_members.decl_index = prev_decl_index;
try addFailedDeclaration(
wip_members,
gz,
.{ .named = full.ast.mut_token + 1 },
member_node,
full.visib_token != null,
);
},
};
},
.@"comptime" => {
const prev_decl_index = wip_members.decl_index;
astgen.comptimeDecl(gz, scope, wip_members, member_node) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {},
error.AnalysisFail => {
wip_members.decl_index = prev_decl_index;
try addFailedDeclaration(
wip_members,
gz,
.@"comptime",
member_node,
false,
);
},
};
},
.@"usingnamespace" => {
const prev_decl_index = wip_members.decl_index;
astgen.usingnamespaceDecl(gz, scope, wip_members, member_node) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {},
error.AnalysisFail => {
wip_members.decl_index = prev_decl_index;
try addFailedDeclaration(
wip_members,
gz,
.@"usingnamespace",
member_node,
is_pub: {
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
const main_token = main_tokens[member_node];
break :is_pub main_token > 0 and token_tags[main_token - 1] == .keyword_pub;
},
);
},
};
},
.test_decl => {
const prev_decl_index = wip_members.decl_index;
// We need to have *some* decl here so that the decl count matches what's expected.
// Since it doesn't strictly matter *what* this is, let's save ourselves the trouble
// of duplicating the test name logic, and just assume this is an unnamed test.
astgen.testDecl(gz, scope, wip_members, member_node) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {},
error.AnalysisFail => {
wip_members.decl_index = prev_decl_index;
try addFailedDeclaration(
wip_members,
gz,
.unnamed_test,
member_node,
false,
);
},
};
},
else => unreachable,
@@ -6155,7 +6218,7 @@ fn orelseCatchExpr(
const payload = payload_token orelse break :blk &else_scope.base;
const err_str = tree.tokenSlice(payload);
if (mem.eql(u8, err_str, "_")) {
return astgen.failTok(payload, "discard of error capture; omit it instead", .{});
try astgen.appendErrorTok(payload, "discard of error capture; omit it instead", .{});
}
const err_name = try astgen.identAsString(payload);
@@ -6614,7 +6677,7 @@ fn whileExpr(
const is_inline = while_full.inline_token != null;
if (parent_gz.is_comptime and is_inline) {
return astgen.failTok(while_full.inline_token.?, "redundant inline keyword in comptime scope", .{});
try astgen.appendErrorTok(while_full.inline_token.?, "redundant inline keyword in comptime scope", .{});
}
const loop_tag: Zir.Inst.Tag = if (is_inline) .block_inline else .loop;
const loop_block = try parent_gz.makeBlockInst(loop_tag, node);
@@ -6904,7 +6967,7 @@ fn forExpr(
const is_inline = for_full.inline_token != null;
if (parent_gz.is_comptime and is_inline) {
return astgen.failTok(for_full.inline_token.?, "redundant inline keyword in comptime scope", .{});
try astgen.appendErrorTok(for_full.inline_token.?, "redundant inline keyword in comptime scope", .{});
}
const tree = astgen.tree;
const token_tags = tree.tokens.items(.tag);
@@ -6965,7 +7028,7 @@ fn forExpr(
.none;
if (end_val == .none and is_discard) {
return astgen.failTok(ident_tok, "discard of unbounded counter", .{});
try astgen.appendErrorTok(ident_tok, "discard of unbounded counter", .{});
}
const start_is_zero = nodeIsTriviallyZero(tree, start_node);
@@ -7467,6 +7530,7 @@ fn switchExprErrUnion(
const err_name = blk: {
const err_str = tree.tokenSlice(error_payload);
if (mem.eql(u8, err_str, "_")) {
// This is fatal because we already know we're switching on the captured error.
return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{});
}
const err_name = try astgen.identAsString(error_payload);
@@ -7521,7 +7585,7 @@ fn switchExprErrUnion(
const capture_slice = tree.tokenSlice(capture_token);
if (mem.eql(u8, capture_slice, "_")) {
return astgen.failTok(capture_token, "discard of error capture; omit it instead", .{});
try astgen.appendErrorTok(capture_token, "discard of error capture; omit it instead", .{});
}
const tag_name = try astgen.identAsString(capture_token);
try astgen.detectLocalShadowing(&case_scope.base, tag_name, capture_token, capture_slice, .capture);
@@ -7992,7 +8056,7 @@ fn switchExpr(
break :blk payload_sub_scope;
const tag_slice = tree.tokenSlice(tag_token);
if (mem.eql(u8, tag_slice, "_")) {
return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{});
try astgen.appendErrorTok(tag_token, "discard of tag capture; omit it instead", .{});
} else if (case.inline_token == null) {
return astgen.failTok(tag_token, "tag capture on non-inline prong", .{});
}
@@ -13678,6 +13742,8 @@ fn scanContainer(
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
var any_invalid_declarations = false;
// This type forms a linked list of source tokens declaring the same name.
const NameEntry = struct {
tok: Ast.TokenIndex,
@@ -13737,6 +13803,7 @@ fn scanContainer(
const ident = main_tokens[member_node] + 1;
if (token_tags[ident] != .identifier) {
try astgen.appendErrorNode(member_node, "missing function name", .{});
any_invalid_declarations = true;
continue;
}
break :blk .{ .decl, ident };
@@ -13832,6 +13899,7 @@ fn scanContainer(
token_bytes,
}),
});
any_invalid_declarations = true;
continue;
}
@@ -13849,6 +13917,7 @@ fn scanContainer(
.{},
),
});
any_invalid_declarations = true;
break;
}
s = local_val.parent;
@@ -13865,6 +13934,7 @@ fn scanContainer(
.{},
),
});
any_invalid_declarations = true;
break;
}
s = local_ptr.parent;
@@ -13876,7 +13946,10 @@ fn scanContainer(
};
}
if (!any_duplicates) return decl_count;
if (!any_duplicates) {
if (any_invalid_declarations) return error.AnalysisFail;
return decl_count;
}
for (names.keys(), names.values()) |name, first| {
if (first.next == null) continue;
@@ -13888,6 +13961,7 @@ fn scanContainer(
try notes.append(astgen.arena, try astgen.errNoteNode(namespace.node, "{s} declared here", .{@tagName(container_kind)}));
const name_duped = try astgen.arena.dupe(u8, mem.span(astgen.nullTerminatedString(name)));
try astgen.appendErrorTokNotes(first.tok, "duplicate {s} member name '{s}'", .{ @tagName(container_kind), name_duped }, notes.items);
any_invalid_declarations = true;
}
for (test_names.keys(), test_names.values()) |name, first| {
@@ -13900,6 +13974,7 @@ fn scanContainer(
try notes.append(astgen.arena, try astgen.errNoteNode(namespace.node, "{s} declared here", .{@tagName(container_kind)}));
const name_duped = try astgen.arena.dupe(u8, mem.span(astgen.nullTerminatedString(name)));
try astgen.appendErrorTokNotes(first.tok, "duplicate test name '{s}'", .{name_duped}, notes.items);
any_invalid_declarations = true;
}
for (decltest_names.keys(), decltest_names.values()) |name, first| {
@@ -13912,9 +13987,11 @@ fn scanContainer(
try notes.append(astgen.arena, try astgen.errNoteNode(namespace.node, "{s} declared here", .{@tagName(container_kind)}));
const name_duped = try astgen.arena.dupe(u8, mem.span(astgen.nullTerminatedString(name)));
try astgen.appendErrorTokNotes(first.tok, "duplicate decltest '{s}'", .{name_duped}, notes.items);
any_invalid_declarations = true;
}
return decl_count;
assert(any_invalid_declarations);
return error.AnalysisFail;
}
/// Assumes capacity for body has already been added. Needed capacity taking into
@@ -14070,6 +14147,37 @@ const DeclarationName = union(enum) {
@"usingnamespace",
};
fn addFailedDeclaration(
wip_members: *WipMembers,
gz: *GenZir,
name: DeclarationName,
src_node: Ast.Node.Index,
is_pub: bool,
) !void {
const decl_inst = try gz.makeDeclaration(src_node);
wip_members.nextDecl(decl_inst);
var decl_gz = gz.makeSubBlock(&gz.base); // scope doesn't matter here
_ = try decl_gz.add(.{
.tag = .extended,
.data = .{ .extended = .{
.opcode = .astgen_error,
.small = undefined,
.operand = undefined,
} },
});
try setDeclaration(
decl_inst,
@splat(0), // use a fixed hash to represent an AstGen failure; we don't care about source changes if AstGen still failed!
name,
gz.astgen.source_line,
is_pub,
false, // we don't care about exports since semantic analysis will fail
.empty,
&decl_gz,
null,
);
}
/// Sets all extra data for a `declaration` instruction.
/// Unstacks `value_gz`, `align_gz`, `linksection_gz`, and `addrspace_gz`.
fn setDeclaration(

View File

@@ -120,7 +120,21 @@ pub fn bodySlice(zir: Zir, start: usize, len: usize) []Inst.Index {
}
pub fn hasCompileErrors(code: Zir) bool {
return code.extra[@intFromEnum(ExtraIndex.compile_errors)] != 0;
if (code.extra[@intFromEnum(ExtraIndex.compile_errors)] != 0) {
return true;
} else {
assert(code.instructions.len != 0); // i.e. lowering did not fail
return false;
}
}
pub fn loweringFailed(code: Zir) bool {
if (code.instructions.len == 0) {
assert(code.hasCompileErrors());
return true;
} else {
return false;
}
}
pub fn deinit(code: *Zir, gpa: Allocator) void {
@@ -2079,7 +2093,14 @@ pub const Inst = struct {
/// `small` is an `Inst.InplaceOp`.
inplace_arith_result_ty,
/// Marks a statement that can be stepped to but produces no code.
/// `operand` and `small` are ignored.
dbg_empty_stmt,
/// At this point, AstGen encountered a fatal error which terminated ZIR lowering for this body.
/// A file-level error has been reported. Sema should terminate semantic analysis.
/// `operand` and `small` are ignored.
/// This instruction is always `noreturn`, however, it is not considered as such by ZIR-level queries. This allows AstGen to assume that
/// any code may have gone here, avoiding false-positive "unreachable code" errors.
astgen_error,
pub const InstData = struct {
opcode: Extended,
@@ -3584,145 +3605,155 @@ pub const DeclIterator = struct {
};
pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator {
const tags = zir.instructions.items(.tag);
const datas = zir.instructions.items(.data);
switch (tags[@intFromEnum(decl_inst)]) {
// Functions are allowed and yield no iterations.
// This is because they are returned by `findDecls`.
.func, .func_inferred, .func_fancy => return .{
.extra_index = undefined,
.decls_remaining = 0,
.zir = zir,
},
const inst = zir.instructions.get(@intFromEnum(decl_inst));
assert(inst.tag == .extended);
const extended = inst.data.extended;
switch (extended.opcode) {
.struct_decl => {
const small: Inst.StructDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.StructDecl).@"struct".fields.len);
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += @intFromBool(small.has_fields_len);
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;
.extended => {
const extended = datas[@intFromEnum(decl_inst)].extended;
switch (extended.opcode) {
// Reifications are allowed and yield no iterations.
// This is because they are returned by `findDecls`.
.reify => return .{
.extra_index = undefined,
.decls_remaining = 0,
.zir = zir,
},
.struct_decl => {
const small: Inst.StructDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.StructDecl).@"struct".fields.len);
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += @intFromBool(small.has_fields_len);
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;
extra_index += captures_len;
extra_index += captures_len;
if (small.has_backing_int) {
const backing_int_body_len = zir.extra[extra_index];
extra_index += 1; // backing_int_body_len
if (backing_int_body_len == 0) {
extra_index += 1; // backing_int_ref
} else {
extra_index += backing_int_body_len; // backing_int_body_inst
}
}
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
.enum_decl => {
const small: Inst.EnumDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.EnumDecl).@"struct".fields.len);
extra_index += @intFromBool(small.has_tag_type);
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += @intFromBool(small.has_body_len);
extra_index += @intFromBool(small.has_fields_len);
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;
extra_index += captures_len;
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
.union_decl => {
const small: Inst.UnionDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.UnionDecl).@"struct".fields.len);
extra_index += @intFromBool(small.has_tag_type);
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += @intFromBool(small.has_body_len);
extra_index += @intFromBool(small.has_fields_len);
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;
extra_index += captures_len;
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
.opaque_decl => {
const small: Inst.OpaqueDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.OpaqueDecl).@"struct".fields.len);
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;
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += captures_len;
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
else => unreachable,
if (small.has_backing_int) {
const backing_int_body_len = zir.extra[extra_index];
extra_index += 1; // backing_int_body_len
if (backing_int_body_len == 0) {
extra_index += 1; // backing_int_ref
} else {
extra_index += backing_int_body_len; // backing_int_body_inst
}
}
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
.enum_decl => {
const small: Inst.EnumDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.EnumDecl).@"struct".fields.len);
extra_index += @intFromBool(small.has_tag_type);
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += @intFromBool(small.has_body_len);
extra_index += @intFromBool(small.has_fields_len);
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;
extra_index += captures_len;
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
.union_decl => {
const small: Inst.UnionDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.UnionDecl).@"struct".fields.len);
extra_index += @intFromBool(small.has_tag_type);
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += @intFromBool(small.has_body_len);
extra_index += @intFromBool(small.has_fields_len);
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;
extra_index += captures_len;
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
.opaque_decl => {
const small: Inst.OpaqueDecl.Small = @bitCast(extended.small);
var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.OpaqueDecl).@"struct".fields.len);
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;
const captures_len = if (small.has_captures_len) captures_len: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :captures_len captures_len;
} else 0;
extra_index += captures_len;
return .{
.extra_index = extra_index,
.decls_remaining = decls_len,
.zir = zir,
};
},
else => unreachable,
}
}
/// Find all type declarations, recursively, within a `declaration` instruction. Does not recurse through
/// said type declarations' declarations; to find all declarations, call this function on the declarations
/// of the discovered types recursively.
/// The iterator would have to allocate memory anyway to iterate, so an `ArrayList` is populated as the result.
pub fn findDecls(zir: Zir, gpa: Allocator, list: *std.ArrayListUnmanaged(Inst.Index), decl_inst: Zir.Inst.Index) !void {
list.clearRetainingCapacity();
/// `DeclContents` contains all "interesting" instructions found within a declaration by `findTrackable`.
/// These instructions are partitioned into a few different sets, since this makes ZIR instruction mapping
/// more effective.
pub const DeclContents = struct {
/// This is a simple optional because ZIR guarantees that a `func`/`func_inferred`/`func_fancy` instruction
/// can only occur once per `declaration`.
func_decl: ?Inst.Index,
explicit_types: std.ArrayListUnmanaged(Inst.Index),
other: std.ArrayListUnmanaged(Inst.Index),
pub const init: DeclContents = .{
.func_decl = null,
.explicit_types = .empty,
.other = .empty,
};
pub fn clear(contents: *DeclContents) void {
contents.func_decl = null;
contents.explicit_types.clearRetainingCapacity();
contents.other.clearRetainingCapacity();
}
pub fn deinit(contents: *DeclContents, gpa: Allocator) void {
contents.explicit_types.deinit(gpa);
contents.other.deinit(gpa);
}
};
/// Find all tracked ZIR instructions, recursively, within a `declaration` instruction. Does not recurse through
/// nested declarations; to find all declarations, call this function recursively on the type declarations discovered
/// in `contents.explicit_types`.
///
/// This populates an `ArrayListUnmanaged` because an iterator would need to allocate memory anyway.
pub fn findTrackable(zir: Zir, gpa: Allocator, contents: *DeclContents, decl_inst: Zir.Inst.Index) !void {
contents.clear();
const declaration, const extra_end = zir.getDeclaration(decl_inst);
const bodies = declaration.getBodies(extra_end, zir);
@@ -3731,27 +3762,27 @@ pub fn findDecls(zir: Zir, gpa: Allocator, list: *std.ArrayListUnmanaged(Inst.In
var found_defers: std.AutoHashMapUnmanaged(u32, void) = .empty;
defer found_defers.deinit(gpa);
try zir.findDeclsBody(gpa, list, &found_defers, bodies.value_body);
if (bodies.align_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b);
if (bodies.linksection_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b);
if (bodies.addrspace_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b);
try zir.findTrackableBody(gpa, contents, &found_defers, bodies.value_body);
if (bodies.align_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
if (bodies.linksection_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
if (bodies.addrspace_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
}
/// Like `findDecls`, but only considers the `main_struct_inst` instruction. This may return more than
/// Like `findTrackable`, but only considers the `main_struct_inst` instruction. This may return more than
/// just that instruction because it will also traverse fields.
pub fn findDeclsRoot(zir: Zir, gpa: Allocator, list: *std.ArrayListUnmanaged(Inst.Index)) !void {
list.clearRetainingCapacity();
pub fn findTrackableRoot(zir: Zir, gpa: Allocator, contents: *DeclContents) !void {
contents.clear();
var found_defers: std.AutoHashMapUnmanaged(u32, void) = .empty;
defer found_defers.deinit(gpa);
try zir.findDeclsInner(gpa, list, &found_defers, .main_struct_inst);
try zir.findTrackableInner(gpa, contents, &found_defers, .main_struct_inst);
}
fn findDeclsInner(
fn findTrackableInner(
zir: Zir,
gpa: Allocator,
list: *std.ArrayListUnmanaged(Inst.Index),
contents: *DeclContents,
defers: *std.AutoHashMapUnmanaged(u32, void),
inst: Inst.Index,
) Allocator.Error!void {
@@ -3995,7 +4026,7 @@ fn findDeclsInner(
.struct_init,
.struct_init_ref,
.struct_init_anon,
=> return list.append(gpa, inst),
=> return contents.other.append(gpa, inst),
.extended => {
const extended = datas[@intFromEnum(inst)].extended;
@@ -4055,21 +4086,22 @@ fn findDeclsInner(
.inplace_arith_result_ty,
.tuple_decl,
.dbg_empty_stmt,
.astgen_error,
=> return,
// `@TypeOf` has a body.
.typeof_peer => {
const extra = zir.extraData(Zir.Inst.TypeOfPeer, extended.operand);
const body = zir.bodySlice(extra.data.body_index, extra.data.body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
},
// Reifications and opaque declarations need tracking, but have no body.
.reify, .opaque_decl => return list.append(gpa, inst),
.reify, .opaque_decl => return contents.other.append(gpa, inst),
// Struct declarations need tracking and have bodies.
.struct_decl => {
try list.append(gpa, inst);
try contents.explicit_types.append(gpa, inst);
const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
const extra = zir.extraData(Zir.Inst.StructDecl, extended.operand);
@@ -4098,7 +4130,7 @@ fn findDeclsInner(
} else {
const body = zir.bodySlice(extra_index, backing_int_body_len);
extra_index += backing_int_body_len;
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
}
}
extra_index += decls_len;
@@ -4154,12 +4186,12 @@ fn findDeclsInner(
// Now, `fields_extra_index` points to `bodies`. Let's treat this as one big body.
const merged_bodies = zir.bodySlice(fields_extra_index, total_bodies_len);
try zir.findDeclsBody(gpa, list, defers, merged_bodies);
try zir.findTrackableBody(gpa, contents, defers, merged_bodies);
},
// Union declarations need tracking and have a body.
.union_decl => {
try list.append(gpa, inst);
try contents.explicit_types.append(gpa, inst);
const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small);
const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand);
@@ -4184,12 +4216,12 @@ fn findDeclsInner(
extra_index += captures_len;
extra_index += decls_len;
const body = zir.bodySlice(extra_index, body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
},
// Enum declarations need tracking and have a body.
.enum_decl => {
try list.append(gpa, inst);
try contents.explicit_types.append(gpa, inst);
const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small);
const extra = zir.extraData(Zir.Inst.EnumDecl, extended.operand);
@@ -4214,7 +4246,7 @@ fn findDeclsInner(
extra_index += captures_len;
extra_index += decls_len;
const body = zir.bodySlice(extra_index, body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
},
}
},
@@ -4223,7 +4255,8 @@ fn findDeclsInner(
.func,
.func_inferred,
=> {
try list.append(gpa, inst);
assert(contents.func_decl == null);
contents.func_decl = inst;
const inst_data = datas[@intFromEnum(inst)].pl_node;
const extra = zir.extraData(Inst.Func, inst_data.payload_index);
@@ -4234,14 +4267,15 @@ fn findDeclsInner(
else => {
const body = zir.bodySlice(extra_index, extra.data.ret_body_len);
extra_index += body.len;
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
},
}
const body = zir.bodySlice(extra_index, extra.data.body_len);
return zir.findDeclsBody(gpa, list, defers, body);
return zir.findTrackableBody(gpa, contents, defers, body);
},
.func_fancy => {
try list.append(gpa, inst);
assert(contents.func_decl == null);
contents.func_decl = inst;
const inst_data = datas[@intFromEnum(inst)].pl_node;
const extra = zir.extraData(Inst.FuncFancy, inst_data.payload_index);
@@ -4252,7 +4286,7 @@ fn findDeclsInner(
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.bodySlice(extra_index, body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
extra_index += body.len;
} else if (extra.data.bits.has_align_ref) {
extra_index += 1;
@@ -4262,7 +4296,7 @@ fn findDeclsInner(
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.bodySlice(extra_index, body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
extra_index += body.len;
} else if (extra.data.bits.has_addrspace_ref) {
extra_index += 1;
@@ -4272,7 +4306,7 @@ fn findDeclsInner(
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.bodySlice(extra_index, body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
extra_index += body.len;
} else if (extra.data.bits.has_section_ref) {
extra_index += 1;
@@ -4282,7 +4316,7 @@ fn findDeclsInner(
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.bodySlice(extra_index, body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
extra_index += body.len;
} else if (extra.data.bits.has_cc_ref) {
extra_index += 1;
@@ -4292,7 +4326,7 @@ fn findDeclsInner(
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.bodySlice(extra_index, body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
extra_index += body.len;
} else if (extra.data.bits.has_ret_ty_ref) {
extra_index += 1;
@@ -4301,7 +4335,7 @@ fn findDeclsInner(
extra_index += @intFromBool(extra.data.bits.has_any_noalias);
const body = zir.bodySlice(extra_index, extra.data.body_len);
return zir.findDeclsBody(gpa, list, defers, body);
return zir.findTrackableBody(gpa, contents, defers, body);
},
// Block instructions, recurse over the bodies.
@@ -4316,24 +4350,24 @@ fn findDeclsInner(
const inst_data = datas[@intFromEnum(inst)].pl_node;
const extra = zir.extraData(Inst.Block, inst_data.payload_index);
const body = zir.bodySlice(extra.end, extra.data.body_len);
return zir.findDeclsBody(gpa, list, defers, body);
return zir.findTrackableBody(gpa, contents, defers, body);
},
.condbr, .condbr_inline => {
const inst_data = datas[@intFromEnum(inst)].pl_node;
const extra = zir.extraData(Inst.CondBr, inst_data.payload_index);
const then_body = zir.bodySlice(extra.end, extra.data.then_body_len);
const else_body = zir.bodySlice(extra.end + then_body.len, extra.data.else_body_len);
try zir.findDeclsBody(gpa, list, defers, then_body);
try zir.findDeclsBody(gpa, list, defers, else_body);
try zir.findTrackableBody(gpa, contents, defers, then_body);
try zir.findTrackableBody(gpa, contents, defers, else_body);
},
.@"try", .try_ptr => {
const inst_data = datas[@intFromEnum(inst)].pl_node;
const extra = zir.extraData(Inst.Try, inst_data.payload_index);
const body = zir.bodySlice(extra.end, extra.data.body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
},
.switch_block, .switch_block_ref => return zir.findDeclsSwitch(gpa, list, defers, inst, .normal),
.switch_block_err_union => return zir.findDeclsSwitch(gpa, list, defers, inst, .err_union),
.switch_block, .switch_block_ref => return zir.findTrackableSwitch(gpa, contents, defers, inst, .normal),
.switch_block_err_union => return zir.findTrackableSwitch(gpa, contents, defers, inst, .err_union),
.suspend_block => @panic("TODO iterate suspend block"),
@@ -4341,7 +4375,7 @@ fn findDeclsInner(
const inst_data = datas[@intFromEnum(inst)].pl_tok;
const extra = zir.extraData(Inst.Param, inst_data.payload_index);
const body = zir.bodySlice(extra.end, extra.data.body_len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
},
inline .call, .field_call => |tag| {
@@ -4357,7 +4391,7 @@ fn findDeclsInner(
const first_arg_start_off = args_len;
const final_arg_end_off = zir.extra[extra.end + args_len - 1];
const args_body = zir.bodySlice(extra.end + first_arg_start_off, final_arg_end_off - first_arg_start_off);
try zir.findDeclsBody(gpa, list, defers, args_body);
try zir.findTrackableBody(gpa, contents, defers, args_body);
}
},
.@"defer" => {
@@ -4365,7 +4399,7 @@ fn findDeclsInner(
const gop = try defers.getOrPut(gpa, inst_data.index);
if (!gop.found_existing) {
const body = zir.bodySlice(inst_data.index, inst_data.len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
}
},
.defer_err_code => {
@@ -4374,16 +4408,16 @@ fn findDeclsInner(
const gop = try defers.getOrPut(gpa, extra.index);
if (!gop.found_existing) {
const body = zir.bodySlice(extra.index, extra.len);
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
}
},
}
}
fn findDeclsSwitch(
fn findTrackableSwitch(
zir: Zir,
gpa: Allocator,
list: *std.ArrayListUnmanaged(Inst.Index),
contents: *DeclContents,
defers: *std.AutoHashMapUnmanaged(u32, void),
inst: Inst.Index,
/// Distinguishes between `switch_block[_ref]` and `switch_block_err_union`.
@@ -4419,7 +4453,7 @@ fn findDeclsSwitch(
const body = zir.bodySlice(extra_index, prong_info.body_len);
extra_index += body.len;
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
break :has_special extra.data.bits.has_else;
},
@@ -4431,7 +4465,7 @@ fn findDeclsSwitch(
const body = zir.bodySlice(extra_index, prong_info.body_len);
extra_index += body.len;
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
}
{
@@ -4443,7 +4477,7 @@ fn findDeclsSwitch(
const body = zir.bodySlice(extra_index, prong_info.body_len);
extra_index += body.len;
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
}
}
{
@@ -4460,20 +4494,20 @@ fn findDeclsSwitch(
const body = zir.bodySlice(extra_index, prong_info.body_len);
extra_index += body.len;
try zir.findDeclsBody(gpa, list, defers, body);
try zir.findTrackableBody(gpa, contents, defers, body);
}
}
}
fn findDeclsBody(
fn findTrackableBody(
zir: Zir,
gpa: Allocator,
list: *std.ArrayListUnmanaged(Inst.Index),
contents: *DeclContents,
defers: *std.AutoHashMapUnmanaged(u32, void),
body: []const Inst.Index,
) Allocator.Error!void {
for (body) |member| {
try zir.findDeclsInner(gpa, list, defers, member);
try zir.findTrackableInner(gpa, contents, defers, member);
}
}

View File

@@ -3223,17 +3223,29 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
}
}
if (comp.zcu) |zcu| {
if (comp.incremental and bundle.root_list.items.len == 0) {
const should_have_error = for (zcu.transitive_failed_analysis.keys()) |failed_unit| {
const refs = try zcu.resolveReferences();
if (refs.contains(failed_unit)) break true;
} else false;
if (should_have_error) {
@panic("referenced transitive analysis errors, but none actually emitted");
// TODO: eventually, this should be behind `std.debug.runtime_safety`. But right now, this is a
// very common way for incremental compilation bugs to manifest, so let's always check it.
if (comp.zcu) |zcu| if (comp.incremental and bundle.root_list.items.len == 0) {
for (zcu.transitive_failed_analysis.keys()) |failed_unit| {
const refs = try zcu.resolveReferences();
var ref = refs.get(failed_unit) orelse continue;
// This AU is referenced and has a transitive compile error, meaning it referenced something with a compile error.
// However, we haven't reported any such error.
// This is a compiler bug.
const stderr = std.io.getStdErr().writer();
try stderr.writeAll("referenced transitive analysis errors, but none actually emitted\n");
try stderr.print("{} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)});
while (ref) |r| {
try stderr.print("referenced by: {}{s}\n", .{
zcu.fmtAnalUnit(r.referencer),
if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "",
});
ref = refs.get(r.referencer).?;
}
@panic("referenced transitive analysis errors, but none actually emitted");
}
}
};
const compile_log_text = if (comp.zcu) |m| m.compile_log_text.items else "";
return bundle.toOwnedBundle(compile_log_text);

View File

@@ -8616,6 +8616,16 @@ pub fn getFuncDecl(
defer gop.deinit();
if (gop == .existing) {
extra.mutate.len = prev_extra_len;
const zir_body_inst_ptr = ip.funcDeclInfo(gop.existing).zirBodyInstPtr(ip);
if (zir_body_inst_ptr.* != key.zir_body_inst) {
// Since this function's `owner_nav` matches `key`, this *is* the function we're talking
// about. The only way it could have a different ZIR `func` instruction is if the old
// instruction has been lost and replaced with a new `TrackedInst.Index`.
assert(zir_body_inst_ptr.resolve(ip) == null);
zir_body_inst_ptr.* = key.zir_body_inst;
}
return gop.existing;
}
@@ -8762,6 +8772,16 @@ pub fn getFuncDeclIes(
// An existing function type was found; undo the additions to our two arrays.
items.mutate.len -= 4;
extra.mutate.len = prev_extra_len;
const zir_body_inst_ptr = ip.funcDeclInfo(func_gop.existing).zirBodyInstPtr(ip);
if (zir_body_inst_ptr.* != key.zir_body_inst) {
// Since this function's `owner_nav` matches `key`, this *is* the function we're talking
// about. The only way it could have a different ZIR `func` instruction is if the old
// instruction has been lost and replaced with a new `TrackedInst.Index`.
assert(zir_body_inst_ptr.resolve(ip) == null);
zir_body_inst_ptr.* = key.zir_body_inst;
}
return func_gop.existing;
}
func_gop.putTentative(func_index);

View File

@@ -1361,6 +1361,7 @@ fn analyzeBodyInner(
i += 1;
continue;
},
.astgen_error => return error.AnalysisFail,
};
},

View File

@@ -2593,26 +2593,44 @@ pub fn mapOldZirToNew(
defer match_stack.deinit(gpa);
// Used as temporary buffers for namespace declaration instructions
var old_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .empty;
defer old_decls.deinit(gpa);
var new_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .empty;
defer new_decls.deinit(gpa);
var old_contents: Zir.DeclContents = .init;
defer old_contents.deinit(gpa);
var new_contents: Zir.DeclContents = .init;
defer new_contents.deinit(gpa);
// Map the main struct inst (and anything in its fields)
{
try old_zir.findDeclsRoot(gpa, &old_decls);
try new_zir.findDeclsRoot(gpa, &new_decls);
try old_zir.findTrackableRoot(gpa, &old_contents);
try new_zir.findTrackableRoot(gpa, &new_contents);
assert(old_decls.items[0] == .main_struct_inst);
assert(new_decls.items[0] == .main_struct_inst);
assert(old_contents.explicit_types.items[0] == .main_struct_inst);
assert(new_contents.explicit_types.items[0] == .main_struct_inst);
// We don't have any smart way of matching up these type declarations, so we always
// correlate them based on source order.
const n = @min(old_decls.items.len, new_decls.items.len);
try match_stack.ensureUnusedCapacity(gpa, n);
for (old_decls.items[0..n], new_decls.items[0..n]) |old_inst, new_inst| {
assert(old_contents.func_decl == null);
assert(new_contents.func_decl == null);
// We don't have any smart way of matching up these instructions, so we correlate them based on source order
// in their respective arrays.
const num_explicit_types = @min(old_contents.explicit_types.items.len, new_contents.explicit_types.items.len);
try match_stack.ensureUnusedCapacity(gpa, @intCast(num_explicit_types));
for (
old_contents.explicit_types.items[0..num_explicit_types],
new_contents.explicit_types.items[0..num_explicit_types],
) |old_inst, new_inst| {
// Here we use `match_stack`, so that we will recursively consider declarations on these types.
match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
}
const num_other = @min(old_contents.other.items.len, new_contents.other.items.len);
try inst_map.ensureUnusedCapacity(gpa, @intCast(num_other));
for (
old_contents.other.items[0..num_other],
new_contents.other.items[0..num_other],
) |old_inst, new_inst| {
// These instructions don't have declarations, so we just modify `inst_map` directly.
inst_map.putAssumeCapacity(old_inst, new_inst);
}
}
while (match_stack.popOrNull()) |match_item| {
@@ -2700,17 +2718,39 @@ pub fn mapOldZirToNew(
// Match the `declaration` instruction
try inst_map.put(gpa, old_decl_inst, new_decl_inst);
// Find container type declarations within this declaration
try old_zir.findDecls(gpa, &old_decls, old_decl_inst);
try new_zir.findDecls(gpa, &new_decls, new_decl_inst);
// Find trackable instructions within this declaration
try old_zir.findTrackable(gpa, &old_contents, old_decl_inst);
try new_zir.findTrackable(gpa, &new_contents, new_decl_inst);
// We don't have any smart way of matching up these type declarations, so we always
// correlate them based on source order.
const n = @min(old_decls.items.len, new_decls.items.len);
try match_stack.ensureUnusedCapacity(gpa, n);
for (old_decls.items[0..n], new_decls.items[0..n]) |old_inst, new_inst| {
// We don't have any smart way of matching up these instructions, so we correlate them based on source order
// in their respective arrays.
const num_explicit_types = @min(old_contents.explicit_types.items.len, new_contents.explicit_types.items.len);
try match_stack.ensureUnusedCapacity(gpa, @intCast(num_explicit_types));
for (
old_contents.explicit_types.items[0..num_explicit_types],
new_contents.explicit_types.items[0..num_explicit_types],
) |old_inst, new_inst| {
// Here we use `match_stack`, so that we will recursively consider declarations on these types.
match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
}
const num_other = @min(old_contents.other.items.len, new_contents.other.items.len);
try inst_map.ensureUnusedCapacity(gpa, @intCast(num_other));
for (
old_contents.other.items[0..num_other],
new_contents.other.items[0..num_other],
) |old_inst, new_inst| {
// These instructions don't have declarations, so we just modify `inst_map` directly.
inst_map.putAssumeCapacity(old_inst, new_inst);
}
if (old_contents.func_decl) |old_func_inst| {
if (new_contents.func_decl) |new_func_inst| {
// There are no declarations on a function either, so again, we just directly add it to `inst_map`.
try inst_map.put(gpa, old_func_inst, new_func_inst);
}
}
}
}
}

View File

@@ -185,11 +185,11 @@ pub fn astGenFile(
log.debug("AstGen cached success: {s}", .{file.sub_file_path});
if (file.zir.hasCompileErrors()) {
{
comp.mutex.lock();
defer comp.mutex.unlock();
try zcu.failed_files.putNoClobber(gpa, file, null);
}
comp.mutex.lock();
defer comp.mutex.unlock();
try zcu.failed_files.putNoClobber(gpa, file, null);
}
if (file.zir.loweringFailed()) {
file.status = .astgen_failure;
return error.AnalysisFail;
}
@@ -226,7 +226,7 @@ pub fn astGenFile(
// single-threaded context, so we need to keep both versions around
// until that point in the pipeline. Previous ZIR data is freed after
// that.
if (file.zir_loaded and !file.zir.hasCompileErrors()) {
if (file.zir_loaded and !file.zir.loweringFailed()) {
assert(file.prev_zir == null);
const prev_zir_ptr = try gpa.create(Zir);
file.prev_zir = prev_zir_ptr;
@@ -321,11 +321,11 @@ pub fn astGenFile(
};
if (file.zir.hasCompileErrors()) {
{
comp.mutex.lock();
defer comp.mutex.unlock();
try zcu.failed_files.putNoClobber(gpa, file, null);
}
comp.mutex.lock();
defer comp.mutex.unlock();
try zcu.failed_files.putNoClobber(gpa, file, null);
}
if (file.zir.loweringFailed()) {
file.status = .astgen_failure;
return error.AnalysisFail;
}
@@ -363,7 +363,7 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
.file = file,
.inst_map = .{},
};
if (!new_zir.hasCompileErrors()) {
if (!new_zir.loweringFailed()) {
try Zcu.mapOldZirToNew(gpa, old_zir.*, file.zir, &gop.value_ptr.inst_map);
}
}
@@ -379,20 +379,19 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
const file = updated_file.file;
if (file.zir.hasCompileErrors()) {
// If we mark this as outdated now, users of this inst will just get a transitive analysis failure.
// Ultimately, they would end up throwing out potentially useful analysis results.
// So, do nothing. We already have the file failure -- that's sufficient for now!
continue;
}
const old_inst = tracked_inst.inst.unwrap() orelse continue; // we can't continue tracking lost insts
const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{
.tid = @enumFromInt(tid),
.index = @intCast(tracked_inst_unwrapped_index),
}).wrap(ip);
const new_inst = updated_file.inst_map.get(old_inst) orelse {
// Tracking failed for this instruction. Invalidate associated `src_hash` deps.
log.debug("tracking failed for %{d}", .{old_inst});
// Tracking failed for this instruction.
// This may be due to changes in the ZIR, or AstGen might have failed due to a very broken file.
// Either way, invalidate associated `src_hash` deps.
log.debug("tracking failed for %{d}{s}", .{
old_inst,
if (file.zir.loweringFailed()) " due to AstGen failure" else "",
});
tracked_inst.inst = .lost;
try zcu.markDependeeOutdated(.not_marked_po, .{ .src_hash = tracked_inst_index });
continue;
@@ -494,8 +493,8 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
for (updated_files.keys(), updated_files.values()) |file_index, updated_file| {
const file = updated_file.file;
if (file.zir.hasCompileErrors()) {
// Keep `prev_zir` around: it's the last non-error ZIR.
if (file.zir.loweringFailed()) {
// Keep `prev_zir` around: it's the last usable ZIR.
// Don't update the namespace, as we have no new data to update *to*.
} else {
const prev_zir = file.prev_zir.?;
@@ -539,7 +538,7 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
const cau = ip.getCau(cau_index);
log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)});
log.debug("ensureCauAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
assert(!zcu.analysis_in_progress.contains(anal_unit));
@@ -577,13 +576,19 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu
}
const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result|
.{ result, false }
// This `Cau` has gone from failed to success, so even if the value of the owner `Nav` didn't actually
// change, we need to invalidate the dependencies anyway.
.{ .{
.invalidate_decl_val = result.invalidate_decl_val or prev_failed,
.invalidate_decl_ref = result.invalidate_decl_ref or prev_failed,
}, false }
else |err| switch (err) {
error.AnalysisFail => res: {
if (!zcu.failed_analysis.contains(anal_unit)) {
// If this `Cau` caused the error, it would have an entry in `failed_analysis`.
// Since it does not, this must be a transitive failure.
try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
}
// We consider this `Cau` to be outdated if:
// * Previous analysis succeeded; in this case, we need to re-analyze dependants to ensure
@@ -708,12 +713,12 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
// We only care about the uncoerced function.
const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
const anal_unit = AnalUnit.wrap(.{ .func = func_index });
log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
const func = zcu.funcInfo(maybe_coerced_func_index);
log.debug("ensureFuncBodyAnalyzed {d}", .{@intFromEnum(func_index)});
const anal_unit = AnalUnit.wrap(.{ .func = func_index });
const func_outdated = zcu.outdated.swapRemove(anal_unit) or
zcu.potentially_outdated.swapRemove(anal_unit);
@@ -741,6 +746,7 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
// If this function caused the error, it would have an entry in `failed_analysis`.
// Since it does not, this must be a transitive failure.
try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
}
// We consider the IES to be outdated if the function previously succeeded analysis; in this case,
// we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting
@@ -752,10 +758,8 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
if (func_outdated) {
if (ies_outdated) {
log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)});
try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index });
} else {
log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)});
try zcu.markPoDependeeUpToDate(.{ .interned = func_index });
}
}
@@ -780,6 +784,7 @@ fn ensureFuncBodyAnalyzedInner(
// results in the worst case.
if (func.generic_owner == .none) {
// Among another things, this ensures that the function's `zir_body_inst` is correct.
try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?);
if (ip.getNav(func.owner_nav).status.resolved.val != func_index) {
// This function is no longer referenced! There's no point in re-analyzing it.
@@ -788,6 +793,7 @@ fn ensureFuncBodyAnalyzedInner(
}
} else {
const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;
// Among another things, this ensures that the function's `zir_body_inst` is correct.
try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?);
if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) {
// The generic owner is no longer referenced, so this function is also unreferenced.
@@ -825,8 +831,8 @@ fn ensureFuncBodyAnalyzedInner(
}
}
log.debug("analyze and generate fn body '{d}'; reason='{s}'", .{
@intFromEnum(func_index),
log.debug("analyze and generate fn body {}; reason='{s}'", .{
zcu.fmtAnalUnit(anal_unit),
if (func_outdated) "outdated" else "never analyzed",
});
@@ -1165,7 +1171,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
.none, .type => false,
};
log.debug("semaCau '{d}'", .{@intFromEnum(cau_index)});
log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)});
try zcu.analysis_in_progress.put(gpa, anal_unit, {});
errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
@@ -2308,16 +2314,14 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err
return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, name));
}
/// Removes any entry from `Zcu.failed_files` associated with `file`. Acquires `Compilation.mutex` as needed.
/// `file.zir` must be unchanged from the last update, as it is used to determine if there is such an entry.
fn lockAndClearFileCompileError(pt: Zcu.PerThread, file: *Zcu.File) void {
switch (file.status) {
.success_zir, .retryable_failure => {},
.never_loaded, .parse_failure, .astgen_failure => {
pt.zcu.comp.mutex.lock();
defer pt.zcu.comp.mutex.unlock();
if (pt.zcu.failed_files.fetchSwapRemove(file)) |kv| {
if (kv.value) |msg| msg.destroy(pt.zcu.gpa); // Delete previous error message.
}
},
if (!file.zir_loaded or !file.zir.hasCompileErrors()) return;
pt.zcu.comp.mutex.lock();
defer pt.zcu.comp.mutex.unlock();
if (pt.zcu.failed_files.fetchSwapRemove(file)) |kv| {
if (kv.value) |msg| msg.destroy(pt.zcu.gpa); // Delete previous error message.
}
}
@@ -2507,6 +2511,19 @@ pub fn populateTestFunctions(
for (test_fn_vals, zcu.test_functions.keys()) |*test_fn_val, test_nav_index| {
const test_nav = ip.getNav(test_nav_index);
{
// The test declaration might have failed; if that's the case, just return, as we'll
// be emitting a compile error anyway.
const cau = test_nav.analysis_owner.unwrap().?;
const anal_unit: AnalUnit = .wrap(.{ .cau = cau });
if (zcu.failed_analysis.contains(anal_unit) or
zcu.transitive_failed_analysis.contains(anal_unit))
{
return;
}
}
const test_nav_name = test_nav.fqn;
const test_nav_name_len = test_nav_name.length(ip);
const test_name_anon_decl: InternPool.Key.Ptr.BaseAddr.Uav = n: {

View File

@@ -6097,11 +6097,18 @@ fn cmdAstCheck(
var error_bundle = try wip_errors.toOwnedBundle("");
defer error_bundle.deinit(gpa);
error_bundle.renderToStdErr(color.renderOptions());
process.exit(1);
if (file.zir.loweringFailed()) {
process.exit(1);
}
}
if (!want_output_text) {
return cleanExit();
if (file.zir.hasCompileErrors()) {
process.exit(1);
} else {
return cleanExit();
}
}
if (!build_options.enable_debug_extensions) {
fatal("-t option only available in builds of zig with debug extensions", .{});
@@ -6145,7 +6152,13 @@ fn cmdAstCheck(
// zig fmt: on
}
return @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut());
try @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut());
if (file.zir.hasCompileErrors()) {
process.exit(1);
} else {
return cleanExit();
}
}
fn cmdDetectCpu(
@@ -6458,7 +6471,7 @@ fn cmdChangelist(
file.zir_loaded = true;
defer file.zir.deinit(gpa);
if (file.zir.hasCompileErrors()) {
if (file.zir.loweringFailed()) {
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
try wip_errors.init(gpa);
defer wip_errors.deinit();
@@ -6493,7 +6506,7 @@ fn cmdChangelist(
file.zir = try AstGen.generate(gpa, new_tree);
file.zir_loaded = true;
if (file.zir.hasCompileErrors()) {
if (file.zir.loweringFailed()) {
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
try wip_errors.init(gpa);
defer wip_errors.deinit();

View File

@@ -623,6 +623,7 @@ const Writer = struct {
.inplace_arith_result_ty => try self.writeInplaceArithResultTy(stream, extended),
.dbg_empty_stmt => try stream.writeAll("))"),
.astgen_error => try stream.writeAll("))"),
}
}

View File

@@ -1,11 +1,8 @@
const A = B;
test "Crash" {
pub const A = B;
export fn foo() void {
_ = @typeInfo(@This()).@"struct".decls[0];
}
// error
// backend=stage2
// target=native
// is_test=true
//
// :1:11: error: use of undeclared identifier 'B'
// :1:15: error: use of undeclared identifier 'B'

View File

@@ -0,0 +1,17 @@
const a = bogus; // astgen error (undeclared identifier)
const b: u32 = "hi"; // sema error (type mismatch)
comptime {
_ = b;
@compileError("not hit because 'b' failed");
}
comptime {
@compileError("this should be hit");
}
// error
//
// :1:11: error: use of undeclared identifier 'bogus'
// :2:16: error: expected type 'u32', found '*const [2:0]u8'
// :10:5: error: this should be hit

View File

@@ -1,13 +1,8 @@
fn func() bogus {}
fn func() bogus {}
export fn entry() usize {
return @sizeOf(@TypeOf(func));
}
fn func() void {}
fn func() void {}
// error
//
// :1:4: error: duplicate struct member name 'func'
// :2:4: note: duplicate name here
// :1:1: note: struct declared here
// :1:11: error: use of undeclared identifier 'bogus'
// :2:11: error: use of undeclared identifier 'bogus'

View File

@@ -19,3 +19,5 @@ export fn entry() void {
//
// :4:5: error: unreachable code
// :4:25: note: control flow is diverted here
// :4:25: error: aoeu
// :1:36: note: called from here

View File

@@ -1,22 +1,27 @@
comptime {
const a = "foo";
if (a == "foo") unreachable;
if (a != "foo") unreachable;
}
comptime {
const a = "foo";
if (a == ("foo")) unreachable; // intentionally allow
if (a == "foo") {} else unreachable;
}
comptime {
const a = "foo";
if (a != ("foo")) {} // intentionally allow
if (a == ("foo")) {} // intentionally allow
}
comptime {
const a = "foo";
switch (a) {
"foo" => unreachable,
else => {},
"foo" => {},
else => unreachable,
}
}
comptime {
const a = "foo";
switch (a) {
("foo") => unreachable, // intentionally allow
("foo") => {}, // intentionally allow
else => {},
}
}
@@ -25,5 +30,6 @@ comptime {
// backend=stage2
// target=native
//
// :3:11: error: cannot compare strings with ==
// :12:9: error: cannot switch on strings
// :3:11: error: cannot compare strings with !=
// :7:11: error: cannot compare strings with ==
// :17:9: error: cannot switch on strings

View File

@@ -1,6 +1,6 @@
export fn foo() void {
const a = 1;
struct {
_ = struct {
test a {}
};
}

View File

@@ -28,10 +28,6 @@ fn foo() void {
_ = jd;
}
export fn entry() usize {
return @sizeOf(@TypeOf(foo));
}
// error
// backend=stage2
// target=native

View File

@@ -7,7 +7,7 @@ export fn entry2() void {
@panic("");
}
export fn entry3() void {
@compileError("");
@compileError("expect to hit this");
@compileError("");
}
@@ -21,3 +21,4 @@ export fn entry3() void {
// :6:5: note: control flow is diverted here
// :11:5: error: unreachable code
// :10:5: note: control flow is diverted here
// :10:5: error: expect to hit this

View File

@@ -2,14 +2,8 @@
fn entry() void {}
fn entry() void {}
fn foo() void {
var foo = 1234;
}
// error
//
// :2:4: error: duplicate struct member name 'entry'
// :3:4: note: duplicate name here
// :2:1: note: struct declared here
// :6:9: error: local variable shadows declaration of 'foo'
// :5:1: note: declared here

View File

@@ -1,6 +1,6 @@
pub fn main() void {
const x = 1;
const y, var z = .{ 2, 3 };
const y, var z: u32 = .{ 2, 3 };
}
// error