compiler,std: implement ZON support

This commit allows using ZON (Zig Object Notation) in a few ways.

* `@import` can be used to load ZON at comptime and convert it to a
  normal Zig value. In this case, `@import` must have a result type.
* `std.zon.parse` can be used to parse ZON at runtime, akin to the
  parsing logic in `std.json`.
* `std.zon.stringify` can be used to convert arbitrary data structures
  to ZON at runtime, again akin to `std.json`.
This commit is contained in:
Mason Remaley 2024-11-04 14:03:36 -08:00 committed by mlugg
parent 953355ebea
commit 13c6eb0d71
No known key found for this signature in database
GPG key ID: 3F5B7DCCBF4AF02E
145 changed files with 8786 additions and 434 deletions

View file

@ -406,7 +406,7 @@ pub fn build(b: *std.Build) !void {
const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];
const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" };
const fmt_exclude_paths = &.{"test/cases"};
const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" };
const do_fmt = b.addFmt(.{
.paths = fmt_include_paths,
.exclude_paths = fmt_exclude_paths,

View file

@ -473,7 +473,7 @@ pub fn toInt(v: Value, comptime T: type, comp: *const Compilation) ?T {
if (comp.interner.get(v.ref()) != .int) return null;
var space: BigIntSpace = undefined;
const big_int = v.toBigInt(&space, comp);
return big_int.to(T) catch null;
return big_int.toInt(T) catch null;
}
const ComplexOp = enum {

View file

@ -628,13 +628,13 @@ pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref {
if (data.fitsInTwosComp(.unsigned, 32)) {
i.items.appendAssumeCapacity(.{
.tag = .u32,
.data = data.to(u32) catch unreachable,
.data = data.toInt(u32) catch unreachable,
});
break :int;
} else if (data.fitsInTwosComp(.signed, 32)) {
i.items.appendAssumeCapacity(.{
.tag = .i32,
.data = @bitCast(data.to(i32) catch unreachable),
.data = @bitCast(data.toInt(i32) catch unreachable),
});
break :int;
}

View file

@ -2175,10 +2175,13 @@ pub const Const = struct {
TargetTooSmall,
};
/// Convert self to type T.
/// Deprecated; use `toInt`.
pub const to = toInt;
/// Convert self to integer type T.
///
/// Returns an error if self cannot be narrowed into the requested type without truncation.
pub fn to(self: Const, comptime T: type) ConvertError!T {
pub fn toInt(self: Const, comptime T: type) ConvertError!T {
switch (@typeInfo(T)) {
.int => |info| {
// Make sure -0 is handled correctly.
@ -2216,7 +2219,26 @@ pub const Const = struct {
}
}
},
else => @compileError("cannot convert Const to type " ++ @typeName(T)),
else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"),
}
}
/// Convert self to float type T.
pub fn toFloat(self: Const, comptime T: type) T {
if (self.limbs.len == 0) return 0;
const base = std.math.maxInt(std.math.big.Limb) + 1;
var result: f128 = 0;
var i: usize = self.limbs.len;
while (i != 0) {
i -= 1;
const limb: f128 = @floatFromInt(self.limbs[i]);
result = @mulAdd(f128, base, result, limb);
}
if (self.positive) {
return @floatCast(result);
} else {
return @floatCast(-result);
}
}
@ -2775,11 +2797,19 @@ pub const Managed = struct {
pub const ConvertError = Const.ConvertError;
/// Convert self to type T.
/// Deprecated; use `toInt`.
pub const to = toInt;
/// Convert self to integer type T.
///
/// Returns an error if self cannot be narrowed into the requested type without truncation.
pub fn to(self: Managed, comptime T: type) ConvertError!T {
return self.toConst().to(T);
pub fn toInt(self: Managed, comptime T: type) ConvertError!T {
return self.toConst().toInt(T);
}
/// Convert self to float type T.
pub fn toFloat(self: Managed, comptime T: type) T {
return self.toConst().toFloat(T);
}
/// Set self from the string representation `value`.

File diff suppressed because it is too large Load diff

View file

@ -518,28 +518,28 @@ test "set" {
defer a.deinit();
try a.setInt(5);
try testing.expect((try a.p.to(u32)) == 5);
try testing.expect((try a.q.to(u32)) == 1);
try testing.expect((try a.p.toInt(u32)) == 5);
try testing.expect((try a.q.toInt(u32)) == 1);
try a.setRatio(7, 3);
try testing.expect((try a.p.to(u32)) == 7);
try testing.expect((try a.q.to(u32)) == 3);
try testing.expect((try a.p.toInt(u32)) == 7);
try testing.expect((try a.q.toInt(u32)) == 3);
try a.setRatio(9, 3);
try testing.expect((try a.p.to(i32)) == 3);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == 3);
try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(-9, 3);
try testing.expect((try a.p.to(i32)) == -3);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == -3);
try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(9, -3);
try testing.expect((try a.p.to(i32)) == -3);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == -3);
try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(-9, -3);
try testing.expect((try a.p.to(i32)) == 3);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == 3);
try testing.expect((try a.q.toInt(i32)) == 1);
}
test "setFloat" {
@ -547,24 +547,24 @@ test "setFloat" {
defer a.deinit();
try a.setFloat(f64, 2.5);
try testing.expect((try a.p.to(i32)) == 5);
try testing.expect((try a.q.to(i32)) == 2);
try testing.expect((try a.p.toInt(i32)) == 5);
try testing.expect((try a.q.toInt(i32)) == 2);
try a.setFloat(f32, -2.5);
try testing.expect((try a.p.to(i32)) == -5);
try testing.expect((try a.q.to(i32)) == 2);
try testing.expect((try a.p.toInt(i32)) == -5);
try testing.expect((try a.q.toInt(i32)) == 2);
try a.setFloat(f32, 3.141593);
// = 3.14159297943115234375
try testing.expect((try a.p.to(u32)) == 3294199);
try testing.expect((try a.q.to(u32)) == 1048576);
try testing.expect((try a.p.toInt(u32)) == 3294199);
try testing.expect((try a.q.toInt(u32)) == 1048576);
try a.setFloat(f64, 72.141593120712409172417410926841290461290467124);
// = 72.1415931207124145885245525278151035308837890625
try testing.expect((try a.p.to(u128)) == 5076513310880537);
try testing.expect((try a.q.to(u128)) == 70368744177664);
try testing.expect((try a.p.toInt(u128)) == 5076513310880537);
try testing.expect((try a.q.toInt(u128)) == 70368744177664);
}
test "setFloatString" {
@ -574,8 +574,8 @@ test "setFloatString" {
try a.setFloatString("72.14159312071241458852455252781510353");
// = 72.1415931207124145885245525278151035308837890625
try testing.expect((try a.p.to(u128)) == 7214159312071241458852455252781510353);
try testing.expect((try a.q.to(u128)) == 100000000000000000000000000000000000);
try testing.expect((try a.p.toInt(u128)) == 7214159312071241458852455252781510353);
try testing.expect((try a.q.toInt(u128)) == 100000000000000000000000000000000000);
}
test "toFloat" {
@ -612,8 +612,8 @@ test "copy" {
defer b.deinit();
try a.copyInt(b);
try testing.expect((try a.p.to(u32)) == 5);
try testing.expect((try a.q.to(u32)) == 1);
try testing.expect((try a.p.toInt(u32)) == 5);
try testing.expect((try a.q.toInt(u32)) == 1);
var c = try Int.initSet(testing.allocator, 7);
defer c.deinit();
@ -621,8 +621,8 @@ test "copy" {
defer d.deinit();
try a.copyRatio(c, d);
try testing.expect((try a.p.to(u32)) == 7);
try testing.expect((try a.q.to(u32)) == 3);
try testing.expect((try a.p.toInt(u32)) == 7);
try testing.expect((try a.q.toInt(u32)) == 3);
var e = try Int.initSet(testing.allocator, 9);
defer e.deinit();
@ -630,8 +630,8 @@ test "copy" {
defer f.deinit();
try a.copyRatio(e, f);
try testing.expect((try a.p.to(u32)) == 3);
try testing.expect((try a.q.to(u32)) == 1);
try testing.expect((try a.p.toInt(u32)) == 3);
try testing.expect((try a.q.toInt(u32)) == 1);
}
test "negate" {
@ -639,16 +639,16 @@ test "negate" {
defer a.deinit();
try a.setInt(-50);
try testing.expect((try a.p.to(i32)) == -50);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == -50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.negate();
try testing.expect((try a.p.to(i32)) == 50);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == 50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.negate();
try testing.expect((try a.p.to(i32)) == -50);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == -50);
try testing.expect((try a.q.toInt(i32)) == 1);
}
test "abs" {
@ -656,16 +656,16 @@ test "abs" {
defer a.deinit();
try a.setInt(-50);
try testing.expect((try a.p.to(i32)) == -50);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == -50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.abs();
try testing.expect((try a.p.to(i32)) == 50);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == 50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.abs();
try testing.expect((try a.p.to(i32)) == 50);
try testing.expect((try a.q.to(i32)) == 1);
try testing.expect((try a.p.toInt(i32)) == 50);
try testing.expect((try a.q.toInt(i32)) == 1);
}
test "swap" {
@ -677,19 +677,19 @@ test "swap" {
try a.setRatio(50, 23);
try b.setRatio(17, 3);
try testing.expect((try a.p.to(u32)) == 50);
try testing.expect((try a.q.to(u32)) == 23);
try testing.expect((try a.p.toInt(u32)) == 50);
try testing.expect((try a.q.toInt(u32)) == 23);
try testing.expect((try b.p.to(u32)) == 17);
try testing.expect((try b.q.to(u32)) == 3);
try testing.expect((try b.p.toInt(u32)) == 17);
try testing.expect((try b.q.toInt(u32)) == 3);
a.swap(&b);
try testing.expect((try a.p.to(u32)) == 17);
try testing.expect((try a.q.to(u32)) == 3);
try testing.expect((try a.p.toInt(u32)) == 17);
try testing.expect((try a.q.toInt(u32)) == 3);
try testing.expect((try b.p.to(u32)) == 50);
try testing.expect((try b.q.to(u32)) == 23);
try testing.expect((try b.p.toInt(u32)) == 50);
try testing.expect((try b.q.toInt(u32)) == 23);
}
test "order" {

View file

@ -93,6 +93,7 @@ pub const valgrind = @import("valgrind.zig");
pub const wasm = @import("wasm.zig");
pub const zig = @import("zig.zig");
pub const zip = @import("zip.zig");
pub const zon = @import("zon.zig");
pub const start = @import("start.zig");
const root = @import("root");

View file

@ -9448,7 +9448,18 @@ fn builtinCall(
} else if (str.len == 0) {
return astgen.failTok(str_lit_token, "import path cannot be empty", .{});
}
const result = try gz.addStrTok(.import, str.index, str_lit_token);
const res_ty = try ri.rl.resultType(gz, node) orelse .none;
const payload_index = try addExtra(gz.astgen, Zir.Inst.Import{
.res_ty = res_ty,
.path = str.index,
});
const result = try gz.add(.{
.tag = .import,
.data = .{ .pl_tok = .{
.src_tok = gz.tokenIndexToRelative(str_lit_token),
.payload_index = payload_index,
} },
});
const gop = try astgen.imports.getOrPut(astgen.gpa, str.index);
if (!gop.found_existing) {
gop.value_ptr.* = str_lit_token;
@ -11551,9 +11562,21 @@ fn parseStrLit(
}
}
fn failWithStrLitError(astgen: *AstGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, bytes: []const u8, offset: u32) InnerError {
fn failWithStrLitError(
astgen: *AstGen,
err: std.zig.string_literal.Error,
token: Ast.TokenIndex,
bytes: []const u8,
offset: u32,
) InnerError {
const raw_string = bytes[offset..];
return err.lower(raw_string, offset, AstGen.failOff, .{ astgen, token });
return failOff(
astgen,
token,
@intCast(offset + err.offset()),
"{}",
.{err.fmt(raw_string)},
);
}
fn failNode(

View file

@ -483,7 +483,7 @@ pub const Inst = struct {
/// Uses the `pl_node` union field. `payload_index` points to a `FuncFancy`.
func_fancy,
/// Implements the `@import` builtin.
/// Uses the `str_tok` field.
/// Uses the `pl_tok` field.
import,
/// Integer literal that fits in a u64. Uses the `int` union field.
int,
@ -1673,7 +1673,7 @@ pub const Inst = struct {
.func = .pl_node,
.func_inferred = .pl_node,
.func_fancy = .pl_node,
.import = .str_tok,
.import = .pl_tok,
.int = .int,
.int_big = .str,
.float = .float,
@ -3841,6 +3841,13 @@ pub const Inst = struct {
/// If `.none`, restore unconditionally.
operand: Ref,
};
pub const Import = struct {
/// The result type of the import, or `.none` if none was available.
res_ty: Ref,
/// The import path.
path: NullTerminatedString,
};
};
pub const SpecialProng = enum { none, @"else", under };

View file

@ -54,7 +54,7 @@ pub const Node = union(enum) {
/// A floating-point literal.
float_literal: f128,
/// A Unicode codepoint literal.
char_literal: u32,
char_literal: u21,
/// An enum literal. The string is the literal, i.e. `foo` for `.foo`.
enum_literal: NullTerminatedString,
/// A string literal.
@ -96,7 +96,7 @@ pub const Node = union(enum) {
} } },
.float_literal_small => .{ .float_literal = @as(f32, @bitCast(repr.data)) },
.float_literal => .{ .float_literal = @bitCast(zoir.extra[repr.data..][0..4].*) },
.char_literal => .{ .char_literal = repr.data },
.char_literal => .{ .char_literal = @intCast(repr.data) },
.enum_literal => .{ .enum_literal = @enumFromInt(repr.data) },
.string_literal => .{ .string_literal = s: {
const start, const len = zoir.extra[repr.data..][0..2].*;

View file

@ -3,6 +3,8 @@
gpa: Allocator,
tree: Ast,
options: Options,
nodes: std.MultiArrayList(Zoir.Node.Repr),
extra: std.ArrayListUnmanaged(u32),
limbs: std.ArrayListUnmanaged(std.math.big.Limb),
@ -12,12 +14,21 @@ string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.d
compile_errors: std.ArrayListUnmanaged(Zoir.CompileError),
error_notes: std.ArrayListUnmanaged(Zoir.CompileError.Note),
pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zoir {
pub const Options = struct {
/// When false, string literals are not parsed. `string_literal` nodes will contain empty
/// strings, and errors that normally occur during string parsing will not be raised.
///
/// `parseStrLit` and `strLitSizeHint` may be used to parse string literals after the fact.
parse_str_lits: bool = true,
};
pub fn generate(gpa: Allocator, tree: Ast, options: Options) Allocator.Error!Zoir {
assert(tree.mode == .zon);
var zg: ZonGen = .{
.gpa = gpa,
.tree = tree,
.options = options,
.nodes = .empty,
.extra = .empty,
.limbs = .empty,
@ -250,7 +261,20 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator
.block_two_semicolon,
.block,
.block_semicolon,
=> try zg.addErrorNode(node, "blocks are not allowed in ZON", .{}),
=> {
const size = switch (node_tags[node]) {
.block_two, .block_two_semicolon => @intFromBool(node_datas[node].lhs != 0) + @intFromBool(node_datas[node].rhs != 0),
.block, .block_semicolon => node_datas[node].rhs - node_datas[node].lhs,
else => unreachable,
};
if (size == 0) {
try zg.addErrorNodeNotes(node, "void literals are not available in ZON", .{}, &.{
try zg.errNoteNode(node, "void union payloads can be represented by enum literals", .{}),
});
} else {
try zg.addErrorNode(node, "blocks are not allowed in ZON", .{});
}
},
.array_init_one,
.array_init_one_comma,
@ -403,58 +427,37 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator
.ast_node = node,
});
// For short initializers, track the names on the stack rather than going through gpa.
var sfba_state = std.heap.stackFallback(256, gpa);
const sfba = sfba_state.get();
var field_names: std.AutoHashMapUnmanaged(Zoir.NullTerminatedString, Ast.TokenIndex) = .empty;
defer field_names.deinit(sfba);
var reported_any_duplicate = false;
for (full.ast.fields, names_start.., first_elem..) |elem_node, extra_name_idx, elem_dest_node| {
const name_token = tree.firstToken(elem_node) - 2;
zg.extra.items[extra_name_idx] = @intFromEnum(zg.identAsString(name_token) catch |err| switch (err) {
error.BadString => undefined, // doesn't matter, there's an error
if (zg.identAsString(name_token)) |name_str| {
zg.extra.items[extra_name_idx] = @intFromEnum(name_str);
const gop = try field_names.getOrPut(sfba, name_str);
if (gop.found_existing and !reported_any_duplicate) {
reported_any_duplicate = true;
const earlier_token = gop.value_ptr.*;
try zg.addErrorTokNotes(earlier_token, "duplicate struct field name", .{}, &.{
try zg.errNoteTok(name_token, "duplicate name here", .{}),
});
}
gop.value_ptr.* = name_token;
} else |err| switch (err) {
error.BadString => {}, // there's an error, so it's fine to not populate `zg.extra`
error.OutOfMemory => |e| return e,
});
}
try zg.expr(elem_node, @enumFromInt(elem_dest_node));
}
},
}
}
fn parseStrLit(zg: *ZonGen, token: Ast.TokenIndex, offset: u32) !u32 {
const raw_string = zg.tree.tokenSlice(token)[offset..];
const start = zg.string_bytes.items.len;
switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) {
.success => return @intCast(start),
.failure => |err| {
try zg.lowerStrLitError(err, token, raw_string, offset);
return error.BadString;
},
}
}
fn parseMultilineStrLit(zg: *ZonGen, node: Ast.Node.Index) !u32 {
const gpa = zg.gpa;
const tree = zg.tree;
const string_bytes = &zg.string_bytes;
const first_tok, const last_tok = bounds: {
const node_data = tree.nodes.items(.data)[node];
break :bounds .{ node_data.lhs, node_data.rhs };
};
const str_index: u32 = @intCast(string_bytes.items.len);
// First line: do not append a newline.
{
const line_bytes = tree.tokenSlice(first_tok)[2..];
try string_bytes.appendSlice(gpa, line_bytes);
}
// Following lines: each line prepends a newline.
for (first_tok + 1..last_tok + 1) |tok_idx| {
const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..];
try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1);
string_bytes.appendAssumeCapacity('\n');
string_bytes.appendSliceAssumeCapacity(line_bytes);
}
return @intCast(str_index);
}
fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 {
const tree = zg.tree;
assert(tree.tokens.items(.tag)[ident_token] == .identifier);
@ -464,7 +467,18 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 {
try zg.string_bytes.appendSlice(zg.gpa, ident_name);
return @intCast(start);
} else {
const start = try zg.parseStrLit(ident_token, 1);
const offset = 1;
const start: u32 = @intCast(zg.string_bytes.items.len);
const raw_string = zg.tree.tokenSlice(ident_token)[offset..];
try zg.string_bytes.ensureUnusedCapacity(zg.gpa, raw_string.len);
switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) {
.success => {},
.failure => |err| {
try zg.lowerStrLitError(err, ident_token, raw_string, offset);
return error.BadString;
},
}
const slice = zg.string_bytes.items[start..];
if (mem.indexOfScalar(u8, slice, 0) != null) {
try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{});
@ -477,19 +491,93 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 {
}
}
/// Estimates the size of a string node without parsing it.
pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize {
switch (tree.nodes.items(.tag)[node]) {
// Parsed string literals are typically around the size of the raw strings.
.string_literal => {
const token = tree.nodes.items(.main_token)[node];
const raw_string = tree.tokenSlice(token);
return raw_string.len;
},
// Multiline string literal lengths can be computed exactly.
.multiline_string_literal => {
const first_tok, const last_tok = bounds: {
const node_data = tree.nodes.items(.data)[node];
break :bounds .{ node_data.lhs, node_data.rhs };
};
var size = tree.tokenSlice(first_tok)[2..].len;
for (first_tok + 1..last_tok + 1) |tok_idx| {
size += 1; // Newline
size += tree.tokenSlice(@intCast(tok_idx))[2..].len;
}
return size;
},
else => unreachable,
}
}
/// Parses the given node as a string literal.
pub fn parseStrLit(
tree: Ast,
node: Ast.Node.Index,
writer: anytype,
) error{OutOfMemory}!std.zig.string_literal.Result {
switch (tree.nodes.items(.tag)[node]) {
.string_literal => {
const token = tree.nodes.items(.main_token)[node];
const raw_string = tree.tokenSlice(token);
return std.zig.string_literal.parseWrite(writer, raw_string);
},
.multiline_string_literal => {
const first_tok, const last_tok = bounds: {
const node_data = tree.nodes.items(.data)[node];
break :bounds .{ node_data.lhs, node_data.rhs };
};
// First line: do not append a newline.
{
const line_bytes = tree.tokenSlice(first_tok)[2..];
try writer.writeAll(line_bytes);
}
// Following lines: each line prepends a newline.
for (first_tok + 1..last_tok + 1) |tok_idx| {
const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..];
try writer.writeByte('\n');
try writer.writeAll(line_bytes);
}
return .success;
},
// Node must represent a string
else => unreachable,
}
}
const StringLiteralResult = union(enum) {
nts: Zoir.NullTerminatedString,
slice: struct { start: u32, len: u32 },
};
fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) !StringLiteralResult {
if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } };
const gpa = zg.gpa;
const string_bytes = &zg.string_bytes;
const str_index = switch (zg.tree.nodes.items(.tag)[str_node]) {
.string_literal => try zg.parseStrLit(zg.tree.nodes.items(.main_token)[str_node], 0),
.multiline_string_literal => try zg.parseMultilineStrLit(str_node),
else => unreachable,
};
const str_index: u32 = @intCast(zg.string_bytes.items.len);
const size_hint = strLitSizeHint(zg.tree, str_node);
try string_bytes.ensureUnusedCapacity(zg.gpa, size_hint);
switch (try parseStrLit(zg.tree, str_node, zg.string_bytes.writer(zg.gpa))) {
.success => {},
.failure => |err| {
const token = zg.tree.nodes.items(.main_token)[str_node];
const raw_string = zg.tree.tokenSlice(token);
try zg.lowerStrLitError(err, token, raw_string, 0);
return error.BadString;
},
}
const key: []const u8 = string_bytes.items[str_index..];
if (std.mem.indexOfScalar(u8, key, 0) != null) return .{ .slice = .{
.start = str_index,
@ -540,7 +628,7 @@ fn numberLiteral(zg: *ZonGen, num_node: Ast.Node.Index, src_node: Ast.Node.Index
if (unsigned_num == 0 and sign == .negative) {
try zg.addErrorTokNotes(num_token, "integer literal '-0' is ambiguous", .{}, &.{
try zg.errNoteTok(num_token, "use '0' for an integer zero", .{}),
try zg.errNoteTok(num_token, "use '-0.0' for a flaoting-point signed zero", .{}),
try zg.errNoteTok(num_token, "use '-0.0' for a floating-point signed zero", .{}),
});
return;
}
@ -679,8 +767,20 @@ fn setNode(zg: *ZonGen, dest: Zoir.Node.Index, repr: Zoir.Node.Repr) void {
zg.nodes.set(@intFromEnum(dest), repr);
}
fn lowerStrLitError(zg: *ZonGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, raw_string: []const u8, offset: u32) Allocator.Error!void {
return err.lower(raw_string, offset, ZonGen.addErrorTokOff, .{ zg, token });
fn lowerStrLitError(
zg: *ZonGen,
err: std.zig.string_literal.Error,
token: Ast.TokenIndex,
raw_string: []const u8,
offset: u32,
) Allocator.Error!void {
return ZonGen.addErrorTokOff(
zg,
token,
@intCast(offset + err.offset()),
"{}",
.{err.fmt(raw_string)},
);
}
fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) Allocator.Error!void {

View file

@ -39,40 +39,82 @@ pub const Error = union(enum) {
/// `''`. Not returned for string literals.
empty_char_literal,
/// Returns `func(first_args[0], ..., first_args[n], offset + bad_idx, format, args)`.
pub fn lower(
const FormatMessage = struct {
err: Error,
raw_string: []const u8,
offset: u32,
comptime func: anytype,
first_args: anytype,
) @typeInfo(@TypeOf(func)).@"fn".return_type.? {
switch (err) {
inline else => |bad_index_or_void, tag| {
const bad_index: u32 = switch (@TypeOf(bad_index_or_void)) {
void => 0,
else => @intCast(bad_index_or_void),
};
const fmt_str: []const u8, const args = switch (tag) {
.invalid_escape_character => .{ "invalid escape character: '{c}'", .{raw_string[bad_index]} },
.expected_hex_digit => .{ "expected hex digit, found '{c}'", .{raw_string[bad_index]} },
.empty_unicode_escape_sequence => .{ "empty unicode escape sequence", .{} },
.expected_hex_digit_or_rbrace => .{ "expected hex digit or '}}', found '{c}'", .{raw_string[bad_index]} },
.invalid_unicode_codepoint => .{ "unicode escape does not correspond to a valid unicode scalar value", .{} },
.expected_lbrace => .{ "expected '{{', found '{c}'", .{raw_string[bad_index]} },
.expected_rbrace => .{ "expected '}}', found '{c}'", .{raw_string[bad_index]} },
.expected_single_quote => .{ "expected singel quote ('), found '{c}'", .{raw_string[bad_index]} },
.invalid_character => .{ "invalid byte in string or character literal: '{c}'", .{raw_string[bad_index]} },
.empty_char_literal => .{ "empty character literal", .{} },
};
return @call(.auto, func, first_args ++ .{
offset + bad_index,
fmt_str,
args,
});
},
};
fn formatMessage(
self: FormatMessage,
comptime f: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = f;
_ = options;
switch (self.err) {
.invalid_escape_character => |bad_index| try writer.print(
"invalid escape character: '{c}'",
.{self.raw_string[bad_index]},
),
.expected_hex_digit => |bad_index| try writer.print(
"expected hex digit, found '{c}'",
.{self.raw_string[bad_index]},
),
.empty_unicode_escape_sequence => try writer.writeAll(
"empty unicode escape sequence",
),
.expected_hex_digit_or_rbrace => |bad_index| try writer.print(
"expected hex digit or '}}', found '{c}'",
.{self.raw_string[bad_index]},
),
.invalid_unicode_codepoint => try writer.writeAll(
"unicode escape does not correspond to a valid unicode scalar value",
),
.expected_lbrace => |bad_index| try writer.print(
"expected '{{', found '{c}'",
.{self.raw_string[bad_index]},
),
.expected_rbrace => |bad_index| try writer.print(
"expected '}}', found '{c}'",
.{self.raw_string[bad_index]},
),
.expected_single_quote => |bad_index| try writer.print(
"expected single quote ('), found '{c}'",
.{self.raw_string[bad_index]},
),
.invalid_character => |bad_index| try writer.print(
"invalid byte in string or character literal: '{c}'",
.{self.raw_string[bad_index]},
),
.empty_char_literal => try writer.writeAll(
"empty character literal",
),
}
}
pub fn fmt(self: @This(), raw_string: []const u8) std.fmt.Formatter(formatMessage) {
return .{ .data = .{
.err = self,
.raw_string = raw_string,
} };
}
pub fn offset(err: Error) usize {
return switch (err) {
inline .invalid_escape_character,
.expected_hex_digit,
.empty_unicode_escape_sequence,
.expected_hex_digit_or_rbrace,
.invalid_unicode_codepoint,
.expected_lbrace,
.expected_rbrace,
.expected_single_quote,
.invalid_character,
=> |n| n,
.empty_char_literal => 0,
};
}
};
/// Asserts the slice starts and ends with single-quotes.

45
lib/std/zon.zig Normal file
View file

@ -0,0 +1,45 @@
//! ZON parsing and stringification.
//!
//! ZON ("Zig Object Notation") is a textual file format. Outside of `nan` and `inf` literals, ZON's
//! grammar is a subset of Zig's.
//!
//! Supported Zig primitives:
//! * boolean literals
//! * number literals (including `nan` and `inf`)
//! * character literals
//! * enum literals
//! * `null` literals
//! * string literals
//! * multiline string literals
//!
//! Supported Zig container types:
//! * anonymous struct literals
//! * anonymous tuple literals
//!
//! Here is an example ZON object:
//! ```
//! .{
//! .a = 1.5,
//! .b = "hello, world!",
//! .c = .{ true, false },
//! .d = .{ 1, 2, 3 },
//! }
//! ```
//!
//! Individual primitives are also valid ZON, for example:
//! ```
//! "This string is a valid ZON object."
//! ```
//!
//! ZON may not contain type names.
//!
//! ZON does not have syntax for pointers, but the parsers will allocate as needed to match the
//! given Zig types. Similarly, the serializer will traverse pointers.
pub const parse = @import("zon/parse.zig");
pub const stringify = @import("zon/stringify.zig");
test {
_ = parse;
_ = stringify;
}

3449
lib/std/zon/parse.zig Normal file

File diff suppressed because it is too large Load diff

2306
lib/std/zon/stringify.zig Normal file

File diff suppressed because it is too large Load diff

View file

@ -2220,7 +2220,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
try comp.astgen_work_queue.ensureUnusedCapacity(zcu.import_table.count());
for (zcu.import_table.values()) |file_index| {
if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue;
comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
const file = zcu.fileByIndex(file_index);
if (file.getMode() == .zig) {
comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
}
}
if (comp.file_system_inputs) |fsi| {
for (zcu.import_table.values()) |file_index| {
@ -3206,10 +3209,16 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
if (error_msg) |msg| {
try addModuleErrorMsg(zcu, &bundle, msg.*);
} else {
// Must be ZIR errors. Note that this may include AST errors.
// addZirErrorMessages asserts that the tree is loaded.
_ = try file.getTree(gpa);
try addZirErrorMessages(&bundle, file);
// Must be ZIR or Zoir errors. Note that this may include AST errors.
_ = try file.getTree(gpa); // Tree must be loaded.
if (file.zir_loaded) {
try addZirErrorMessages(&bundle, file);
} else if (file.zoir != null) {
try addZoirErrorMessages(&bundle, file);
} else {
// Either Zir or Zoir must have been loaded.
unreachable;
}
}
}
var sorted_failed_analysis: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, *Zcu.ErrorMsg).DataList.Slice = s: {
@ -3623,6 +3632,15 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
return eb.addZirErrorMessages(file.zir, file.tree, file.source, src_path);
}
pub fn addZoirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
assert(file.source_loaded);
assert(file.tree_loaded);
const gpa = eb.gpa;
const src_path = try file.fullPath(gpa);
defer gpa.free(src_path);
return eb.addZoirErrorMessages(file.zoir.?, file.tree, file.source, src_path);
}
pub fn performAllTheWork(
comp: *Compilation,
main_progress_node: std.Progress.Node,
@ -4272,6 +4290,7 @@ fn workerAstGenFile(
wg: *WaitGroup,
src: Zcu.AstGenSrc,
) void {
assert(file.getMode() == .zig);
const child_prog_node = prog_node.start(file.sub_file_path, 0);
defer child_prog_node.end();
@ -4325,7 +4344,7 @@ fn workerAstGenFile(
const imported_path_digest = pt.zcu.filePathDigest(res.file_index);
break :blk .{ res, imported_path_digest };
};
if (import_result.is_new) {
if (import_result.is_new and import_result.file.getMode() == .zig) {
log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{
file.sub_file_path, import_path, import_result.file.sub_file_path,
});

View file

@ -4389,7 +4389,7 @@ pub const LoadedEnumType = struct {
// Auto-numbered enum. Convert `int_tag_val` to field index.
const field_index = switch (ip.indexToKey(int_tag_val).int.storage) {
inline .u64, .i64 => |x| std.math.cast(u32, x) orelse return null,
.big_int => |x| x.to(u32) catch return null,
.big_int => |x| x.toInt(u32) catch return null,
.lazy_align, .lazy_size => unreachable,
};
return if (field_index < self.names.len) field_index else null;
@ -7957,7 +7957,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
.big_int => |big_int| {
items.appendAssumeCapacity(.{
.tag = .int_u8,
.data = big_int.to(u8) catch unreachable,
.data = big_int.toInt(u8) catch unreachable,
});
break :b;
},
@ -7974,7 +7974,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
.big_int => |big_int| {
items.appendAssumeCapacity(.{
.tag = .int_u16,
.data = big_int.to(u16) catch unreachable,
.data = big_int.toInt(u16) catch unreachable,
});
break :b;
},
@ -7991,7 +7991,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
.big_int => |big_int| {
items.appendAssumeCapacity(.{
.tag = .int_u32,
.data = big_int.to(u32) catch unreachable,
.data = big_int.toInt(u32) catch unreachable,
});
break :b;
},
@ -8006,7 +8006,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
},
.i32_type => switch (int.storage) {
.big_int => |big_int| {
const casted = big_int.to(i32) catch unreachable;
const casted = big_int.toInt(i32) catch unreachable;
items.appendAssumeCapacity(.{
.tag = .int_i32,
.data = @as(u32, @bitCast(casted)),
@ -8024,7 +8024,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
},
.usize_type => switch (int.storage) {
.big_int => |big_int| {
if (big_int.to(u32)) |casted| {
if (big_int.toInt(u32)) |casted| {
items.appendAssumeCapacity(.{
.tag = .int_usize,
.data = casted,
@ -8045,14 +8045,14 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
},
.comptime_int_type => switch (int.storage) {
.big_int => |big_int| {
if (big_int.to(u32)) |casted| {
if (big_int.toInt(u32)) |casted| {
items.appendAssumeCapacity(.{
.tag = .int_comptime_int_u32,
.data = casted,
});
break :b;
} else |_| {}
if (big_int.to(i32)) |casted| {
if (big_int.toInt(i32)) |casted| {
items.appendAssumeCapacity(.{
.tag = .int_comptime_int_i32,
.data = @as(u32, @bitCast(casted)),
@ -8082,7 +8082,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
}
switch (int.storage) {
.big_int => |big_int| {
if (big_int.to(u32)) |casted| {
if (big_int.toInt(u32)) |casted| {
items.appendAssumeCapacity(.{
.tag = .int_small,
.data = try addExtra(extra, IntSmall{

View file

@ -187,6 +187,7 @@ const Alignment = InternPool.Alignment;
const AnalUnit = InternPool.AnalUnit;
const ComptimeAllocIndex = InternPool.ComptimeAllocIndex;
const Cache = std.Build.Cache;
const LowerZon = @import("Sema/LowerZon.zig");
pub const default_branch_quota = 1000;
pub const default_reference_trace_len = 2;
@ -5790,7 +5791,7 @@ fn addNullTerminatedStrLit(sema: *Sema, string: InternPool.NullTerminatedString)
return sema.addStrLit(string.toString(), string.length(&sema.pt.zcu.intern_pool));
}
fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref {
pub fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const array_ty = try pt.arrayType(.{
.len = len,
@ -13964,9 +13965,10 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
const extra = sema.code.extraData(Zir.Inst.Import, inst_data.payload_index).data;
const operand_src = block.tokenOffset(inst_data.src_tok);
const operand = inst_data.get(sema.code);
const operand = sema.code.nullTerminatedString(extra.path);
const result = pt.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) {
error.ImportOutsideModulePath => {
@ -13983,12 +13985,42 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
},
};
try sema.declareDependency(.{ .file = result.file_index });
try pt.ensureFileAnalyzed(result.file_index);
const ty = zcu.fileRootType(result.file_index);
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(operand_src, ty);
return Air.internedToRef(ty);
switch (result.file.getMode()) {
.zig => {
try sema.declareDependency(.{ .file = result.file_index });
try pt.ensureFileAnalyzed(result.file_index);
const ty = zcu.fileRootType(result.file_index);
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(operand_src, ty);
return Air.internedToRef(ty);
},
.zon => {
_ = result.file.getTree(zcu.gpa) catch |err| {
// TODO: these errors are file system errors; make sure an update() will
// retry this and not cache the file system error, which may be transient.
return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ result.file.sub_file_path, @errorName(err) });
};
if (extra.res_ty == .none) {
return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{});
}
const res_ty_inst = try sema.resolveInst(extra.res_ty);
const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst);
if (res_ty.isGenericPoison()) {
return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{});
}
const interned = try LowerZon.run(
sema,
result.file,
result.file_index,
res_ty,
operand_src,
block,
);
return Air.internedToRef(interned);
},
}
}
fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {

858
src/Sema/LowerZon.zig Normal file
View file

@ -0,0 +1,858 @@
const std = @import("std");
const Zcu = @import("../Zcu.zig");
const Sema = @import("../Sema.zig");
const Air = @import("../Air.zig");
const InternPool = @import("../InternPool.zig");
const Type = @import("../Type.zig");
const Value = @import("../Value.zig");
const Zir = std.zig.Zir;
const AstGen = std.zig.AstGen;
const CompileError = Zcu.CompileError;
const Ast = std.zig.Ast;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const File = Zcu.File;
const LazySrcLoc = Zcu.LazySrcLoc;
const Ref = std.zig.Zir.Inst.Ref;
const NullTerminatedString = InternPool.NullTerminatedString;
const NumberLiteralError = std.zig.number_literal.Error;
const NodeIndex = std.zig.Ast.Node.Index;
const Zoir = std.zig.Zoir;
const LowerZon = @This();
sema: *Sema,
file: *File,
file_index: Zcu.File.Index,
import_loc: LazySrcLoc,
block: *Sema.Block,
base_node_inst: InternPool.TrackedInst.Index,
/// Lowers the given file as ZON.
pub fn run(
sema: *Sema,
file: *File,
file_index: Zcu.File.Index,
res_ty: Type,
import_loc: LazySrcLoc,
block: *Sema.Block,
) CompileError!InternPool.Index {
const pt = sema.pt;
_ = try file.getZoir(pt.zcu);
const tracked_inst = try pt.zcu.intern_pool.trackZir(pt.zcu.gpa, pt.tid, .{
.file = file_index,
.inst = .main_struct_inst, // this is the only trackable instruction in a ZON file
});
var lower_zon: LowerZon = .{
.sema = sema,
.file = file,
.file_index = file_index,
.import_loc = import_loc,
.block = block,
.base_node_inst = tracked_inst,
};
try lower_zon.checkType(res_ty);
return lower_zon.lowerExpr(.root, res_ty);
}
/// Validate that `ty` is a valid ZON type. If not, emit a compile error.
/// i.e. no nested optionals, no error sets, etc.
fn checkType(self: *LowerZon, ty: Type) !void {
var visited: std.AutoHashMapUnmanaged(InternPool.Index, void) = .empty;
try self.checkTypeInner(ty, null, &visited);
}
fn checkTypeInner(
self: *LowerZon,
ty: Type,
parent_opt_ty: ?Type,
/// Visited structs and unions (not tuples). These are tracked because they are the only way in
/// which a type can be self-referential, so must be tracked to avoid loops. Tracking more types
/// consumes memory unnecessarily, and would be complicated by optionals.
/// Allocated into `self.sema.arena`.
visited: *std.AutoHashMapUnmanaged(InternPool.Index, void),
) !void {
const sema = self.sema;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
switch (ty.zigTypeTag(zcu)) {
.bool,
.int,
.float,
.null,
.@"enum",
.comptime_float,
.comptime_int,
.enum_literal,
=> {},
.noreturn,
.void,
.type,
.undefined,
.error_union,
.error_set,
.@"fn",
.frame,
.@"anyframe",
.@"opaque",
=> return self.failUnsupportedResultType(ty, null),
.pointer => {
const ptr_info = ty.ptrInfo(zcu);
if (!ptr_info.flags.is_const) {
return self.failUnsupportedResultType(
ty,
"ZON does not allow mutable pointers",
);
}
switch (ptr_info.flags.size) {
.one => try self.checkTypeInner(
.fromInterned(ptr_info.child),
parent_opt_ty, // preserved
visited,
),
.slice => try self.checkTypeInner(
.fromInterned(ptr_info.child),
null,
visited,
),
.many => return self.failUnsupportedResultType(ty, "ZON does not allow many-pointers"),
.c => return self.failUnsupportedResultType(ty, "ZON does not allow C pointers"),
}
},
.optional => if (parent_opt_ty) |p| {
return self.failUnsupportedResultType(p, "ZON does not allow nested optionals");
} else try self.checkTypeInner(
ty.optionalChild(zcu),
ty,
visited,
),
.array, .vector => {
try self.checkTypeInner(ty.childType(zcu), null, visited);
},
.@"struct" => if (ty.isTuple(zcu)) {
const tuple_info = ip.indexToKey(ty.toIntern()).tuple_type;
const field_types = tuple_info.types.get(ip);
for (field_types) |field_type| {
try self.checkTypeInner(.fromInterned(field_type), null, visited);
}
} else {
const gop = try visited.getOrPut(sema.arena, ty.toIntern());
if (gop.found_existing) return;
try ty.resolveFields(pt);
const struct_info = zcu.typeToStruct(ty).?;
for (struct_info.field_types.get(ip)) |field_type| {
try self.checkTypeInner(.fromInterned(field_type), null, visited);
}
},
.@"union" => {
const gop = try visited.getOrPut(sema.arena, ty.toIntern());
if (gop.found_existing) return;
try ty.resolveFields(pt);
const union_info = zcu.typeToUnion(ty).?;
for (union_info.field_types.get(ip)) |field_type| {
if (field_type != .void_type) {
try self.checkTypeInner(.fromInterned(field_type), null, visited);
}
}
},
}
}
fn nodeSrc(self: *LowerZon, node: Zoir.Node.Index) LazySrcLoc {
return .{
.base_node_inst = self.base_node_inst,
.offset = .{ .node_abs = node.getAstNode(self.file.zoir.?) },
};
}
fn failUnsupportedResultType(
self: *LowerZon,
ty: Type,
opt_note: ?[]const u8,
) error{ AnalysisFail, OutOfMemory } {
@branchHint(.cold);
const sema = self.sema;
const gpa = sema.gpa;
const pt = sema.pt;
return sema.failWithOwnedErrorMsg(self.block, msg: {
const msg = try sema.errMsg(self.import_loc, "type '{}' is not available in ZON", .{ty.fmt(pt)});
errdefer msg.destroy(gpa);
if (opt_note) |n| try sema.errNote(self.import_loc, msg, "{s}", .{n});
break :msg msg;
});
}
fn fail(
self: *LowerZon,
node: Zoir.Node.Index,
comptime format: []const u8,
args: anytype,
) error{ AnalysisFail, OutOfMemory } {
@branchHint(.cold);
const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.nodeSrc(node), format, args);
try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{});
return self.sema.failWithOwnedErrorMsg(self.block, err_msg);
}
fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index {
const pt = self.sema.pt;
return self.lowerExprInner(node, res_ty) catch |err| switch (err) {
error.WrongType => return self.fail(
node,
"expected type '{}'",
.{res_ty.fmt(pt)},
),
else => |e| return e,
};
}
fn lowerExprInner(
self: *LowerZon,
node: Zoir.Node.Index,
res_ty: Type,
) (CompileError || error{WrongType})!InternPool.Index {
const pt = self.sema.pt;
switch (res_ty.zigTypeTag(pt.zcu)) {
.optional => return pt.intern(.{
.opt = .{
.ty = res_ty.toIntern(),
.val = if (node.get(self.file.zoir.?) == .null) b: {
break :b .none;
} else b: {
const child_type = res_ty.optionalChild(pt.zcu);
break :b try self.lowerExprInner(node, child_type);
},
},
}),
.pointer => {
const ptr_info = res_ty.ptrInfo(pt.zcu);
switch (ptr_info.flags.size) {
.one => return pt.intern(.{ .ptr = .{
.ty = res_ty.toIntern(),
.base_addr = .{
.uav = .{
.orig_ty = res_ty.toIntern(),
.val = try self.lowerExprInner(node, .fromInterned(ptr_info.child)),
},
},
.byte_offset = 0,
} }),
.slice => return self.lowerSlice(node, res_ty),
else => {
// Unsupported pointer type, checked in `lower`
unreachable;
},
}
},
.bool => return self.lowerBool(node),
.int, .comptime_int => return self.lowerInt(node, res_ty),
.float, .comptime_float => return self.lowerFloat(node, res_ty),
.null => return self.lowerNull(node),
.@"enum" => return self.lowerEnum(node, res_ty),
.enum_literal => return self.lowerEnumLiteral(node),
.array => return self.lowerArray(node, res_ty),
.@"struct" => return self.lowerStructOrTuple(node, res_ty),
.@"union" => return self.lowerUnion(node, res_ty),
.vector => return self.lowerVector(node, res_ty),
.type,
.noreturn,
.undefined,
.error_union,
.error_set,
.@"fn",
.@"opaque",
.frame,
.@"anyframe",
.void,
=> return self.fail(node, "type '{}' not available in ZON", .{res_ty.fmt(pt)}),
}
}
fn lowerBool(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index {
return switch (node.get(self.file.zoir.?)) {
.true => .bool_true,
.false => .bool_false,
else => return error.WrongType,
};
}
fn lowerInt(
self: *LowerZon,
node: Zoir.Node.Index,
res_ty: Type,
) !InternPool.Index {
@setFloatMode(.strict);
return switch (node.get(self.file.zoir.?)) {
.int_literal => |int| switch (int) {
.small => |val| {
const rhs: i32 = val;
// If our result is a fixed size integer, check that our value is not out of bounds
if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) {
const lhs_info = res_ty.intInfo(self.sema.pt.zcu);
// If lhs is unsigned and rhs is less than 0, we're out of bounds
if (lhs_info.signedness == .unsigned and rhs < 0) return self.fail(
node,
"type '{}' cannot represent integer value '{}'",
.{ res_ty.fmt(self.sema.pt), rhs },
);
// If lhs has less than the 32 bits rhs can hold, we need to check the max and
// min values
if (std.math.cast(u5, lhs_info.bits)) |bits| {
const min_int: i32 = if (lhs_info.signedness == .unsigned or bits == 0) b: {
break :b 0;
} else b: {
break :b -(@as(i32, 1) << (bits - 1));
};
const max_int: i32 = if (bits == 0) b: {
break :b 0;
} else b: {
break :b (@as(i32, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1;
};
if (rhs < min_int or rhs > max_int) {
return self.fail(
node,
"type '{}' cannot represent integer value '{}'",
.{ res_ty.fmt(self.sema.pt), rhs },
);
}
}
}
return self.sema.pt.intern(.{ .int = .{
.ty = res_ty.toIntern(),
.storage = .{ .i64 = rhs },
} });
},
.big => |val| {
if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) {
const int_info = res_ty.intInfo(self.sema.pt.zcu);
if (!val.fitsInTwosComp(int_info.signedness, int_info.bits)) {
return self.fail(
node,
"type '{}' cannot represent integer value '{}'",
.{ res_ty.fmt(self.sema.pt), val },
);
}
}
return self.sema.pt.intern(.{ .int = .{
.ty = res_ty.toIntern(),
.storage = .{ .big_int = val },
} });
},
},
.float_literal => |val| {
// Check for fractional components
if (@rem(val, 1) != 0) {
return self.fail(
node,
"fractional component prevents float value '{}' from coercion to type '{}'",
.{ val, res_ty.fmt(self.sema.pt) },
);
}
// Create a rational representation of the float
var rational = try std.math.big.Rational.init(self.sema.arena);
rational.setFloat(f128, val) catch |err| switch (err) {
error.NonFiniteFloat => unreachable,
error.OutOfMemory => return error.OutOfMemory,
};
// The float is reduced in rational.setFloat, so we assert that denominator is equal to
// one
const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
assert(rational.q.toConst().eqlAbs(big_one));
// Check that the result is in range of the result type
const int_info = res_ty.intInfo(self.sema.pt.zcu);
if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) {
return self.fail(
node,
"type '{}' cannot represent integer value '{}'",
.{ val, res_ty.fmt(self.sema.pt) },
);
}
return self.sema.pt.intern(.{
.int = .{
.ty = res_ty.toIntern(),
.storage = .{ .big_int = rational.p.toConst() },
},
});
},
.char_literal => |val| {
// If our result is a fixed size integer, check that our value is not out of bounds
if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) {
const dest_info = res_ty.intInfo(self.sema.pt.zcu);
const unsigned_bits = dest_info.bits - @intFromBool(dest_info.signedness == .signed);
if (unsigned_bits < 21) {
const out_of_range: u21 = @as(u21, 1) << @intCast(unsigned_bits);
if (val >= out_of_range) {
return self.fail(
node,
"type '{}' cannot represent integer value '{}'",
.{ res_ty.fmt(self.sema.pt), val },
);
}
}
}
return self.sema.pt.intern(.{
.int = .{
.ty = res_ty.toIntern(),
.storage = .{ .i64 = val },
},
});
},
else => return error.WrongType,
};
}
fn lowerFloat(
self: *LowerZon,
node: Zoir.Node.Index,
res_ty: Type,
) !InternPool.Index {
@setFloatMode(.strict);
const value = switch (node.get(self.file.zoir.?)) {
.int_literal => |int| switch (int) {
.small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
.big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)),
},
.float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
.char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
.pos_inf => b: {
if (res_ty.toIntern() == .comptime_float_type) return self.fail(
node,
"expected type '{}'",
.{res_ty.fmt(self.sema.pt)},
);
break :b try self.sema.pt.floatValue(res_ty, std.math.inf(f128));
},
.neg_inf => b: {
if (res_ty.toIntern() == .comptime_float_type) return self.fail(
node,
"expected type '{}'",
.{res_ty.fmt(self.sema.pt)},
);
break :b try self.sema.pt.floatValue(res_ty, -std.math.inf(f128));
},
.nan => b: {
if (res_ty.toIntern() == .comptime_float_type) return self.fail(
node,
"expected type '{}'",
.{res_ty.fmt(self.sema.pt)},
);
break :b try self.sema.pt.floatValue(res_ty, std.math.nan(f128));
},
else => return error.WrongType,
};
return value.toIntern();
}
fn lowerNull(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index {
switch (node.get(self.file.zoir.?)) {
.null => return .null_value,
else => return error.WrongType,
}
}
fn lowerArray(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const array_info = res_ty.arrayInfo(self.sema.pt.zcu);
const nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
.array_literal => |nodes| nodes,
.empty_literal => .{ .start = node, .len = 0 },
else => return error.WrongType,
};
if (nodes.len != array_info.len) {
return error.WrongType;
}
const elems = try self.sema.arena.alloc(
InternPool.Index,
nodes.len + @intFromBool(array_info.sentinel != null),
);
for (0..nodes.len) |i| {
elems[i] = try self.lowerExpr(nodes.at(@intCast(i)), array_info.elem_type);
}
if (array_info.sentinel) |sentinel| {
elems[elems.len - 1] = sentinel.toIntern();
}
return self.sema.pt.intern(.{ .aggregate = .{
.ty = res_ty.toIntern(),
.storage = .{ .elems = elems },
} });
}
fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
switch (node.get(self.file.zoir.?)) {
.enum_literal => |field_name| {
const field_name_interned = try ip.getOrPutString(
self.sema.gpa,
self.sema.pt.tid,
field_name.get(self.file.zoir.?),
.no_embedded_nulls,
);
const field_index = res_ty.enumFieldIndex(field_name_interned, self.sema.pt.zcu) orelse {
return self.fail(
node,
"enum {} has no member named '{}'",
.{
res_ty.fmt(self.sema.pt),
std.zig.fmtId(field_name.get(self.file.zoir.?)),
},
);
};
const value = try self.sema.pt.enumValueFieldIndex(res_ty, field_index);
return value.toIntern();
},
else => return error.WrongType,
}
}
fn lowerEnumLiteral(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
switch (node.get(self.file.zoir.?)) {
.enum_literal => |field_name| {
const field_name_interned = try ip.getOrPutString(
self.sema.gpa,
self.sema.pt.tid,
field_name.get(self.file.zoir.?),
.no_embedded_nulls,
);
return self.sema.pt.intern(.{ .enum_literal = field_name_interned });
},
else => return error.WrongType,
}
}
fn lowerStructOrTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
return switch (ip.indexToKey(res_ty.toIntern())) {
.tuple_type => self.lowerTuple(node, res_ty),
.struct_type => self.lowerStruct(node, res_ty),
else => unreachable,
};
}
fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
const tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type;
const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
.array_literal => |nodes| nodes,
.empty_literal => .{ .start = node, .len = 0 },
else => return error.WrongType,
};
const field_types = tuple_info.types.get(ip);
const elems = try self.sema.arena.alloc(InternPool.Index, field_types.len);
const field_comptime_vals = tuple_info.values.get(ip);
if (field_comptime_vals.len > 0) {
@memcpy(elems, field_comptime_vals);
} else {
@memset(elems, .none);
}
for (0..elem_nodes.len) |i| {
if (i >= elems.len) {
const elem_node = elem_nodes.at(@intCast(i));
return self.fail(
elem_node,
"index {} outside tuple of length {}",
.{
elems.len,
elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?),
},
);
}
const val = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i]));
if (elems[i] != .none and val != elems[i]) {
const elem_node = elem_nodes.at(@intCast(i));
return self.fail(
elem_node,
"value stored in comptime field does not match the default value of the field",
.{},
);
}
elems[i] = val;
}
for (elems, 0..) |val, i| {
if (val == .none) {
return self.fail(node, "missing tuple field with index {}", .{i});
}
}
return self.sema.pt.intern(.{ .aggregate = .{
.ty = res_ty.toIntern(),
.storage = .{ .elems = elems },
} });
}
fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
const gpa = self.sema.gpa;
try res_ty.resolveFields(self.sema.pt);
try res_ty.resolveStructFieldInits(self.sema.pt);
const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?;
const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) {
.struct_literal => |fields| fields,
.empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } },
else => return error.WrongType,
};
const field_values = try self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len);
const field_defaults = struct_info.field_inits.get(ip);
if (field_defaults.len > 0) {
@memcpy(field_values, field_defaults);
} else {
@memset(field_values, .none);
}
for (0..fields.names.len) |i| {
const field_name = try ip.getOrPutString(
gpa,
self.sema.pt.tid,
fields.names[i].get(self.file.zoir.?),
.no_embedded_nulls,
);
const field_node = fields.vals.at(@intCast(i));
const name_index = struct_info.nameIndex(ip, field_name) orelse {
return self.fail(field_node, "unexpected field '{}'", .{field_name.fmt(ip)});
};
const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]);
field_values[name_index] = try self.lowerExpr(field_node, field_type);
if (struct_info.comptime_bits.getBit(ip, name_index)) {
const val = ip.indexToKey(field_values[name_index]);
const default = ip.indexToKey(field_defaults[name_index]);
if (!val.eql(default, ip)) {
return self.fail(
field_node,
"value stored in comptime field does not match the default value of the field",
.{},
);
}
}
}
const field_names = struct_info.field_names.get(ip);
for (field_values, field_names) |*value, name| {
if (value.* == .none) return self.fail(node, "missing field '{}'", .{name.fmt(ip)});
}
return self.sema.pt.intern(.{ .aggregate = .{
.ty = res_ty.toIntern(),
.storage = .{
.elems = field_values,
},
} });
}
fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
const gpa = self.sema.gpa;
const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu);
assert(ptr_info.flags.size == .slice);
// String literals
const string_alignment = ptr_info.flags.alignment == .none or ptr_info.flags.alignment == .@"1";
const string_sentinel = ptr_info.sentinel == .none or ptr_info.sentinel == .zero_u8;
if (string_alignment and ptr_info.child == .u8_type and string_sentinel) {
switch (node.get(self.file.zoir.?)) {
.string_literal => |val| {
const ip_str = try ip.getOrPutString(gpa, self.sema.pt.tid, val, .maybe_embedded_nulls);
const str_ref = try self.sema.addStrLit(ip_str, val.len);
return (try self.sema.coerce(
self.block,
res_ty,
str_ref,
self.nodeSrc(node),
)).toInterned().?;
},
else => {},
}
}
// Slice literals
const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
.array_literal => |nodes| nodes,
.empty_literal => .{ .start = node, .len = 0 },
else => return error.WrongType,
};
const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none));
for (elems, 0..) |*elem, i| {
elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child));
}
if (ptr_info.sentinel != .none) {
elems[elems.len - 1] = ptr_info.sentinel;
}
const array_ty = try self.sema.pt.intern(.{ .array_type = .{
.len = elems.len,
.sentinel = ptr_info.sentinel,
.child = ptr_info.child,
} });
const array = try self.sema.pt.intern(.{ .aggregate = .{
.ty = array_ty,
.storage = .{ .elems = elems },
} });
const many_item_ptr_type = try self.sema.pt.intern(.{ .ptr_type = .{
.child = ptr_info.child,
.sentinel = ptr_info.sentinel,
.flags = b: {
var flags = ptr_info.flags;
flags.size = .many;
break :b flags;
},
.packed_offset = ptr_info.packed_offset,
} });
const many_item_ptr = try self.sema.pt.intern(.{
.ptr = .{
.ty = many_item_ptr_type,
.base_addr = .{
.uav = .{
.orig_ty = (try self.sema.pt.singleConstPtrType(.fromInterned(array_ty))).toIntern(),
.val = array,
},
},
.byte_offset = 0,
},
});
const len = (try self.sema.pt.intValue(.usize, elems.len)).toIntern();
return self.sema.pt.intern(.{ .slice = .{
.ty = res_ty.toIntern(),
.ptr = many_item_ptr,
.len = len,
} });
}
fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
try res_ty.resolveFields(self.sema.pt);
const union_info = self.sema.pt.zcu.typeToUnion(res_ty).?;
const enum_tag_info = union_info.loadTagType(ip);
const field_name, const maybe_field_node = switch (node.get(self.file.zoir.?)) {
.enum_literal => |name| b: {
const field_name = try ip.getOrPutString(
self.sema.gpa,
self.sema.pt.tid,
name.get(self.file.zoir.?),
.no_embedded_nulls,
);
break :b .{ field_name, null };
},
.struct_literal => b: {
const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) {
.struct_literal => |fields| fields,
else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}),
};
if (fields.names.len != 1) {
return error.WrongType;
}
const field_name = try ip.getOrPutString(
self.sema.gpa,
self.sema.pt.tid,
fields.names[0].get(self.file.zoir.?),
.no_embedded_nulls,
);
break :b .{ field_name, fields.vals.at(0) };
},
else => return error.WrongType,
};
const name_index = enum_tag_info.nameIndex(ip, field_name) orelse {
return error.WrongType;
};
const tag = try self.sema.pt.enumValueFieldIndex(.fromInterned(union_info.enum_tag_ty), name_index);
const field_type: Type = .fromInterned(union_info.field_types.get(ip)[name_index]);
const val = if (maybe_field_node) |field_node| b: {
if (field_type.toIntern() == .void_type) {
return self.fail(field_node, "expected type 'void'", .{});
}
break :b try self.lowerExpr(field_node, field_type);
} else b: {
if (field_type.toIntern() != .void_type) {
return error.WrongType;
}
break :b .void_value;
};
return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{
.ty = res_ty.toIntern(),
.tag = tag.toIntern(),
.val = val,
});
}
fn lowerVector(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;
const vector_info = ip.indexToKey(res_ty.toIntern()).vector_type;
const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
.array_literal => |nodes| nodes,
.empty_literal => .{ .start = node, .len = 0 },
else => return error.WrongType,
};
const elems = try self.sema.arena.alloc(InternPool.Index, vector_info.len);
if (elem_nodes.len != vector_info.len) {
return self.fail(
node,
"expected {} vector elements; found {}",
.{ vector_info.len, elem_nodes.len },
);
}
for (elems, 0..) |*elem, i| {
elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(vector_info.child));
}
return self.sema.pt.intern(.{ .aggregate = .{
.ty = res_ty.toIntern(),
.storage = .{ .elems = elems },
} });
}

