compiler: allow @import of ZON without a result type

In particular, this allows importing `build.zig.zon` at comptime.
This commit is contained in:
Mason Remaley 2025-02-15 15:42:59 -08:00 committed by mlugg
parent 1b62a22268
commit 06ee383da9
No known key found for this signature in database
GPG key ID: 3F5B7DCCBF4AF02E
11 changed files with 302 additions and 37 deletions

View file

@ -2136,6 +2136,7 @@ pub const Key = union(enum) {
/// To avoid making this key overly complex, the type-specific data is hashed by Sema. /// To avoid making this key overly complex, the type-specific data is hashed by Sema.
reified: struct { reified: struct {
/// A `reify`, `struct_init`, `struct_init_ref`, or `struct_init_anon` instruction. /// A `reify`, `struct_init`, `struct_init_ref`, or `struct_init_anon` instruction.
/// Alternatively, this is `main_struct_inst` of a ZON file.
zir_index: TrackedInst.Index, zir_index: TrackedInst.Index,
/// A hash of this type's attributes, fields, etc, generated by Sema. /// A hash of this type's attributes, fields, etc, generated by Sema.
type_hash: u64, type_hash: u64,

View file

@ -2998,7 +2998,7 @@ fn zirStructDecl(
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
} }
fn createTypeName( pub fn createTypeName(
sema: *Sema, sema: *Sema,
block: *Block, block: *Block,
name_strategy: Zir.Inst.NameStrategy, name_strategy: Zir.Inst.NameStrategy,
@ -14065,14 +14065,13 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
return Air.internedToRef(ty); return Air.internedToRef(ty);
}, },
.zon => { .zon => {
if (extra.res_ty == .none) { const res_ty: InternPool.Index = b: {
return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{}); if (extra.res_ty == .none) break :b .none;
} const res_ty_inst = try sema.resolveInst(extra.res_ty);
const res_ty_inst = try sema.resolveInst(extra.res_ty); const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst);
const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst); if (res_ty.isGenericPoison()) break :b .none;
if (res_ty.isGenericPoison()) { break :b res_ty.toIntern();
return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{}); };
}
try sema.declareDependency(.{ .zon_file = result.file_index }); try sema.declareDependency(.{ .zon_file = result.file_index });
const interned = try LowerZon.run( const interned = try LowerZon.run(
@ -31699,7 +31698,7 @@ fn addReferenceEntry(
try zcu.addUnitReference(sema.owner, referenced_unit, src); try zcu.addUnitReference(sema.owner, referenced_unit, src);
} }
fn addTypeReferenceEntry( pub fn addTypeReferenceEntry(
sema: *Sema, sema: *Sema,
src: LazySrcLoc, src: LazySrcLoc,
referenced_type: InternPool.Index, referenced_type: InternPool.Index,

View file

@ -33,7 +33,7 @@ pub fn run(
sema: *Sema, sema: *Sema,
file: *File, file: *File,
file_index: Zcu.File.Index, file_index: Zcu.File.Index,
res_ty: Type, res_ty_interned: InternPool.Index,
import_loc: LazySrcLoc, import_loc: LazySrcLoc,
block: *Sema.Block, block: *Sema.Block,
) CompileError!InternPool.Index { ) CompileError!InternPool.Index {
@ -53,13 +53,167 @@ pub fn run(
.base_node_inst = tracked_inst, .base_node_inst = tracked_inst,
}; };
try lower_zon.checkType(res_ty); if (res_ty_interned == .none) {
return lower_zon.lowerExprAnonResTy(.root);
return lower_zon.lowerExpr(.root, res_ty); } else {
const res_ty: Type = .fromInterned(res_ty_interned);
try lower_zon.checkType(res_ty);
return lower_zon.lowerExprKnownResTy(.root, res_ty);
}
} }
/// Validate that `ty` is a valid ZON type. If not, emit a compile error. fn lowerExprAnonResTy(self: *LowerZon, node: Zoir.Node.Index) CompileError!InternPool.Index {
/// i.e. no nested optionals, no error sets, etc. const gpa = self.sema.gpa;
const pt = self.sema.pt;
const ip = &pt.zcu.intern_pool;
switch (node.get(self.file.zoir.?)) {
.true => return .bool_true,
.false => return .bool_false,
.null => return .null_value,
.pos_inf => return self.fail(node, "infinity requires a known result type", .{}),
.neg_inf => return self.fail(node, "negative infinity requires a known result type", .{}),
.nan => return self.fail(node, "NaN requires a known result type", .{}),
.int_literal => |int| switch (int) {
.small => |val| return pt.intern(.{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .i64 = val },
} }),
.big => |val| return pt.intern(.{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .big_int = val },
} }),
},
.float_literal => |val| {
const result = try pt.floatValue(.comptime_float, val);
return result.toIntern();
},
.char_literal => |val| return pt.intern(.{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .i64 = val },
} }),
.enum_literal => |val| return pt.intern(.{
.enum_literal = try ip.getOrPutString(
gpa,
pt.tid,
val.get(self.file.zoir.?),
.no_embedded_nulls,
),
}),
.string_literal => |val| {
const ip_str = try ip.getOrPutString(gpa, pt.tid, val, .maybe_embedded_nulls);
const result = try self.sema.addStrLit(ip_str, val.len);
return result.toInterned().?;
},
.empty_literal => return .empty_tuple,
.array_literal => |nodes| {
const types = try self.sema.arena.alloc(InternPool.Index, nodes.len);
const values = try self.sema.arena.alloc(InternPool.Index, nodes.len);
for (0..nodes.len) |i| {
values[i] = try self.lowerExprAnonResTy(nodes.at(@intCast(i)));
types[i] = Value.fromInterned(values[i]).typeOf(pt.zcu).toIntern();
}
const ty = try ip.getTupleType(
gpa,
pt.tid,
.{
.types = types,
.values = values,
},
);
return pt.intern(.{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = values },
} });
},
.struct_literal => |init| {
const elems = try self.sema.arena.alloc(InternPool.Index, init.names.len);
for (0..init.names.len) |i| {
elems[i] = try self.lowerExprAnonResTy(init.vals.at(@intCast(i)));
}
const struct_ty = switch (try ip.getStructType(
gpa,
pt.tid,
.{
.layout = .auto,
.fields_len = @intCast(init.names.len),
.known_non_opv = false,
.requires_comptime = .no,
.any_comptime_fields = true,
.any_default_inits = true,
.inits_resolved = true,
.any_aligned_fields = false,
.key = .{ .reified = .{
.zir_index = self.base_node_inst,
.type_hash = hash: {
var hasher: std.hash.Wyhash = .init(0);
hasher.update(std.mem.asBytes(&node));
hasher.update(std.mem.sliceAsBytes(elems));
hasher.update(std.mem.sliceAsBytes(init.names));
break :hash hasher.final();
},
} },
},
false,
)) {
.wip => |wip| ty: {
errdefer wip.cancel(ip, pt.tid);
wip.setName(ip, try self.sema.createTypeName(
self.block,
.anon,
"struct",
self.base_node_inst.resolve(ip),
wip.index,
));
const struct_type = ip.loadStructType(wip.index);
for (init.names, 0..) |name, field_idx| {
const name_interned = try ip.getOrPutString(
gpa,
pt.tid,
name.get(self.file.zoir.?),
.no_embedded_nulls,
);
assert(struct_type.addFieldName(ip, name_interned) == null);
struct_type.setFieldComptime(ip, field_idx);
}
@memcpy(struct_type.field_inits.get(ip), elems);
const types = struct_type.field_types.get(ip);
for (0..init.names.len) |i| {
types[i] = Value.fromInterned(elems[i]).typeOf(pt.zcu).toIntern();
}
const new_namespace_index = try pt.createNamespace(.{
.parent = self.block.namespace.toOptional(),
.owner_type = wip.index,
.file_scope = self.block.getFileScopeIndex(pt.zcu),
.generation = pt.zcu.generation,
});
try pt.zcu.comp.queueJob(.{ .resolve_type_fully = wip.index });
codegen_type: {
if (pt.zcu.comp.config.use_llvm) break :codegen_type;
if (self.block.ownerModule().strip) break :codegen_type;
try pt.zcu.comp.queueJob(.{ .codegen_type = wip.index });
}
break :ty wip.finish(ip, new_namespace_index);
},
.existing => |ty| ty,
};
try self.sema.declareDependency(.{ .interned = struct_ty });
try self.sema.addTypeReferenceEntry(self.nodeSrc(node), struct_ty);
return try pt.intern(.{ .aggregate = .{
.ty = struct_ty,
.storage = .{ .elems = elems },
} });
},
}
}
/// Validate that `ty` is a valid ZON type, or emit a compile error.
///
/// Rules out nested optionals, error sets, etc.
fn checkType(self: *LowerZon, ty: Type) !void { fn checkType(self: *LowerZon, ty: Type) !void {
var visited: std.AutoHashMapUnmanaged(InternPool.Index, void) = .empty; var visited: std.AutoHashMapUnmanaged(InternPool.Index, void) = .empty;
try self.checkTypeInner(ty, null, &visited); try self.checkTypeInner(ty, null, &visited);
@ -201,9 +355,9 @@ fn fail(
return self.sema.failWithOwnedErrorMsg(self.block, err_msg); return self.sema.failWithOwnedErrorMsg(self.block, err_msg);
} }
fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { fn lowerExprKnownResTy(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index {
const pt = self.sema.pt; const pt = self.sema.pt;
return self.lowerExprInner(node, res_ty) catch |err| switch (err) { return self.lowerExprKnownResTyInner(node, res_ty) catch |err| switch (err) {
error.WrongType => return self.fail( error.WrongType => return self.fail(
node, node,
"expected type '{}'", "expected type '{}'",
@ -213,7 +367,7 @@ fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!
}; };
} }
fn lowerExprInner( fn lowerExprKnownResTyInner(
self: *LowerZon, self: *LowerZon,
node: Zoir.Node.Index, node: Zoir.Node.Index,
res_ty: Type, res_ty: Type,
@ -227,7 +381,7 @@ fn lowerExprInner(
break :b .none; break :b .none;
} else b: { } else b: {
const child_type = res_ty.optionalChild(pt.zcu); const child_type = res_ty.optionalChild(pt.zcu);
break :b try self.lowerExprInner(node, child_type); break :b try self.lowerExprKnownResTyInner(node, child_type);
}, },
}, },
}), }),
@ -239,7 +393,7 @@ fn lowerExprInner(
.base_addr = .{ .base_addr = .{
.uav = .{ .uav = .{
.orig_ty = res_ty.toIntern(), .orig_ty = res_ty.toIntern(),
.val = try self.lowerExprInner(node, .fromInterned(ptr_info.child)), .val = try self.lowerExprKnownResTyInner(node, .fromInterned(ptr_info.child)),
}, },
}, },
.byte_offset = 0, .byte_offset = 0,
@ -486,7 +640,7 @@ fn lowerArray(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.
); );
for (0..nodes.len) |i| { for (0..nodes.len) |i| {
elems[i] = try self.lowerExpr(nodes.at(@intCast(i)), array_info.elem_type); elems[i] = try self.lowerExprKnownResTy(nodes.at(@intCast(i)), array_info.elem_type);
} }
if (array_info.sentinel) |sentinel| { if (array_info.sentinel) |sentinel| {
@ -587,7 +741,7 @@ fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.
); );
} }
const val = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i])); const val = try self.lowerExprKnownResTy(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i]));
if (elems[i] != .none and val != elems[i]) { if (elems[i] != .none and val != elems[i]) {
const elem_node = elem_nodes.at(@intCast(i)); const elem_node = elem_nodes.at(@intCast(i));
@ -650,7 +804,7 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool
}; };
const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]); const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]);
field_values[name_index] = try self.lowerExpr(field_node, field_type); field_values[name_index] = try self.lowerExprKnownResTy(field_node, field_type);
if (struct_info.comptime_bits.getBit(ip, name_index)) { if (struct_info.comptime_bits.getBit(ip, name_index)) {
const val = ip.indexToKey(field_values[name_index]); const val = ip.indexToKey(field_values[name_index]);
@ -715,7 +869,7 @@ fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.
const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none));
for (elems, 0..) |*elem, i| { for (elems, 0..) |*elem, i| {
elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child)); elem.* = try self.lowerExprKnownResTy(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child));
} }
if (ptr_info.sentinel != .none) { if (ptr_info.sentinel != .none) {
@ -810,7 +964,7 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.
if (field_type.toIntern() == .void_type) { if (field_type.toIntern() == .void_type) {
return self.fail(field_node, "expected type 'void'", .{}); return self.fail(field_node, "expected type 'void'", .{});
} }
break :b try self.lowerExpr(field_node, field_type); break :b try self.lowerExprKnownResTy(field_node, field_type);
} else b: { } else b: {
if (field_type.toIntern() != .void_type) { if (field_type.toIntern() != .void_type) {
return error.WrongType; return error.WrongType;
@ -846,7 +1000,7 @@ fn lowerVector(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool
} }
for (elems, 0..) |*elem, i| { for (elems, 0..) |*elem, i| {
elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(vector_info.child)); elem.* = try self.lowerExprKnownResTy(elem_nodes.at(@intCast(i)), .fromInterned(vector_info.child));
} }
return self.sema.pt.intern(.{ .aggregate = .{ return self.sema.pt.intern(.{ .aggregate = .{

View file

@ -3589,7 +3589,10 @@ pub fn typeDeclSrcLine(ty: Type, zcu: *Zcu) ?u32 {
}; };
const info = tracked.resolveFull(&zcu.intern_pool) orelse return null; const info = tracked.resolveFull(&zcu.intern_pool) orelse return null;
const file = zcu.fileByIndex(info.file); const file = zcu.fileByIndex(info.file);
const zir = file.zir.?; const zir = switch (file.getMode()) {
.zig => file.zir.?,
.zon => return 0,
};
const inst = zir.instructions.get(@intFromEnum(info.inst)); const inst = zir.instructions.get(@intFromEnum(info.inst));
return switch (inst.tag) { return switch (inst.tag) {
.struct_init, .struct_init_ref => zir.extraData(Zir.Inst.StructInit, inst.data.pl_node.payload_index).data.abs_line, .struct_init, .struct_init_ref => zir.extraData(Zir.Inst.StructInit, inst.data.pl_node.payload_index).data.abs_line,

View file

@ -517,3 +517,57 @@ test "recursive" {
const expected: Recursive = .{ .foo = &.{ .foo = null } }; const expected: Recursive = .{ .foo = &.{ .foo = null } };
try expectEqualDeep(expected, @as(Recursive, @import("zon/recursive.zon"))); try expectEqualDeep(expected, @as(Recursive, @import("zon/recursive.zon")));
} }
test "anon" {
const expected = .{
.{
.bool_true = true,
.bool_false = false,
.string = "foo",
},
.{
null,
10,
36893488147419103232,
1.234,
'z',
.bar,
.{},
},
};
const actual = @import("zon/anon.zon");
try expectEqual(expected.len, actual.len);
try expectEqual(expected[1], actual[1]);
const expected_struct = expected[0];
const actual_struct = actual[0];
const expected_fields = @typeInfo(@TypeOf(expected_struct)).@"struct".fields;
const actual_fields = @typeInfo(@TypeOf(actual_struct)).@"struct".fields;
try expectEqual(expected_fields.len, actual_fields.len);
inline for (expected_fields) |field| {
try expectEqual(@field(expected_struct, field.name), @field(actual_struct, field.name));
}
}
test "build.zig.zon" {
const build = @import("zon/build.zig.zon");
try expectEqual(4, @typeInfo(@TypeOf(build)).@"struct".fields.len);
try expectEqualStrings("temp", build.name);
try expectEqualStrings("0.0.0", build.version);
const dependencies = build.dependencies;
try expectEqual(2, @typeInfo(@TypeOf(dependencies)).@"struct".fields.len);
const example_0 = dependencies.example_0;
try expectEqual(2, @typeInfo(@TypeOf(dependencies)).@"struct".fields.len);
try expectEqualStrings("https://example.com/foo.tar.gz", example_0.url);
try expectEqualStrings("...", example_0.hash);
const example_1 = dependencies.example_1;
try expectEqual(2, @typeInfo(@TypeOf(dependencies)).@"struct".fields.len);
try expectEqualStrings("../foo", example_1.path);
try expectEqual(false, example_1.lazy);
try expectEqual(.{ "build.zig", "build.zig.zon", "src" }, build.paths);
}

View file

@ -0,0 +1,16 @@
.{
.{
.bool_true = true,
.bool_false = false,
.string = "foo",
},
.{
null,
10,
36893488147419103232,
1.234,
'z',
.bar,
.{},
},
}

View file

@ -0,0 +1,20 @@
.{
// Comment
.name = "temp",
.version = "0.0.0",
.dependencies = .{
.example_0 = .{
.url = "https://example.com/foo.tar.gz",
.hash = "...",
},
.example_1 = .{
.path = "../foo",
.lazy = false,
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

View file

@ -0,0 +1,9 @@
export fn entry() void {
_ = @import("zon/inf.zon");
}
// error
// imports=zon/inf.zon
//
// inf.zon:1:1: error: infinity requires a known result type
// tmp.zig:2:17: note: imported here

View file

@ -0,0 +1,9 @@
export fn entry() void {
_ = @import("zon/nan.zon");
}
// error
// imports=zon/nan.zon
//
// nan.zon:1:1: error: NaN requires a known result type
// tmp.zig:2:17: note: imported here

View file

@ -0,0 +1,9 @@
export fn entry() void {
_ = @import("zon/neg_inf.zon");
}
// error
// imports=zon/neg_inf.zon
//
// neg_inf.zon:1:1: error: negative infinity requires a known result type
// tmp.zig:2:17: note: imported here

View file

@ -1,9 +0,0 @@
export fn entry() void {
const f = @import("zon/simple_union.zon");
_ = f;
}
// error
// imports=zon/simple_union.zon
//
// tmp.zig:2:23: error: '@import' of ZON must have a known result type