Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement anonymous struct literals and anonymous list literals #3652

Merged
merged 10 commits into from
Nov 12, 2019
113 changes: 112 additions & 1 deletion doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -1734,6 +1734,43 @@ test "array initialization with function calls" {
{#code_end#}
{#see_also|for|Slices#}

{#header_open|Anonymous List Literals#}
<p>Similar to {#link|Enum Literals#} and {#link|Anonymous Struct Literals#}
the type can be omitted from array literals:</p>
{#code_begin|test|anon_list#}
const std = @import("std");
const assert = std.debug.assert;

test "anonymous list literal syntax" {
var array: [4]u8 = .{11, 22, 33, 44};
assert(array[0] == 11);
assert(array[1] == 22);
assert(array[2] == 33);
assert(array[3] == 44);
}
{#code_end#}
<p>
If there is no type in the result location then an anonymous list literal actually
turns into a {#link|struct#} with numbered field names:
</p>
{#code_begin|test|infer_list_literal#}
const std = @import("std");
const assert = std.debug.assert;

test "fully anonymous list literal" {
dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi"});
}

fn dump(args: var) void {
assert(args.@"0" == 1234);
assert(args.@"1" == 12.34);
assert(args.@"2");
assert(args.@"3"[0] == 'h');
assert(args.@"3"[1] == 'i');
}
{#code_end#}
{#header_close#}

{#header_open|Multidimensional Arrays#}
<p>
Mutlidimensional arrays can be created by nesting arrays:
Expand Down Expand Up @@ -2526,7 +2563,8 @@ test "overaligned pointer to packed struct" {
Don't worry, there will be a good solution for this use case in zig.
</p>
{#header_close#}
{#header_open|struct Naming#}

{#header_open|Struct Naming#}
<p>Since all structs are anonymous, Zig infers the type name based on a few rules.</p>
<ul>
<li>If the struct is in the initialization expression of a variable, it gets named after
Expand All @@ -2552,6 +2590,53 @@ fn List(comptime T: type) type {
}
{#code_end#}
{#header_close#}

{#header_open|Anonymous Struct Literals#}
<p>
Zig allows omitting the struct type of a literal. When the result is {#link|coerced|Type Coercion#},
the struct literal will directly instantiate the result location, with no copy:
</p>
{#code_begin|test|struct_result#}
const std = @import("std");
const assert = std.debug.assert;

const Point = struct {x: i32, y: i32};

test "anonymous struct literal" {
var pt: Point = .{
.x = 13,
.y = 67,
};
assert(pt.x == 13);
assert(pt.y == 67);
}
{#code_end#}
<p>
The struct type can be inferred. Here the result location does not include a type, and
so Zig infers the type:
</p>
{#code_begin|test|struct_anon#}
const std = @import("std");
const assert = std.debug.assert;

test "fully anonymous struct" {
dump(.{
.int = @as(u32, 1234),
.float = @as(f64, 12.34),
.b = true,
.s = "hi",
});
}

fn dump(args: var) void {
assert(args.int == 1234);
assert(args.float == 12.34);
assert(args.b);
assert(args.s[0] == 'h');
assert(args.s[1] == 'i');
}
{#code_end#}
{#header_close#}
{#see_also|comptime|@fieldParentPtr#}
{#header_close#}
{#header_open|enum#}
Expand Down Expand Up @@ -2906,6 +2991,32 @@ test "@tagName" {
<p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible
to be in a {#link|packed struct#}.
{#header_close#}

{#header_open|Anonymous Union Literals#}
<p>{#link|Anonymous Struct Literals#} syntax can be used to initialize unions without specifying
the type:</p>
{#code_begin|test|anon_union#}
const std = @import("std");
const assert = std.debug.assert;

const Number = union {
int: i32,
float: f64,
};

test "anonymous union literal syntax" {
var i: Number = .{.int = 42};
var f = makeNumber();
assert(i.int == 42);
assert(f.float == 12.34);
}

fn makeNumber() Number {
return .{.float = 12.34};
}
{#code_end#}
{#header_close#}

{#header_close#}

{#header_open|blocks#}
Expand Down
33 changes: 2 additions & 31 deletions lib/std/builtin.zig
Original file line number Diff line number Diff line change
Expand Up @@ -90,40 +90,11 @@ pub const Mode = enum {
ReleaseSmall,
};

/// This data structure is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const TypeId = enum {
Type,
Void,
Bool,
NoReturn,
Int,
Float,
Pointer,
Array,
Struct,
ComptimeFloat,
ComptimeInt,
Undefined,
Null,
Optional,
ErrorUnion,
ErrorSet,
Enum,
Union,
Fn,
BoundFn,
ArgTuple,
Opaque,
Frame,
AnyFrame,
Vector,
EnumLiteral,
};
pub const TypeId = @TagType(TypeInfo);

/// This data structure is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const TypeInfo = union(TypeId) {
pub const TypeInfo = union(enum) {
Type: void,
Void: void,
Bool: void,
Expand Down
21 changes: 17 additions & 4 deletions lib/std/zig/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1648,10 +1648,15 @@ pub const Node = struct {

pub const SuffixOp = struct {
base: Node,
lhs: *Node,
lhs: Lhs,
op: Op,
rtoken: TokenIndex,

pub const Lhs = union(enum) {
node: *Node,
dot: TokenIndex,
};

pub const Op = union(enum) {
Call: Call,
ArrayAccess: *Node,
Expand Down Expand Up @@ -1679,8 +1684,13 @@ pub const Node = struct {
pub fn iterate(self: *SuffixOp, index: usize) ?*Node {
var i = index;

if (i < 1) return self.lhs;
i -= 1;
switch (self.lhs) {
.node => |node| {
if (i == 0) return node;
i -= 1;
},
.dot => {},
}

switch (self.op) {
.Call => |*call_info| {
Expand Down Expand Up @@ -1721,7 +1731,10 @@ pub const Node = struct {
.Call => |*call_info| if (call_info.async_token) |async_token| return async_token,
else => {},
}
return self.lhs.firstToken();
switch (self.lhs) {
.node => |node| return node.firstToken(),
.dot => |dot| return dot,
}
}

pub fn lastToken(self: *const SuffixOp) TokenIndex {
Expand Down
52 changes: 32 additions & 20 deletions lib/std/zig/parse.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1026,16 +1026,16 @@ fn parseWhileExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
/// CurlySuffixExpr <- TypeExpr InitList?
fn parseCurlySuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
const type_expr = (try parseTypeExpr(arena, it, tree)) orelse return null;
const init_list = (try parseInitList(arena, it, tree)) orelse return type_expr;
init_list.cast(Node.SuffixOp).?.lhs = type_expr;
return init_list;
const suffix_op = (try parseInitList(arena, it, tree)) orelse return type_expr;
suffix_op.lhs.node = type_expr;
return &suffix_op.base;
}

/// InitList
/// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
/// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
/// / LBRACE RBRACE
fn parseInitList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
fn parseInitList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.SuffixOp {
const lbrace = eatToken(it, .LBrace) orelse return null;
var init_list = Node.SuffixOp.Op.InitList.init(arena);

Expand Down Expand Up @@ -1064,11 +1064,11 @@ fn parseInitList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
const node = try arena.create(Node.SuffixOp);
node.* = Node.SuffixOp{
.base = Node{ .id = .SuffixOp },
.lhs = undefined, // set by caller
.lhs = .{.node = undefined}, // set by caller
.op = op,
.rtoken = try expectToken(it, tree, .RBrace),
};
return &node.base;
return node;
}

/// TypeExpr <- PrefixTypeOp* ErrorUnionExpr
Expand Down Expand Up @@ -1117,7 +1117,7 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {

while (try parseSuffixOp(arena, it, tree)) |node| {
switch (node.id) {
.SuffixOp => node.cast(Node.SuffixOp).?.lhs = res,
.SuffixOp => node.cast(Node.SuffixOp).?.lhs = .{.node = res},
.InfixOp => node.cast(Node.InfixOp).?.lhs = res,
else => unreachable,
}
Expand All @@ -1133,7 +1133,7 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
const node = try arena.create(Node.SuffixOp);
node.* = Node.SuffixOp{
.base = Node{ .id = .SuffixOp },
.lhs = res,
.lhs = .{.node = res},
.op = Node.SuffixOp.Op{
.Call = Node.SuffixOp.Op.Call{
.params = params.list,
Expand All @@ -1150,7 +1150,7 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
while (true) {
if (try parseSuffixOp(arena, it, tree)) |node| {
switch (node.id) {
.SuffixOp => node.cast(Node.SuffixOp).?.lhs = res,
.SuffixOp => node.cast(Node.SuffixOp).?.lhs = .{.node = res},
.InfixOp => node.cast(Node.InfixOp).?.lhs = res,
else => unreachable,
}
Expand All @@ -1161,7 +1161,7 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
const call = try arena.create(Node.SuffixOp);
call.* = Node.SuffixOp{
.base = Node{ .id = .SuffixOp },
.lhs = res,
.lhs = .{.node = res},
.op = Node.SuffixOp.Op{
.Call = Node.SuffixOp.Op.Call{
.params = params.list,
Expand Down Expand Up @@ -1215,7 +1215,7 @@ fn parsePrimaryTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*N
return &node.base;
}
if (try parseContainerDecl(arena, it, tree)) |node| return node;
if (try parseEnumLiteral(arena, it, tree)) |node| return node;
if (try parseAnonLiteral(arena, it, tree)) |node| return node;
if (try parseErrorSetDecl(arena, it, tree)) |node| return node;
if (try parseFloatLiteral(arena, it, tree)) |node| return node;
if (try parseFnProto(arena, it, tree)) |node| return node;
Expand Down Expand Up @@ -1494,16 +1494,28 @@ fn parseAsmExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
}

/// DOT IDENTIFIER
fn parseEnumLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
fn parseAnonLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
const dot = eatToken(it, .Period) orelse return null;
const name = try expectToken(it, tree, .Identifier);
const node = try arena.create(Node.EnumLiteral);
node.* = Node.EnumLiteral{
.base = Node{ .id = .EnumLiteral },
.dot = dot,
.name = name,
};
return &node.base;

// anon enum literal
if (eatToken(it, .Identifier)) |name| {
const node = try arena.create(Node.EnumLiteral);
node.* = Node.EnumLiteral{
.base = Node{ .id = .EnumLiteral },
.dot = dot,
.name = name,
};
return &node.base;
}

// anon container literal
if (try parseInitList(arena, it, tree)) |node| {
node.lhs = .{.dot = dot};
return &node.base;
}

putBackToken(it, dot);
return null;
}

/// AsmOutput <- COLON AsmOutputList AsmInput?
Expand Down
17 changes: 17 additions & 0 deletions lib/std/zig/parser_test.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
test "zig fmt: anon struct literal syntax" {
try testCanonical(
\\const x = .{
\\ .a = b,
\\ .c = d,
\\};
\\
);
}

test "zig fmt: anon list literal syntax" {
try testCanonical(
\\const x = .{ a, b, c };
\\
);
}

test "zig fmt: async function" {
try testCanonical(
\\pub const Server = struct {
Expand Down
Loading