View file

@ -270,7 +270,7 @@ pub fn getUnsignedIntInner(
else => switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.undef => unreachable,
.int => |int| switch (int.storage) {
.big_int => |big_int| big_int.to(u64) catch null,
.big_int => |big_int| big_int.toInt(u64) catch null,
.u64 => |x| x,
.i64 => |x| std.math.cast(u64, x),
.lazy_align => |ty| (try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), zcu, tid)).scalar.toByteUnits() orelse 0,
@ -311,7 +311,7 @@ pub fn toSignedInt(val: Value, zcu: *const Zcu) i64 {
.bool_true => 1,
else => switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.int => |int| switch (int.storage) {
.big_int => |big_int| big_int.to(i64) catch unreachable,
.big_int => |big_int| big_int.toInt(i64) catch unreachable,
.i64 => |x| x,
.u64 => |x| @intCast(x),
.lazy_align => |ty| @intCast(Type.fromInterned(ty).abiAlignment(zcu).toByteUnits() orelse 0),
@ -898,7 +898,7 @@ pub fn readFromPackedMemory(
pub fn toFloat(val: Value, comptime T: type, zcu: *Zcu) T {
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.int => |int| switch (int.storage) {
.big_int => |big_int| @floatCast(bigIntToFloat(big_int.limbs, big_int.positive)),
.big_int => |big_int| big_int.toFloat(T),
inline .u64, .i64 => |x| {
if (T == f80) {
@panic("TODO we can't lower this properly on non-x86 llvm backend yet");
@ -915,25 +915,6 @@ pub fn toFloat(val: Value, comptime T: type, zcu: *Zcu) T {
};
}
/// TODO move this to std lib big int code
fn bigIntToFloat(limbs: []const std.math.big.Limb, positive: bool) f128 {
if (limbs.len == 0) return 0;
const base = std.math.maxInt(std.math.big.Limb) + 1;
var result: f128 = 0;
var i: usize = limbs.len;
while (i != 0) {
i -= 1;
const limb: f128 = @floatFromInt(limbs[i]);
result = @mulAdd(f128, base, result, limb);
}
if (positive) {
return result;
} else {
return -result;
}
}
pub fn clz(val: Value, ty: Type, zcu: *Zcu) u64 {
var bigint_buf: BigIntSpace = undefined;
const bigint = val.toBigInt(&bigint_buf, zcu);
@ -1548,7 +1529,7 @@ pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptim
.undef => try pt.undefValue(float_ty),
.int => |int| switch (int.storage) {
.big_int => |big_int| {
const float = bigIntToFloat(big_int.limbs, big_int.positive);
const float = big_int.toFloat(f128);
return pt.floatValue(float_ty, float);
},
inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),
@ -4583,7 +4564,7 @@ pub fn interpret(val: Value, comptime T: type, pt: Zcu.PerThread) error{ OutOfMe
.int => switch (ip.indexToKey(val.toIntern()).int.storage) {
.lazy_align, .lazy_size => unreachable, // `val` is fully resolved
inline .u64, .i64 => |x| std.math.cast(T, x) orelse return error.TypeMismatch,
.big_int => |big| big.to(T) catch return error.TypeMismatch,
.big_int => |big| big.toInt(T) catch return error.TypeMismatch,
},
.float => val.toFloat(T, zcu),

View file

@ -39,6 +39,8 @@ const AnalUnit = InternPool.AnalUnit;
const BuiltinFn = std.zig.BuiltinFn;
const LlvmObject = @import("codegen/llvm.zig").Object;
const dev = @import("dev.zig");
const Zoir = std.zig.Zoir;
const ZonGen = std.zig.ZonGen;
comptime {
@setEvalBranchQuota(4000);
@ -672,6 +674,8 @@ pub const File = struct {
tree: Ast,
/// Whether this is populated or not depends on `zir_loaded`.
zir: Zir,
/// Cached Zoir, generated lazily.
zoir: ?Zoir = null,
/// Module that this file is a part of, managed externally.
mod: *Package.Module,
/// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen.
@ -704,7 +708,19 @@ pub const File = struct {
root: *Package.Module,
};
pub fn getMode(self: File) Ast.Mode {
if (std.mem.endsWith(u8, self.sub_file_path, ".zon")) {
return .zon;
} else if (std.mem.endsWith(u8, self.sub_file_path, ".zig")) {
return .zig;
} else {
// `Module.importFile` rejects all other extensions
unreachable;
}
}
pub fn unload(file: *File, gpa: Allocator) void {
if (file.zoir) |zoir| zoir.deinit(gpa);
file.unloadTree(gpa);
file.unloadSource(gpa);
file.unloadZir(gpa);
@ -778,11 +794,24 @@ pub const File = struct {
if (file.tree_loaded) return &file.tree;
const source = try file.getSource(gpa);
file.tree = try Ast.parse(gpa, source.bytes, .zig);
file.tree = try Ast.parse(gpa, source.bytes, file.getMode());
file.tree_loaded = true;
return &file.tree;
}
pub fn getZoir(file: *File, zcu: *Zcu) !*const Zoir {
if (file.zoir) |*zoir| return zoir;
assert(file.tree_loaded);
assert(file.tree.mode == .zon);
file.zoir = try ZonGen.generate(zcu.gpa, file.tree, .{});
if (file.zoir.?.hasCompileErrors()) {
try zcu.failed_files.putNoClobber(zcu.gpa, file, null);
return error.AnalysisFail;
}
return &file.zoir.?;
}
pub fn fullyQualifiedNameLen(file: File) usize {
const ext = std.fs.path.extension(file.sub_file_path);
return file.sub_file_path.len - ext.len;
@ -895,6 +924,7 @@ pub const File = struct {
pub const Index = InternPool.FileIndex;
};
/// Represents the contents of a file loaded with `@embedFile`.
pub const EmbedFile = struct {
/// Module that this file is a part of, managed externally.
owner: *Package.Module,
@ -2372,6 +2402,12 @@ pub const LazySrcLoc = struct {
break :inst .{ info.file, info.inst };
};
const file = zcu.fileByIndex(file_index);
// If we're relative to .main_struct_inst, we know the ast node is the root and don't need to resolve the ZIR,
// which may not exist e.g. in the case of errors in ZON files.
if (zir_inst == .main_struct_inst) return .{ file, 0 };
// Otherwise, make sure ZIR is loaded.
assert(file.zir_loaded);
const zir = file.zir;
@ -3461,8 +3497,6 @@ pub fn atomicPtrAlignment(
}
/// Returns null in the following cases:
/// * `@TypeOf(.{})`
/// * A struct which has no fields (`struct {}`).
/// * Not a struct.
pub fn typeToStruct(zcu: *const Zcu, ty: Type) ?InternPool.LoadedStructType {
if (ty.ip_index == .none) return null;

View file

@ -1867,6 +1867,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
const file = zcu.fileByIndex(file_index);
assert(file.getMode() == .zig);
assert(zcu.fileRootType(file_index) == .none);
if (file.status != .success_zir) {
@ -2022,7 +2023,9 @@ pub fn importFile(
if (mod.deps.get(import_string)) |pkg| {
return pt.importPkg(pkg);
}
if (!std.mem.endsWith(u8, import_string, ".zig")) {
if (!std.mem.endsWith(u8, import_string, ".zig") and
!std.mem.endsWith(u8, import_string, ".zon"))
{
return error.ModuleNotFound;
}
const gpa = zcu.gpa;

View file

@ -13776,8 +13776,8 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
};
const bit_count = extra.type.scalarBits(self);
const val: i64 = if (bit_count <= 64)
bigint.to(i64) catch unreachable
else if (bigint.to(u64)) |val|
bigint.toInt(i64) catch unreachable
else if (bigint.toInt(u64)) |val|
@bitCast(val)
else |_| {
const limbs = try record.addManyAsSlice(
@ -14276,9 +14276,9 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
else => unreachable,
},
};
const val: i64 = if (bigint.to(i64)) |val|
const val: i64 = if (bigint.toInt(i64)) |val|
val
else |_| if (bigint.to(u64)) |val|
else |_| if (bigint.toInt(u64)) |val|
@bitCast(val)
else |_| {
const limbs_len = std.math.divCeil(u32, extra.bit_width, 64) catch unreachable;

View file

@ -120,7 +120,7 @@ pub fn run(
process.exit(2);
}
} else {
const zoir = try std.zig.ZonGen.generate(gpa, tree);
const zoir = try std.zig.ZonGen.generate(gpa, tree, .{});
defer zoir.deinit(gpa);
if (zoir.hasCompileErrors()) {
@ -335,7 +335,7 @@ fn fmtPathFile(
}
},
.zon => {
var zoir = try std.zig.ZonGen.generate(gpa, tree);
var zoir = try std.zig.ZonGen.generate(gpa, tree, .{});
defer zoir.deinit(gpa);
if (zoir.hasCompileErrors()) {

View file

@ -6278,7 +6278,7 @@ fn cmdAstCheck(
}
},
.zon => {
const zoir = try ZonGen.generate(gpa, file.tree);
const zoir = try ZonGen.generate(gpa, file.tree, .{});
defer zoir.deinit(gpa);
if (zoir.hasCompileErrors()) {

View file

@ -488,7 +488,6 @@ const Writer = struct {
.enum_literal,
.decl_ref,
.decl_val,
.import,
.ret_err_value,
.ret_err_value_code,
.param_anytype,
@ -515,6 +514,8 @@ const Writer = struct {
.declaration => try self.writeDeclaration(stream, inst),
.extended => try self.writeExtended(stream, inst),
.import => try self.writeImport(stream, inst),
}
}
@ -2842,4 +2843,13 @@ const Writer = struct {
try stream.writeByte('\n');
}
}
fn writeImport(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
const extra = self.code.extraData(Zir.Inst.Import, inst_data.payload_index).data;
try self.writeInstRef(stream, extra.res_ty);
const import_path = self.code.nullTerminatedString(extra.path);
try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(import_path)});
try self.writeSrcTok(stream, inst_data.src_tok);
}
};

519
test/behavior/zon.zig Normal file
View file

@ -0,0 +1,519 @@
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualDeep = std.testing.expectEqualDeep;
const expectEqualSlices = std.testing.expectEqualSlices;
const expectEqualStrings = std.testing.expectEqualStrings;
test "bool" {
try expectEqual(true, @as(bool, @import("zon/true.zon")));
try expectEqual(false, @as(bool, @import("zon/false.zon")));
}
test "optional" {
const some: ?u32 = @import("zon/some.zon");
const none: ?u32 = @import("zon/none.zon");
const @"null": @TypeOf(null) = @import("zon/none.zon");
try expectEqual(@as(u32, 10), some);
try expectEqual(@as(?u32, null), none);
try expectEqual(null, @"null");
}
test "union" {
// No tag
{
const Union = union {
x: f32,
y: bool,
z: void,
};
const union1: Union = @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
try expectEqual(1.5, union1.x);
try expectEqual(true, union2.y);
try expectEqual({}, union3.z);
}
// Inferred tag
{
const Union = union(enum) {
x: f32,
y: bool,
z: void,
};
const union1: Union = comptime @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
try expectEqual(1.5, union1.x);
try expectEqual(true, union2.y);
try expectEqual({}, union3.z);
}
// Explicit tag
{
const Tag = enum(i128) {
x = -1,
y = 2,
z = 1,
};
const Union = union(Tag) {
x: f32,
y: bool,
z: void,
};
const union1: Union = @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
try expectEqual(1.5, union1.x);
try expectEqual(true, union2.y);
try expectEqual({}, union3.z);
}
}
test "struct" {
const Vec0 = struct {};
const Vec1 = struct { x: f32 };
const Vec2 = struct { x: f32, y: f32 };
const Escaped = struct { @"0": f32, foo: f32 };
try expectEqual(Vec0{}, @as(Vec0, @import("zon/vec0.zon")));
try expectEqual(Vec1{ .x = 1.5 }, @as(Vec1, @import("zon/vec1.zon")));
try expectEqual(Vec2{ .x = 1.5, .y = 2 }, @as(Vec2, @import("zon/vec2.zon")));
try expectEqual(Escaped{ .@"0" = 1.5, .foo = 2 }, @as(Escaped, @import("zon/escaped_struct.zon")));
}
test "struct default fields" {
const Vec3 = struct {
x: f32,
y: f32,
z: f32 = 123.4,
};
try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, @as(Vec3, @import("zon/vec2.zon")));
const ascribed: Vec3 = @import("zon/vec2.zon");
try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, ascribed);
const Vec2 = struct {
x: f32 = 20.0,
y: f32 = 10.0,
};
try expectEqual(Vec2{ .x = 1.5, .y = 2.0 }, @as(Vec2, @import("zon/vec2.zon")));
}
test "struct enum field" {
const Struct = struct {
x: enum { x, y, z },
};
try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon")));
}
test "tuple" {
const Tuple = struct { f32, bool, []const u8, u16 };
try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
}
test "comptime fields" {
// Test setting comptime tuple fields to the correct value
{
const Tuple = struct {
comptime f32 = 1.2,
comptime bool = true,
comptime []const u8 = "hello",
comptime u16 = 3,
};
try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
}
// Test setting comptime struct fields to the correct value
{
const Vec2 = struct {
comptime x: f32 = 1.5,
comptime y: f32 = 2.0,
};
try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/vec2.zon")));
}
// Test allowing comptime tuple fields to be set to their defaults
{
const Tuple = struct {
f32,
bool,
[]const u8,
u16,
comptime u8 = 255,
};
try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
}
// Test allowing comptime struct fields to be set to their defaults
{
const Vec2 = struct {
comptime x: f32 = 1.5,
comptime y: f32 = 2.0,
};
try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/slice-empty.zon")));
}
}
test "char" {
try expectEqual(@as(u8, 'a'), @as(u8, @import("zon/a.zon")));
try expectEqual(@as(u8, 'z'), @as(u8, @import("zon/z.zon")));
}
test "arrays" {
try expectEqual([0]u8{}, @as([0]u8, @import("zon/vec0.zon")));
try expectEqual([0:1]u8{}, @as([0:1]u8, @import("zon/vec0.zon")));
try expectEqual(1, @as([0:1]u8, @import("zon/vec0.zon"))[0]);
try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @as([4]u8, @import("zon/array.zon")));
try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @as([4:2]u8, @import("zon/array.zon")));
try expectEqual(2, @as([4:2]u8, @import("zon/array.zon"))[4]);
}
test "slices, arrays, tuples" {
{
const expected_slice: []const u8 = &.{};
const found_slice: []const u8 = @import("zon/slice-empty.zon");
try expectEqualSlices(u8, expected_slice, found_slice);
const expected_array: [0]u8 = .{};
const found_array: [0]u8 = @import("zon/slice-empty.zon");
try expectEqual(expected_array, found_array);
const T = struct {};
const expected_tuple: T = .{};
const found_tuple: T = @import("zon/slice-empty.zon");
try expectEqual(expected_tuple, found_tuple);
}
{
const expected_slice: []const u8 = &.{1};
const found_slice: []const u8 = @import("zon/slice1_no_newline.zon");
try expectEqualSlices(u8, expected_slice, found_slice);
const expected_array: [1]u8 = .{1};
const found_array: [1]u8 = @import("zon/slice1_no_newline.zon");
try expectEqual(expected_array, found_array);
const T = struct { u8 };
const expected_tuple: T = .{1};
const found_tuple: T = @import("zon/slice1_no_newline.zon");
try expectEqual(expected_tuple, found_tuple);
}
{
const expected_slice: []const u8 = &.{ 'a', 'b', 'c' };
const found_slice: []const u8 = @import("zon/slice-abc.zon");
try expectEqualSlices(u8, expected_slice, found_slice);
const expected_array: [3]u8 = .{ 'a', 'b', 'c' };
const found_array: [3]u8 = @import("zon/slice-abc.zon");
try expectEqual(expected_array, found_array);
const T = struct { u8, u8, u8 };
const expected_tuple: T = .{ 'a', 'b', 'c' };
const found_tuple: T = @import("zon/slice-abc.zon");
try expectEqual(expected_tuple, found_tuple);
}
}
test "string literals" {
try expectEqualSlices(u8, "abc", @import("zon/abc.zon"));
try expectEqualSlices(u8, "ab\\c", @import("zon/abc-escaped.zon"));
const zero_terminated: [:0]const u8 = @import("zon/abc.zon");
try expectEqualDeep(zero_terminated, "abc");
try expectEqual(0, zero_terminated[zero_terminated.len]);
try expectEqualStrings(
\\Hello, world!
\\This is a multiline string!
\\ There are no escapes, we can, for example, include \n in the string
, @import("zon/multiline_string.zon"));
try expectEqualStrings("a\nb\x00c", @import("zon/string_embedded_null.zon"));
}
test "enum literals" {
const Enum = enum {
foo,
bar,
baz,
@"0\na",
};
try expectEqual(Enum.foo, @as(Enum, @import("zon/foo.zon")));
try expectEqual(.foo, @as(@TypeOf(.foo), @import("zon/foo.zon")));
try expectEqual(Enum.@"0\na", @as(Enum, @import("zon/escaped_enum.zon")));
}
test "int" {
const T = struct {
u8,
i16,
i14,
i32,
i8,
i8,
u8,
u8,
u65,
u65,
i128,
i128,
i66,
i66,
i8,
i8,
i16,
i16,
i16,
i16,
i16,
i16,
u65,
i66,
i66,
u65,
i66,
i66,
u65,
i66,
i66,
};
const expected: T = .{
// Test various numbers and types
10,
24,
-4,
-123,
// Test limits
127,
-128,
// Test characters
'a',
'z',
// Test big integers
36893488147419103231,
36893488147419103231,
-18446744073709551615, // Only a big int due to negation
-9223372036854775809, // Only a big int due to negation
// Test big integer limits
36893488147419103231,
-36893488147419103232,
// Test parsing whole number floats as integers
-1,
123,
// Test non-decimal integers
0xff,
-0xff,
0o77,
-0o77,
0b11,
-0b11,
// Test non-decimal big integers
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
};
const actual: T = @import("zon/ints.zon");
try expectEqual(expected, actual);
}
test "floats" {
const T = struct {
f16,
f32,
f64,
f128,
f16,
f16,
f32,
f32,
f32,
f32,
f32,
f32,
f128,
f32,
f32,
f32,
f32,
f32,
};
const expected: T = .{
// Test decimals
0.5,
123.456,
-123.456,
42.5,
// Test whole numbers with and without decimals
5.0,
5.0,
-102,
-102,
// Test characters and negated characters
'a',
'z',
// Test big integers
36893488147419103231,
-36893488147419103231,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
// Exponents, underscores
123.0E+77,
// Hexadecimal
0x103.70p-5,
-0x103.70,
0x1234_5678.9ABC_CDEFp-10,
};
const actual: T = @import("zon/floats.zon");
try expectEqual(expected, actual);
}
test "inf and nan" {
// f32
{
const actual: struct { f32, f32, f32 } = @import("zon/inf_and_nan.zon");
try expect(std.math.isNan(actual[0]));
try expect(std.math.isPositiveInf(actual[1]));
try expect(std.math.isNegativeInf(actual[2]));
}
// f128
{
const actual: struct { f128, f128, f128 } = @import("zon/inf_and_nan.zon");
try expect(std.math.isNan(actual[0]));
try expect(std.math.isPositiveInf(actual[1]));
try expect(std.math.isNegativeInf(actual[2]));
}
}
test "vector" {
{
const actual: @Vector(0, bool) = @import("zon/vec0.zon");
const expected: @Vector(0, bool) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, bool) = @import("zon/vec3_bool.zon");
const expected: @Vector(3, bool) = .{ false, false, true };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, f32) = @import("zon/vec0.zon");
const expected: @Vector(0, f32) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, f32) = @import("zon/vec3_float.zon");
const expected: @Vector(3, f32) = .{ 1.5, 2.5, 3.5 };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, u8) = @import("zon/vec0.zon");
const expected: @Vector(0, u8) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, u8) = @import("zon/vec3_int.zon");
const expected: @Vector(3, u8) = .{ 2, 4, 6 };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, *const u8) = @import("zon/vec0.zon");
const expected: @Vector(0, *const u8) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, *const u8) = @import("zon/vec3_int.zon");
const expected: @Vector(3, *const u8) = .{ &2, &4, &6 };
try expectEqual(expected, actual);
}
{
const actual: @Vector(0, ?*const u8) = @import("zon/vec0.zon");
const expected: @Vector(0, ?*const u8) = .{};
try expectEqual(expected, actual);
}
{
const actual: @Vector(3, ?*const u8) = @import("zon/vec3_int_opt.zon");
const expected: @Vector(3, ?*const u8) = .{ &2, null, &6 };
try expectEqual(expected, actual);
}
}
test "pointers" {
// Primitive with varying levels of pointers
try expectEqual(@as(u8, 'a'), @as(*const u8, @import("zon/a.zon")).*);
try expectEqual(@as(u8, 'a'), @as(*const *const u8, @import("zon/a.zon")).*.*);
try expectEqual(@as(u8, 'a'), @as(*const *const *const u8, @import("zon/a.zon")).*.*.*);
// Primitive optional with varying levels of pointers
try expectEqual(@as(u8, 'a'), @as(?*const u8, @import("zon/a.zon")).?.*);
try expectEqual(null, @as(?*const u8, @import("zon/none.zon")));
try expectEqual(@as(u8, 'a'), @as(*const ?u8, @import("zon/a.zon")).*.?);
try expectEqual(null, @as(*const ?u8, @import("zon/none.zon")).*);
try expectEqual(@as(u8, 'a'), @as(?*const *const u8, @import("zon/a.zon")).?.*.*);
try expectEqual(null, @as(?*const *const u8, @import("zon/none.zon")));
try expectEqual(@as(u8, 'a'), @as(*const ?*const u8, @import("zon/a.zon")).*.?.*);
try expectEqual(null, @as(*const ?*const u8, @import("zon/none.zon")).*);
try expectEqual(@as(u8, 'a'), @as(*const *const ?u8, @import("zon/a.zon")).*.*.?);
try expectEqual(null, @as(*const *const ?u8, @import("zon/none.zon")).*.*);
try expectEqual([3]u8{ 2, 4, 6 }, @as(*const [3]u8, @import("zon/vec3_int.zon")).*);
// A complicated type with nested internal pointers and string allocations
{
const Inner = struct {
f1: *const ?*const []const u8,
f2: *const ?*const []const u8,
};
const Outer = struct {
f1: *const ?*const Inner,
f2: *const ?*const Inner,
};
const expected: Outer = .{
.f1 = &&.{
.f1 = &null,
.f2 = &&"foo",
},
.f2 = &null,
};
const found: ?*const Outer = @import("zon/complex.zon");
try std.testing.expectEqualDeep(expected, found.?.*);
}
}
test "recursive" {
const Recursive = struct { foo: ?*const @This() };
const expected: Recursive = .{ .foo = &.{ .foo = null } };
try expectEqualDeep(expected, @as(Recursive, @import("zon/recursive.zon")));
}

1
test/behavior/zon/a.zon Normal file
View file

@ -0,0 +1 @@
'a'

View file

@ -0,0 +1 @@
"ab\\c"

View file

@ -0,0 +1 @@
"abc"

View file

@ -0,0 +1 @@
.{ 'a', 'b', 'c', 'd' }

View file

@ -0,0 +1,7 @@
.{
.f1 = .{
.f1 = null,
.f2 = "foo",
},
.f2 = null,
}

View file

@ -0,0 +1 @@
.{ .x = .z }

View file

@ -0,0 +1 @@
.@"0\na"

View file

@ -0,0 +1,2 @@
.{ .@"0" = 1.5, .@"foo" = 2 }

View file

@ -0,0 +1,4 @@
// Comment
false // Another comment
// Yet another comment

View file

@ -0,0 +1,25 @@
.{
0.5,
123.456,
-123.456,
42.5,
5.0,
5,
-102.0,
-102,
'a',
'z',
36893488147419103231,
-36893488147419103231,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
12_3.0E+77,
0x103.70p-5,
-0x103.70,
0x1234_5678.9ABC_CDEFp-10,
}

View file

@ -0,0 +1 @@
.foo

View file

@ -0,0 +1,5 @@
.{
nan,
inf,
-inf,
}

View file

@ -0,0 +1,40 @@
.{
10,
24,
-4,
-123,
127,
-128,
'a',
'z',
36893488147419103231,
368934_881_474191032_31,
-18446744073709551615,
-9223372036854775809,
36893488147419103231,
-36893488147419103232,
-1.0,
123.0,
0xff,
-0xff,
0o77,
-0o77,
0b11,
-0b11,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
0o3777777777777777777777,
0o3777777777777777777777,
-0o3777777777777777777777,
0b11111111111111111111111111111111111111111111111111111111111111111,
0b11111111111111111111111111111111111111111111111111111111111111111,
-0b11111111111111111111111111111111111111111111111111111111111111111,
}

View file

@ -0,0 +1,4 @@
// zig fmt: off
\\Hello, world!
\\This is a multiline string!
\\ There are no escapes, we can, for example, include \n in the string

View file

@ -0,0 +1 @@
null

View file

@ -0,0 +1 @@
.{ .foo = .{ .foo = null } }

View file

@ -0,0 +1 @@
.{'a', 'b', 'c'}

View file

@ -0,0 +1 @@
.{}

View file

@ -0,0 +1 @@
.{ 1 }

View file

@ -0,0 +1 @@
10

View file

@ -0,0 +1 @@
"a\nb\x00c"

View file

@ -0,0 +1 @@
true

View file

@ -0,0 +1 @@
.{ 1.2, true, "hello", 3 }

View file

@ -0,0 +1 @@
.{ .x = 1.5 }

View file

@ -0,0 +1 @@
.{ .y = true }

View file

@ -0,0 +1 @@
.z

View file

@ -0,0 +1 @@
.{}

View file

@ -0,0 +1 @@
.{ .x = 1.5 }

View file

@ -0,0 +1 @@
.{ .x = 1.5, .y = 2 }

View file

@ -0,0 +1 @@
.{ false, false, true }

View file

@ -0,0 +1 @@
.{ 1.5, 2.5, 3.5 }

View file

@ -0,0 +1 @@
.{ 2, 4, 6 }

View file

@ -0,0 +1 @@
.{ 2, null, 6 }

1
test/behavior/zon/z.zon Normal file
View file

@ -0,0 +1 @@
'z'

View file

@ -0,0 +1,9 @@
pub fn main() void {
const f: struct { value: []const i32 } = @import("zon/addr_slice.zon");
_ = f;
}
// error
// imports=zon/addr_slice.zon
//
// addr_slice.zon:2:14: error: pointers are not available in ZON

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: [4]u8 = @import("zon/array.zon");
_ = f;
}
// error
// imports=zon/array.zon
//
// array.zon:1:2: error: expected type '[4]u8'
// tmp.zig:2:30: note: imported here

View file

@ -0,0 +1,9 @@
export fn entry() void {
_ = @import(
"bogus-does-not-exist.zon",
);
}
// error
//
// :3:9: error: unable to open 'bogus-does-not-exist.zon': FileNotFound

View file

@ -0,0 +1,125 @@
export fn testVoid() void {
const f: void = @import("zon/neg_inf.zon");
_ = f;
}
export fn testInStruct() void {
const f: struct { f: [*]const u8 } = @import("zon/neg_inf.zon");
_ = f;
}
export fn testError() void {
const f: struct { error{foo} } = @import("zon/neg_inf.zon");
_ = f;
}
export fn testInUnion() void {
const f: union(enum) { a: void, b: [*c]const u8 } = @import("zon/neg_inf.zon");
_ = f;
}
export fn testInVector() void {
const f: @Vector(0, [*c]const u8) = @import("zon/neg_inf.zon");
_ = f;
}
export fn testInOpt() void {
const f: *const ?[*c]const u8 = @import("zon/neg_inf.zon");
_ = f;
}
export fn testComptimeField() void {
const f: struct { comptime foo: ??u8 = null } = @import("zon/neg_inf.zon");
_ = f;
}
export fn testEnumLiteral() void {
const f: @TypeOf(.foo) = @import("zon/neg_inf.zon");
_ = f;
}
export fn testNestedOpt1() void {
const f: ??u8 = @import("zon/neg_inf.zon");
_ = f;
}
export fn testNestedOpt2() void {
const f: ?*const ?u8 = @import("zon/neg_inf.zon");
_ = f;
}
export fn testNestedOpt3() void {
const f: *const ?*const ?*const u8 = @import("zon/neg_inf.zon");
_ = f;
}
export fn testOpt() void {
const f: ?u8 = @import("zon/neg_inf.zon");
_ = f;
}
export fn testNonExhaustiveEnum() void {
const f: enum(u8) { _ } = @import("zon/neg_inf.zon");
_ = f;
}
export fn testUntaggedUnion() void {
const f: union { foo: void } = @import("zon/neg_inf.zon");
_ = f;
}
export fn testTaggedUnionVoid() void {
const f: union(enum) { foo: void } = @import("zon/neg_inf.zon");
_ = f;
}
export fn testVisited() void {
const V = struct {
?f32, // Adds `?f32` to the visited list
??f32, // `?f32` is already visited, we need to detect the nested opt anyway
f32,
};
const f: V = @import("zon/neg_inf.zon");
_ = f;
}
export fn testMutablePointer() void {
const f: *i32 = @import("zon/neg_inf.zon");
_ = f;
}
// error
// imports=zon/neg_inf.zon
//
// tmp.zig:2:29: error: type 'void' is not available in ZON
// tmp.zig:7:50: error: type '[*]const u8' is not available in ZON
// tmp.zig:7:50: note: ZON does not allow many-pointers
// tmp.zig:12:46: error: type 'error{foo}' is not available in ZON
// tmp.zig:17:65: error: type '[*c]const u8' is not available in ZON
// tmp.zig:17:65: note: ZON does not allow C pointers
// tmp.zig:22:49: error: type '[*c]const u8' is not available in ZON
// tmp.zig:22:49: note: ZON does not allow C pointers
// tmp.zig:27:45: error: type '[*c]const u8' is not available in ZON
// tmp.zig:27:45: note: ZON does not allow C pointers
// tmp.zig:32:61: error: type '??u8' is not available in ZON
// tmp.zig:32:61: note: ZON does not allow nested optionals
// tmp.zig:42:29: error: type '??u8' is not available in ZON
// tmp.zig:42:29: note: ZON does not allow nested optionals
// tmp.zig:47:36: error: type '?*const ?u8' is not available in ZON
// tmp.zig:47:36: note: ZON does not allow nested optionals
// tmp.zig:52:50: error: type '?*const ?*const u8' is not available in ZON
// tmp.zig:52:50: note: ZON does not allow nested optionals
// tmp.zig:82:26: error: type '??f32' is not available in ZON
// tmp.zig:82:26: note: ZON does not allow nested optionals
// tmp.zig:87:29: error: type '*i32' is not available in ZON
// tmp.zig:87:29: note: ZON does not allow mutable pointers
// neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)'
// tmp.zig:37:38: note: imported here
// neg_inf.zon:1:1: error: expected type '?u8'
// tmp.zig:57:28: note: imported here
// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_490'
// tmp.zig:62:39: note: imported here
// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_492'
// tmp.zig:67:44: note: imported here
// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_495'
// tmp.zig:72:50: note: imported here

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: comptime_float = @import("zon/inf.zon");
_ = f;
}
// error
// imports=zon/inf.zon
//
// inf.zon:1:1: error: expected type 'comptime_float'
// tmp.zig:2:39: note: imported here

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: comptime_float = @import("zon/nan.zon");
_ = f;
}
// error
// imports=zon/nan.zon
//
// nan.zon:1:1: error: expected type 'comptime_float'
// tmp.zig:2:39: note: imported here

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: comptime_float = @import("zon/neg_inf.zon");
_ = f;
}
// error
// imports=zon/neg_inf.zon
//
// neg_inf.zon:1:1: error: expected type 'comptime_float'
// tmp.zig:2:39: note: imported here

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: struct { foo: type } = @import("zon/doc_comment.zon");
_ = f;
}
// error
// imports=zon/doc_comment.zon
//
// doc_comment.zon:1:1: error: expected expression, found 'a document comment'

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: f32 = @import("zon/double_negation_float.zon");
_ = f;
}
// error
// imports=zon/double_negation_float.zon
//
// double_negation_float.zon:1:1: error: expected number or 'inf' after '-'

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: i32 = @import("zon/double_negation_int.zon");
_ = f;
}
// error
// imports=zon/double_negation_int.zon
//
// double_negation_int.zon:1:1: error: expected number or 'inf' after '-'

