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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 92 additions & 29 deletions lib/std/zon/parse.zig
Original file line number Diff line number Diff line change
Expand Up @@ -665,8 +665,26 @@ const Parser = struct {
}
}

fn parseDeclLiteral(self: @This(), T: type, nodeidx: Zoir.Node.Index, node: ?Zoir.Node) !T {
const real_node = node orelse nodeidx.get(self.zoir);
_ = real_node == .enum_literal or return error.WrongType;

const target_name = real_node.enum_literal.get(self.zoir);
const T_decls = switch (@typeInfo(T)) {
inline .@"struct", .@"union", .@"enum" => |e| e.decls,
else => return error.WrongType,
};

inline for (T_decls) |decl| {
if (@TypeOf(@field(T, decl.name)) != T) continue;
if (std.mem.eql(u8, target_name, decl.name))
return @field(T, decl.name);
}
Comment on lines +672 to +682
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second glance, this might need some more in-depth work because of pointer decl literals.

Decl literals also work for single-item pointers to namespaced-types. For instance,

const MyType = struct {
  const value_decl: MyType = .{};
  const pointer_decl: *const MyType = &.value_decl;
};

comptime {
  // This works for pointers to a container
  const ptr: *const MyType = .pointer_decl;
  
  // It also works when pointers to a container have stricter qualifiers than the actual declaration, so coercion might occur
  const coerced_ptr: *const volatile MyType = .pointer_decl;
    
  @compileLog(ptr, coerced_ptr);
}

So for pointer types, we would have to to a more complicated check to first, see if it is a single-item pointer to a container type (including opaque types), then second, check for declarations which can coerce into said pointer type, which itself would require some reflection rather than a simple T == U check.

Then there's also the matter of packed pointers, which are currently impossible to differentiate via reflection, so they would unfortunately have to be a known issue with std.zon until language support is added (I'm holding out for #24061!)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this also apply to optionals?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, this does not. I'll get back to you in a minute after testing that, though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was dead wrong! The following does work:

const MyStruct = struct {
    const val_decl: MyStruct = .{};
    const ptr_decl: *const MyStruct = &.val_decl;

    // Optionals can coerce from decl literals of their child types
    const opt_val_decl: ?MyStruct = .val_decl;
    const opt_ptr_decl: ?*const MyStruct = .ptr_decl;

    comptime {
        // Additionally, optionals can be decl literals in and of themselves
        const x: ?MyStruct = .opt_val_decl;
        const y: ?*const MyStruct = .opt_ptr_decl;
        @compileLog(x, y);
    }

    // This even applies to nested optionals
    // Thankfully we don't have to worry about that for ZON though.
    const opt_opt_val_decl: ??MyStruct = .opt_val_decl;
    const opt_opt_ptr_decl: ?*const ?*const MyStruct = &.opt_ptr_decl;
    comptime {
        const x: ??MyStruct = .opt_opt_val_decl;
        const y: ?*const ?*const MyStruct = .opt_opt_ptr_decl;
        @compileLog(x, y);
    }

    const V = @Vector(3, ?*const MyStruct);

    // Error unions too!
    // Suffice to say, this is more in-depth than I thought.
    const err_opt_val_decl: anyerror!?MyStruct = error.Foo;
    comptime {
        const x: anyerror!?MyStruct = .err_opt_val_decl;
        @compileLog(x);
    }
};
comptime {
    _ = MyStruct;
}

There may be examples of even more stuff I'm not aware of, but I'm going to take a bit to read up on how decl literals are coerced and hopefully get back to you with a code example of how one might handle this.

return error.NotFound;
}
fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T {
switch (node.get(self.zoir)) {
const repr = node.get(self.zoir);
switch (repr) {
.enum_literal => |field_name| {
// Create a comptime string map for the enum fields
const enum_fields = @typeInfo(T).@"enum".fields;
Expand All @@ -678,8 +696,14 @@ const Parser = struct {

// Get the tag if it exists
const field_name_str = field_name.get(self.zoir);
return enum_tags.get(field_name_str) orelse
self.failUnexpected(T, "enum literal", node, null, field_name_str);
if (enum_tags.get(field_name_str)) |tag|
return tag
else if (self.parseDeclLiteral(T, node, repr)) |t|
return t
else |err| switch (err) {
error.NotFound => return self.failUnexpected(T, "enum literal", node, null, field_name_str),
else => |e| return e,
}
},
else => return error.WrongType,
}
Expand Down Expand Up @@ -805,6 +829,14 @@ const Parser = struct {
const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) {
.struct_literal => |nodes| nodes,
.empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } },
.enum_literal => |_| {
return self.parseDeclLiteral(T, node, repr) catch |err| switch (err) {
error.NotFound => return self.failNode(node, "expected decl literal"),
else => |e| {
return e;
},
};
},
else => return error.WrongType,
};

Expand Down Expand Up @@ -953,32 +985,34 @@ const Parser = struct {
};

// Parse the union
switch (node.get(self.zoir)) {
const repr = node.get(self.zoir);
switch (repr) {
.enum_literal => |field_name| {
// The union must be tagged for an enum literal to coerce to it
if (@"union".tag_type == null) {
return error.WrongType;
}

const field_name_str = field_name.get(self.zoir);
// Get the index of the named field. We don't use `parseEnum` here as
// the order of the enum and the order of the union might not match!
const field_index = b: {
const field_name_str = field_name.get(self.zoir);
break :b field_indices.get(field_name_str) orelse
return self.failUnexpected(T, "field", node, null, field_name_str);
};

// Initialize the union from the given field.
switch (field_index) {
inline 0...field_infos.len - 1 => |i| {
// Fail if the field is not void
if (field_infos[i].type != void)
return self.failNode(node, "expected union");

// Instantiate the union
return @unionInit(T, field_infos[i].name, {});
},
else => unreachable, // Can't be out of bounds
if (field_indices.get(field_name_str)) |field_index| {
// The union must be tagged for an enum literal to coerce to it
if (@"union".tag_type == null) {
return error.WrongType;
}
// Initialize the union from the given field.
switch (field_index) {
inline 0...field_infos.len - 1 => |i| {
// Fail if the field is not void
if (field_infos[i].type != void)
return self.failNode(node, "expected union");

// Instantiate the union
return @unionInit(T, field_infos[i].name, {});
},
else => unreachable, // Can't be out of bounds
}
} else if (self.parseDeclLiteral(T, node, repr)) |t| {
return t;
} else |err| switch (err) {
error.NotFound => return self.failUnexpected(T, "field", node, null, field_name_str),
else => |e| return e,
}
},
.struct_literal => |struct_fields| {
Expand Down Expand Up @@ -1413,8 +1447,20 @@ test "std.zon unions" {

// Unions
{
const Tagged = union(enum) { x: f32, @"y y": bool, z, @"z z" };
const Untagged = union { x: f32, @"y y": bool, z: void, @"z z": void };
const Tagged = union(enum) {
pub const LiteralXVal: @This() = .{ .x = 1.1 };
x: f32,
@"y y": bool,
z,
@"z z",
};
const Untagged = union {
pub const LiteralXVal: @This() = .{ .x = 1.1 };
x: f32,
@"y y": bool,
z: void,
@"z z": void,
};

const tagged_x = try fromSlice(Tagged, gpa, ".{.x = 1.5}", null, .{});
try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x);
Expand All @@ -1424,11 +1470,15 @@ test "std.zon unions" {
try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand);
const tagged_zz_shorthand = try fromSlice(Tagged, gpa, ".@\"z z\"", null, .{});
try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand);
const tagged_x_decllit = try fromSlice(Tagged, gpa, ".LiteralXVal", null, .{});
try std.testing.expectEqual(@as(Tagged, .LiteralXVal), tagged_x_decllit);

const untagged_x = try fromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{});
try std.testing.expect(untagged_x.x == 1.5);
const untagged_y = try fromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{});
try std.testing.expect(untagged_y.@"y y");
const untagged_x_decllit = try fromSlice(Untagged, gpa, ".LiteralXVal", null, .{});
try std.testing.expectEqual(@as(Untagged, .LiteralXVal).x, untagged_x_decllit.x);
}

// Deep free
Expand Down Expand Up @@ -1541,7 +1591,12 @@ test "std.zon structs" {
const Vec0 = struct {};
const Vec1 = struct { x: f32 };
const Vec2 = struct { x: f32, y: f32 };
const Vec3 = struct { x: f32, y: f32, z: f32 };
const Vec3 = struct {
pub const Zero: @This() = .{ .x = 0, .y = 0, .z = 0 };
x: f32,
y: f32,
z: f32,
};

const zero = try fromSlice(Vec0, gpa, ".{}", null, .{});
try std.testing.expectEqual(Vec0{}, zero);
Expand All @@ -1554,6 +1609,12 @@ test "std.zon structs" {

const three = try fromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", null, .{});
try std.testing.expectEqual(Vec3{ .x = 1.2, .y = 3.4, .z = 5.6 }, three);

const decl_literal_Zero = try fromSlice(Vec3, gpa, ".Zero", null, .{});
try std.testing.expectEqual(decl_literal_Zero, @as(Vec3, .Zero));

const decl_literal_One: ?Vec3 = fromSlice(Vec3, gpa, ".DeclarationThatDoesNotExist", null, .{}) catch null;
try std.testing.expectEqual(decl_literal_One, null);
}

// Deep free (structs and arrays)
Expand Down Expand Up @@ -2335,6 +2396,7 @@ test "std.zon enum literals" {
const gpa = std.testing.allocator;

const Enum = enum {
pub const NotBar: @This() = .baz;
foo,
bar,
baz,
Expand All @@ -2349,6 +2411,7 @@ test "std.zon enum literals" {
Enum.@"ab\nc",
try fromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}),
);
try std.testing.expectEqual(Enum.NotBar, try fromSlice(Enum, gpa, ".NotBar", null, .{}));

// Bad tag
{
Expand Down
2 changes: 1 addition & 1 deletion src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26793,7 +26793,7 @@ fn fieldPtrLoad(
return analyzeLoad(sema, block, src, field_ptr, field_name_src);
}

fn fieldVal(
pub fn fieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
Expand Down
33 changes: 33 additions & 0 deletions src/Sema/LowerZon.zig
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,9 @@ fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I
field_name.get(self.file.zoir.?),
.no_embedded_nulls,
);

const enum_info = ip.loadEnumType(res_ty.ip_index);
if (try self.lowerDeclLiteral(node, res_ty, field_name, enum_info.namespace)) |val| return val;
const field_index = res_ty.enumFieldIndex(field_name_interned, self.sema.pt.zcu) orelse {
return self.fail(
node,
Expand Down Expand Up @@ -746,6 +749,29 @@ fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.
return (try self.sema.pt.aggregateValue(res_ty, elems)).toIntern();
}

fn lowerDeclLiteral(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type, name: Zoir.NullTerminatedString, namespace: InternPool.NamespaceIndex) !?InternPool.Index {
const ip = &self.sema.pt.zcu.intern_pool;

const air = Air.internedToRef(res_ty.toIntern());
// Intern the incoming name
const decl_name = try ip.getOrPutString(
self.sema.gpa,
self.sema.pt.tid,
name.get(self.file.zoir.?),
.no_embedded_nulls,
);

for (ip.namespacePtr(namespace).pub_decls.keys()) |decl| {
const nav = ip.getNav(decl);
if (nav.name == decl_name) {
const src = self.nodeSrc(node);
const val = try self.sema.fieldVal(self.block, src, air, decl_name, src);
return val.toInterned().?;
}
}
Comment on lines +764 to +771
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointer decl-literals would also have to be handled here of course

return null;
}

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;
Expand All @@ -757,6 +783,10 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool
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 } },
.enum_literal => |name| {
if (try self.lowerDeclLiteral(node, res_ty, name, struct_info.namespace)) |val| return val;
return error.WrongType;
},
else => return error.WrongType,
};

Expand Down Expand Up @@ -905,6 +935,9 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.
name.get(self.file.zoir.?),
.no_embedded_nulls,
);

if (try self.lowerDeclLiteral(node, res_ty, name, union_info.namespace)) |val| return val;

break :b .{ field_name, null };
},
.struct_literal => b: {
Expand Down
30 changes: 30 additions & 0 deletions test/behavior/zon.zig
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,33 @@ test "build.zig.zon" {

try expectEqual(.{ "build.zig", "build.zig.zon", "src" }, build.paths);
}

test "decl literal" {
const Struct = struct {
pub const ADeclLitStruct: @This() = .{.a = 5,.b = 0.6};
a: u64,
b: f64,
};
const Union = union(enum){
pub const ADeclLitUnion: @This() = .{.a = 5,};
a: u64,
b: f64,
};
const Enum = enum {
pub const ADeclLitEnum: @This() = .B;
A,
B,
C,
};

const Lits = struct {
a: Struct,
b: Union,
c: Enum,
};

const Preset: Lits = @import("zon/DeclLiterals.zon");
try expectEqual(Preset.a, Struct.ADeclLitStruct);
try expectEqual(Preset.b, Union.ADeclLitUnion);
try expectEqual(Preset.c, Enum.ADeclLitEnum);
}
5 changes: 5 additions & 0 deletions test/behavior/zon/DeclLiterals.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.{
.a = .ADeclLitStruct,
.b = .ADeclLitUnion,
.c = .ADeclLitEnum,
}