Skip to content

Commit

Permalink
Merge pull request #3652 from ziglang/anon-container-lit
Browse files Browse the repository at this point in the history
implement anonymous struct literals and anonymous list literals
  • Loading branch information
andrewrk authored Nov 12, 2019
2 parents ae0a219 + 0c315e7 commit 5502160
Show file tree
Hide file tree
Showing 18 changed files with 783 additions and 243 deletions.
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

0 comments on commit 5502160

Please sign in to comment.