View file

@ -0,0 +1,11 @@
const std = @import("std");
export fn entry() void {
const E = enum { foo };
const f: struct { E, E } = @import("zon/enum_embedded_null.zon");
_ = f;
}
// error
// imports=zon/enum_embedded_null.zon
//
// enum_embedded_null.zon:2:6: error: identifier cannot contain null bytes

View file

@ -0,0 +1,11 @@
export fn entry() void {
const U = union(enum) { a: void };
const f: U = @import("zon/simple_union.zon");
_ = f;
}
// error
// imports=zon/simple_union.zon
//
// simple_union.zon:1:9: error: expected type 'void'
// tmp.zig:3:26: note: imported here

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: u8 = @import("zon/invalid_character.zon");
_ = f;
}
// error
// imports=zon/invalid_character.zon
//
// invalid_character.zon:1:3: error: invalid escape character: 'a'

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: u128 = @import("zon/invalid_number.zon");
_ = f;
}
// error
// imports=zon/invalid_number.zon
//
// invalid_number.zon:1:19: error: invalid digit 'a' for decimal base

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: []const u8 = @import("zon/invalid_string.zon");
_ = f;
}
// error
// imports=zon/invalid_string.zon
//
// invalid_string.zon:1:5: error: invalid escape character: 'a'

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: u128 = @import("zon/leading_zero_in_integer.zon");
_ = f;
}
// error
// imports=zon/leading_zero_in_integer.zon
//
// leading_zero_in_integer.zon:1:1: error: number '0012' has leading zero
// leading_zero_in_integer.zon:1:1: note: use '0o' prefix for octal literals

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: u8 = @import("zon/neg_char.zon");
_ = f;
}
// error
// imports=zon/neg_char.zon
//
// neg_char.zon:1:1: error: expected number or 'inf' after '-'

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: u8 = @import("zon/neg_nan.zon");
_ = f;
}
// error
// imports=zon/neg_nan.zon
//
// neg_nan.zon:1:1: error: expected number or 'inf' after '-'

View file

@ -0,0 +1,11 @@
export fn entry() void {
const f: i8 = @import("zon/negative_zero.zon");
_ = f;
}
// error
// imports=zon/negative_zero.zon
//
// negative_zero.zon:1:2: error: integer literal '-0' is ambiguous
// negative_zero.zon:1:2: note: use '0' for an integer zero
// negative_zero.zon:1:2: note: use '-0.0' for a floating-point signed zero

View file

@ -0,0 +1,9 @@
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

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: i66 = @import("zon/large_number.zon");
_ = f;
}
// error
// imports=zon/large_number.zon
//
// large_number.zon:1:1: error: type 'i66' cannot represent integer value '36893488147419103232'
// tmp.zig:2:28: note: imported here

View file

@ -0,0 +1,16 @@
export fn entry() void {
{
const f: u6 = @import("zon/char_32.zon");
_ = f;
}
{
const f: u5 = @import("zon/char_32.zon");
_ = f;
}
}
// error
// imports=zon/char_32.zon
//
// char_32.zon:1:1: error: type 'u5' cannot represent integer value '32'
// tmp.zig:7:31: note: imported here

View file

@ -0,0 +1,16 @@
export fn entry() void {
{
const f: i7 = @import("zon/char_32.zon");
_ = f;
}
{
const f: i6 = @import("zon/char_32.zon");
_ = f;
}
}
// error
// imports=zon/char_32.zon
//
// char_32.zon:1:1: error: type 'i6' cannot represent integer value '32'
// tmp.zig:7:31: note: imported here

View file

@ -0,0 +1,16 @@
export fn entry() void {
{
const f: u6 = @import("zon/int_32.zon");
_ = f;
}
{
const f: u5 = @import("zon/int_32.zon");
_ = f;
}
}
// error
// imports=zon/int_32.zon
//
// int_32.zon:1:1: error: type 'u5' cannot represent integer value '32'
// tmp.zig:7:31: note: imported here

View file

@ -0,0 +1,16 @@
export fn entry() void {
{
const f: i7 = @import("zon/int_32.zon");
_ = f;
}
{
const f: i6 = @import("zon/int_32.zon");
_ = f;
}
}
// error
// imports=zon/int_32.zon
//
// int_32.zon:1:1: error: type 'i6' cannot represent integer value '32'
// tmp.zig:7:31: note: imported here

View file

@ -0,0 +1,16 @@
export fn entry() void {
{
const f: i7 = @import("zon/int_neg_33.zon");
_ = f;
}
{
const f: i6 = @import("zon/int_neg_33.zon");
_ = f;
}
}
// error
// imports=zon/int_neg_33.zon
//
// int_neg_33.zon:1:1: error: type 'i6' cannot represent integer value '-33'
// tmp.zig:7:31: note: imported here

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: u64 = @import("zon/int_neg_33.zon");
_ = f;
}
// error
// imports=zon/int_neg_33.zon
//
// int_neg_33.zon:1:1: error: type 'u64' cannot represent integer value '-33'
// tmp.zig:2:28: note: imported here

View file

@ -0,0 +1,82 @@
export fn testFloatA() void {
const f: ?f32 = @import("zon/vec2.zon");
_ = f;
}
export fn testFloatB() void {
const f: *const ?f32 = @import("zon/vec2.zon");
_ = f;
}
export fn testFloatC() void {
const f: ?*const f32 = @import("zon/vec2.zon");
_ = f;
}
export fn testBool() void {
const f: ?bool = @import("zon/vec2.zon");
_ = f;
}
export fn testInt() void {
const f: ?i32 = @import("zon/vec2.zon");
_ = f;
}
const Enum = enum { foo };
export fn testEnum() void {
const f: ?Enum = @import("zon/vec2.zon");
_ = f;
}
export fn testEnumLit() void {
const f: ?@TypeOf(.foo) = @import("zon/vec2.zon");
_ = f;
}
export fn testArray() void {
const f: ?[1]u8 = @import("zon/vec2.zon");
_ = f;
}
const Union = union {};
export fn testUnion() void {
const f: ?Union = @import("zon/vec2.zon");
_ = f;
}
export fn testSlice() void {
const f: ?[]const u8 = @import("zon/vec2.zon");
_ = f;
}
export fn testVector() void {
const f: ?@Vector(3, f32) = @import("zon/vec2.zon");
_ = f;
}
// error
// imports=zon/vec2.zon
//
// vec2.zon:1:2: error: expected type '?f32'
// tmp.zig:2:29: note: imported here
// vec2.zon:1:2: error: expected type '*const ?f32'
// tmp.zig:7:36: note: imported here
// vec2.zon:1:2: error: expected type '?*const f32'
// tmp.zig:12:36: note: imported here
// vec2.zon:1:2: error: expected type '?bool'
// tmp.zig:17:30: note: imported here
// vec2.zon:1:2: error: expected type '?i32'
// tmp.zig:22:29: note: imported here
// vec2.zon:1:2: error: expected type '?tmp.Enum'
// tmp.zig:28:30: note: imported here
// vec2.zon:1:2: error: expected type '?@Type(.enum_literal)'
// tmp.zig:33:39: note: imported here
// vec2.zon:1:2: error: expected type '?[1]u8'
// tmp.zig:38:31: note: imported here
// vec2.zon:1:2: error: expected type '?tmp.Union'
// tmp.zig:44:31: note: imported here
// vec2.zon:1:2: error: expected type '?[]const u8'
// tmp.zig:49:36: note: imported here
// vec2.zon:1:2: error: expected type '?@Vector(3, f32)'
// tmp.zig:54:41: note: imported here

View file

@ -0,0 +1,19 @@
const Struct = struct { f: bool };
export fn testStruct() void {
const f: ?Struct = @import("zon/nan.zon");
_ = f;
}
const Tuple = struct { bool };
export fn testTuple() void {
const f: ?Tuple = @import("zon/nan.zon");
_ = f;
}
// error
// imports=zon/nan.zon
//
//nan.zon:1:1: error: expected type '?tmp.Struct'
//tmp.zig:3:32: note: imported here
//nan.zon:1:1: error: expected type '?struct { bool }'
//tmp.zig:9:31: note: imported here

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: [5]u8 = @import("zon/hello.zon");
_ = f;
}
// error
// imports=zon/hello.zon
//
// hello.zon:1:1: error: expected type '[5]u8'
// tmp.zig:2:30: note: imported here

View file

@ -0,0 +1,11 @@
const std = @import("std");
export fn entry() void {
const f: struct { name: u8 } = @import("zon/struct_dup_field.zon");
_ = f;
}
// error
// imports=zon/struct_dup_field.zon
//
// struct_dup_field.zon:2:6: error: duplicate struct field name
// struct_dup_field.zon:3:6: note: duplicate name here

View file

@ -0,0 +1,14 @@
export fn entry() void {
const Vec2 = struct {
comptime x: f32 = 1.5,
comptime y: f32 = 2.5,
};
const f: Vec2 = @import("zon/vec2.zon");
_ = f;
}
// error
// imports=zon/vec2.zon
//
// vec2.zon:1:19: error: value stored in comptime field does not match the default value of the field
// tmp.zig:6:29: note: imported here

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: bool = @import("zon/syntax_error.zon");
_ = f;
}
// error
// imports=zon/syntax_error.zon
//
// syntax_error.zon:3:13: error: expected ',' after initializer

View file

@ -0,0 +1,14 @@
export fn entry() void {
const T = struct {
comptime f32 = 1.5,
comptime f32 = 2.5,
};
const f: T = @import("zon/tuple.zon");
_ = f;
}
// error
// imports=zon/tuple.zon
//
// tuple.zon:1:9: error: value stored in comptime field does not match the default value of the field
// tmp.zig:6:26: note: imported here

View file

@ -0,0 +1,9 @@
export fn entry() void {
const f: struct { foo: type } = @import("zon/type_decl.zon");
_ = f;
}
// error
// imports=zon/type_decl.zon
//
// type_decl.zon:2:12: error: types are not available in ZON

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: [3]i32 = @import("zon/type_expr_array.zon");
_ = f;
}
// error
// imports=zon/type_expr_array.zon
//
// type_expr_array.zon:1:1: error: types are not available in ZON
// type_expr_array.zon:1:1: note: replace the type with '.'

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: i32 = @import("zon/type_expr_fn.zon");
_ = f;
}
// error
// imports=zon/type_expr_fn.zon
//
// type_expr_fn.zon:1:1: error: types are not available in ZON
// type_expr_fn.zon:1:1: note: replace the type with '.'

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: struct { x: f32, y: f32 } = @import("zon/type_expr_struct.zon");
_ = f;
}
// error
// imports=zon/type_expr_struct.zon
//
// type_expr_struct.zon:1:1: error: types are not available in ZON
// type_expr_struct.zon:1:1: note: replace the type with '.'

View file

@ -0,0 +1,10 @@
export fn entry() void {
const f: struct { f32, f32 } = @import("zon/type_expr_tuple.zon");
_ = f;
}
// error
// imports=zon/type_expr_tuple.zon
//
// type_expr_tuple.zon:1:1: error: types are not available in ZON
// type_expr_tuple.zon:1:1: note: replace the type with '.'

Some files were not shown because too many files have changed in this diff Show more