Commit 7a1a924788

Andrew Kelley <andrew@ziglang.org>
2020-07-21 01:21:32
stage2: AST: (breaking) flatten out suffix operations
1 parent 1ac28ee
Changed files (5)
lib/std/zig/ast.zig
@@ -471,9 +471,14 @@ pub const Node = struct {
         ArrayTypeSentinel,
         PtrType,
         SliceType,
-        /// Not all suffix operations are under this tag. To save memory, some
-        /// suffix operations have dedicated Node tags.
-        SuffixOp,
+        /// `a[b..c]`
+        Slice,
+        /// `a.*`
+        Deref,
+        /// `a.?`
+        UnwrapOptional,
+        /// `a[b]`
+        ArrayAccess,
         /// `T{a, b}`
         ArrayInitializer,
         /// ArrayInitializer but with `.` instead of a left-hand-side operand.
@@ -601,7 +606,9 @@ pub const Node = struct {
 
                 .PtrType => PtrType,
                 .SliceType => SliceType,
-                .SuffixOp => SuffixOp,
+                .Slice => Slice,
+                .Deref, .UnwrapOptional => SimpleSuffixOp,
+                .ArrayAccess => ArrayAccess,
 
                 .ArrayInitializer => ArrayInitializer,
                 .ArrayInitializerDot => ArrayInitializerDot,
@@ -2398,8 +2405,8 @@ pub const Node = struct {
     /// Parameter nodes directly follow Call in memory.
     pub const Call = struct {
         base: Node = Node{ .tag = .Call },
-        lhs: *Node,
         rtoken: TokenIndex,
+        lhs: *Node,
         params_len: NodeIndex,
         async_token: ?TokenIndex,
 
@@ -2450,62 +2457,90 @@ pub const Node = struct {
         }
     };
 
-    pub const SuffixOp = struct {
-        base: Node = Node{ .tag = .SuffixOp },
-        op: Op,
+    pub const ArrayAccess = struct {
+        base: Node = Node{ .tag = .ArrayAccess },
+        rtoken: TokenIndex,
         lhs: *Node,
+        index_expr: *Node,
+
+        pub fn iterate(self: *const ArrayAccess, index: usize) ?*Node {
+            var i = index;
+
+            if (i < 1) return self.lhs;
+            i -= 1;
+
+            if (i < 1) return self.index_expr;
+            i -= 1;
+
+            return null;
+        }
+
+        pub fn firstToken(self: *const ArrayAccess) TokenIndex {
+            return self.lhs.firstToken();
+        }
+
+        pub fn lastToken(self: *const ArrayAccess) TokenIndex {
+            return self.rtoken;
+        }
+    };
+
+    pub const SimpleSuffixOp = struct {
+        base: Node,
         rtoken: TokenIndex,
+        lhs: *Node,
 
-        pub const Op = union(enum) {
-            ArrayAccess: *Node,
-            Slice: Slice,
-            Deref,
-            UnwrapOptional,
+        pub fn iterate(self: *const SimpleSuffixOp, index: usize) ?*Node {
+            var i = index;
 
-            pub const Slice = struct {
-                start: *Node,
-                end: ?*Node,
-                sentinel: ?*Node,
-            };
-        };
+            if (i < 1) return self.lhs;
+            i -= 1;
 
-        pub fn iterate(self: *const SuffixOp, index: usize) ?*Node {
+            return null;
+        }
+
+        pub fn firstToken(self: *const SimpleSuffixOp) TokenIndex {
+            return self.lhs.firstToken();
+        }
+
+        pub fn lastToken(self: *const SimpleSuffixOp) TokenIndex {
+            return self.rtoken;
+        }
+    };
+
+    pub const Slice = struct {
+        base: Node = Node{ .tag = .Slice },
+        rtoken: TokenIndex,
+        lhs: *Node,
+        start: *Node,
+        end: ?*Node,
+        sentinel: ?*Node,
+
+        pub fn iterate(self: *const Slice, index: usize) ?*Node {
             var i = index;
 
-            if (i == 0) return self.lhs;
+            if (i < 1) return self.lhs;
             i -= 1;
 
-            switch (self.op) {
-                .ArrayAccess => |index_expr| {
-                    if (i < 1) return index_expr;
-                    i -= 1;
-                },
-                .Slice => |range| {
-                    if (i < 1) return range.start;
-                    i -= 1;
+            if (i < 1) return self.start;
+            i -= 1;
 
-                    if (range.end) |end| {
-                        if (i < 1) return end;
-                        i -= 1;
-                    }
-                    if (range.sentinel) |sentinel| {
-                        if (i < 1) return sentinel;
-                        i -= 1;
-                    }
-                },
-                .UnwrapOptional,
-                .Deref,
-                => {},
+            if (self.end) |end| {
+                if (i < 1) return end;
+                i -= 1;
+            }
+            if (self.sentinel) |sentinel| {
+                if (i < 1) return sentinel;
+                i -= 1;
             }
 
             return null;
         }
 
-        pub fn firstToken(self: *const SuffixOp) TokenIndex {
+        pub fn firstToken(self: *const Slice) TokenIndex {
             return self.lhs.firstToken();
         }
 
-        pub fn lastToken(self: *const SuffixOp) TokenIndex {
+        pub fn lastToken(self: *const Slice) TokenIndex {
             return self.rtoken;
         }
     };
lib/std/zig/parse.zig
@@ -1439,58 +1439,7 @@ const Parser = struct {
                 .ExpectedPrimaryTypeExpr = .{ .token = p.tok_i },
             });
 
-            // TODO pass `res` into `parseSuffixOp` rather than patching it up afterwards.
-            while (try p.parseSuffixOp()) |node| {
-                switch (node.tag) {
-                    .SuffixOp => node.cast(Node.SuffixOp).?.lhs = res,
-                    .Catch => node.castTag(.Catch).?.lhs = res,
-
-                    .Add,
-                    .AddWrap,
-                    .ArrayCat,
-                    .ArrayMult,
-                    .Assign,
-                    .AssignBitAnd,
-                    .AssignBitOr,
-                    .AssignBitShiftLeft,
-                    .AssignBitShiftRight,
-                    .AssignBitXor,
-                    .AssignDiv,
-                    .AssignSub,
-                    .AssignSubWrap,
-                    .AssignMod,
-                    .AssignAdd,
-                    .AssignAddWrap,
-                    .AssignMul,
-                    .AssignMulWrap,
-                    .BangEqual,
-                    .BitAnd,
-                    .BitOr,
-                    .BitShiftLeft,
-                    .BitShiftRight,
-                    .BitXor,
-                    .BoolAnd,
-                    .BoolOr,
-                    .Div,
-                    .EqualEqual,
-                    .ErrorUnion,
-                    .GreaterOrEqual,
-                    .GreaterThan,
-                    .LessOrEqual,
-                    .LessThan,
-                    .MergeErrorSets,
-                    .Mod,
-                    .Mul,
-                    .MulWrap,
-                    .Period,
-                    .Range,
-                    .Sub,
-                    .SubWrap,
-                    .OrElse,
-                    => node.cast(Node.SimpleInfixOp).?.lhs = res,
-
-                    else => unreachable,
-                }
+            while (try p.parseSuffixOp(res)) |node| {
                 res = node;
             }
 
@@ -1516,57 +1465,7 @@ const Parser = struct {
             var res = expr;
 
             while (true) {
-                // TODO pass `res` into `parseSuffixOp` rather than patching it up afterwards.
-                if (try p.parseSuffixOp()) |node| {
-                    switch (node.tag) {
-                        .SuffixOp => node.cast(Node.SuffixOp).?.lhs = res,
-                        .Catch => node.castTag(.Catch).?.lhs = res,
-
-                        .Add,
-                        .AddWrap,
-                        .ArrayCat,
-                        .ArrayMult,
-                        .Assign,
-                        .AssignBitAnd,
-                        .AssignBitOr,
-                        .AssignBitShiftLeft,
-                        .AssignBitShiftRight,
-                        .AssignBitXor,
-                        .AssignDiv,
-                        .AssignSub,
-                        .AssignSubWrap,
-                        .AssignMod,
-                        .AssignAdd,
-                        .AssignAddWrap,
-                        .AssignMul,
-                        .AssignMulWrap,
-                        .BangEqual,
-                        .BitAnd,
-                        .BitOr,
-                        .BitShiftLeft,
-                        .BitShiftRight,
-                        .BitXor,
-                        .BoolAnd,
-                        .BoolOr,
-                        .Div,
-                        .EqualEqual,
-                        .ErrorUnion,
-                        .GreaterOrEqual,
-                        .GreaterThan,
-                        .LessOrEqual,
-                        .LessThan,
-                        .MergeErrorSets,
-                        .Mod,
-                        .Mul,
-                        .MulWrap,
-                        .Period,
-                        .Range,
-                        .Sub,
-                        .SubWrap,
-                        .OrElse,
-                        => node.cast(Node.SimpleInfixOp).?.lhs = res,
-                        else => unreachable,
-                    }
+                if (try p.parseSuffixOp(res)) |node| {
                     res = node;
                     continue;
                 }
@@ -2733,78 +2632,77 @@ const Parser = struct {
     ///      / DOT IDENTIFIER
     ///      / DOTASTERISK
     ///      / DOTQUESTIONMARK
-    fn parseSuffixOp(p: *Parser) !?*Node {
-        const OpAndToken = struct {
-            op: Node.SuffixOp.Op,
-            token: TokenIndex,
-        };
-        const op_and_token: OpAndToken = blk: {
-            if (p.eatToken(.LBracket)) |_| {
-                const index_expr = try p.expectNode(parseExpr, .{
-                    .ExpectedExpr = .{ .token = p.tok_i },
-                });
-
-                if (p.eatToken(.Ellipsis2) != null) {
-                    const end_expr = try p.parseExpr();
-                    const sentinel: ?*Node = if (p.eatToken(.Colon) != null)
-                        try p.parseExpr()
-                    else
-                        null;
-                    break :blk .{
-                        .op = .{
-                            .Slice = .{
-                                .start = index_expr,
-                                .end = end_expr,
-                                .sentinel = sentinel,
-                            },
-                        },
-                        .token = try p.expectToken(.RBracket),
-                    };
-                }
+    fn parseSuffixOp(p: *Parser, lhs: *Node) !?*Node {
+        if (p.eatToken(.LBracket)) |_| {
+            const index_expr = try p.expectNode(parseExpr, .{
+                .ExpectedExpr = .{ .token = p.tok_i },
+            });
 
-                break :blk .{
-                    .op = .{ .ArrayAccess = index_expr },
-                    .token = try p.expectToken(.RBracket),
+            if (p.eatToken(.Ellipsis2) != null) {
+                const end_expr = try p.parseExpr();
+                const sentinel: ?*Node = if (p.eatToken(.Colon) != null)
+                    try p.parseExpr()
+                else
+                    null;
+                const rtoken = try p.expectToken(.RBracket);
+                const node = try p.arena.allocator.create(Node.Slice);
+                node.* = .{
+                    .lhs = lhs,
+                    .rtoken = rtoken,
+                    .start = index_expr,
+                    .end = end_expr,
+                    .sentinel = sentinel,
                 };
+                return &node.base;
             }
 
-            if (p.eatToken(.PeriodAsterisk)) |period_asterisk| {
-                break :blk .{ .op = .Deref, .token = period_asterisk };
-            }
+            const rtoken = try p.expectToken(.RBracket);
+            const node = try p.arena.allocator.create(Node.ArrayAccess);
+            node.* = .{
+                .lhs = lhs,
+                .rtoken = rtoken,
+                .index_expr = index_expr,
+            };
+            return &node.base;
+        }
 
-            if (p.eatToken(.Period)) |period| {
-                if (try p.parseIdentifier()) |identifier| {
-                    // TODO: It's a bit weird to return a SimpleInfixOp from the SuffixOp parser.
-                    // Should there be an Node.SuffixOp.FieldAccess variant? Or should
-                    // this grammar rule be altered?
-                    const node = try p.arena.allocator.create(Node.SimpleInfixOp);
-                    node.* = .{
-                        .base = Node{ .tag = .Period },
-                        .op_token = period,
-                        .lhs = undefined, // set by caller
-                        .rhs = identifier,
-                    };
-                    return &node.base;
-                }
-                if (p.eatToken(.QuestionMark)) |question_mark| {
-                    break :blk .{ .op = .UnwrapOptional, .token = question_mark };
-                }
-                try p.errors.append(p.gpa, .{
-                    .ExpectedSuffixOp = .{ .token = p.tok_i },
-                });
-                return null;
-            }
+        if (p.eatToken(.PeriodAsterisk)) |period_asterisk| {
+            const node = try p.arena.allocator.create(Node.SimpleSuffixOp);
+            node.* = .{
+                .base = .{ .tag = .Deref },
+                .lhs = lhs,
+                .rtoken = period_asterisk,
+            };
+            return &node.base;
+        }
 
+        if (p.eatToken(.Period)) |period| {
+            if (try p.parseIdentifier()) |identifier| {
+                const node = try p.arena.allocator.create(Node.SimpleInfixOp);
+                node.* = .{
+                    .base = Node{ .tag = .Period },
+                    .op_token = period,
+                    .lhs = lhs,
+                    .rhs = identifier,
+                };
+                return &node.base;
+            }
+            if (p.eatToken(.QuestionMark)) |question_mark| {
+                const node = try p.arena.allocator.create(Node.SimpleSuffixOp);
+                node.* = .{
+                    .base = .{ .tag = .UnwrapOptional },
+                    .lhs = lhs,
+                    .rtoken = question_mark,
+                };
+                return &node.base;
+            }
+            try p.errors.append(p.gpa, .{
+                .ExpectedSuffixOp = .{ .token = p.tok_i },
+            });
             return null;
-        };
+        }
 
-        const node = try p.arena.allocator.create(Node.SuffixOp);
-        node.* = .{
-            .lhs = undefined, // set by caller
-            .op = op_and_token.op,
-            .rtoken = op_and_token.token,
-        };
-        return &node.base;
+        return null;
     }
 
     /// FnCallArguments <- LPAREN ExprList RPAREN
lib/std/zig/render.zig
@@ -1044,68 +1044,67 @@ fn renderExpression(
             return renderToken(tree, stream, call.rtoken, indent, start_col, space);
         },
 
-        .SuffixOp => {
-            const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", base);
-
-            switch (suffix_op.op) {
-                .ArrayAccess => |index_expr| {
-                    const lbracket = tree.nextToken(suffix_op.lhs.lastToken());
-                    const rbracket = tree.nextToken(index_expr.lastToken());
-
-                    try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
-                    try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
-
-                    const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
-                    const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
-                    const new_indent = if (ends_with_comment) indent + indent_delta else indent;
-                    const new_space = if (ends_with_comment) Space.Newline else Space.None;
-                    try renderExpression(allocator, stream, tree, new_indent, start_col, index_expr, new_space);
-                    if (starts_with_comment) {
-                        try stream.writeByte('\n');
-                    }
-                    if (ends_with_comment or starts_with_comment) {
-                        try stream.writeByteNTimes(' ', indent);
-                    }
-                    return renderToken(tree, stream, rbracket, indent, start_col, space); // ]
-                },
+        .ArrayAccess => {
+            const suffix_op = base.castTag(.ArrayAccess).?;
 
-                .Deref => {
-                    try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
-                    return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // .*
-                },
+            const lbracket = tree.nextToken(suffix_op.lhs.lastToken());
+            const rbracket = tree.nextToken(suffix_op.index_expr.lastToken());
 
-                .UnwrapOptional => {
-                    try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
-                    try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, start_col, Space.None); // .
-                    return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ?
-                },
+            try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
+            try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
+
+            const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
+            const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
+            const new_indent = if (ends_with_comment) indent + indent_delta else indent;
+            const new_space = if (ends_with_comment) Space.Newline else Space.None;
+            try renderExpression(allocator, stream, tree, new_indent, start_col, suffix_op.index_expr, new_space);
+            if (starts_with_comment) {
+                try stream.writeByte('\n');
+            }
+            if (ends_with_comment or starts_with_comment) {
+                try stream.writeByteNTimes(' ', indent);
+            }
+            return renderToken(tree, stream, rbracket, indent, start_col, space); // ]
+        },
+        .Slice => {
+            const suffix_op = base.castTag(.Slice).?;
 
-                .Slice => |range| {
-                    try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
+            try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
 
-                    const lbracket = tree.prevToken(range.start.firstToken());
-                    const dotdot = tree.nextToken(range.start.lastToken());
+            const lbracket = tree.prevToken(suffix_op.start.firstToken());
+            const dotdot = tree.nextToken(suffix_op.start.lastToken());
 
-                    const after_start_space_bool = nodeCausesSliceOpSpace(range.start) or
-                        (if (range.end) |end| nodeCausesSliceOpSpace(end) else false);
-                    const after_start_space = if (after_start_space_bool) Space.Space else Space.None;
-                    const after_op_space = if (range.end != null) after_start_space else Space.None;
+            const after_start_space_bool = nodeCausesSliceOpSpace(suffix_op.start) or
+                (if (suffix_op.end) |end| nodeCausesSliceOpSpace(end) else false);
+            const after_start_space = if (after_start_space_bool) Space.Space else Space.None;
+            const after_op_space = if (suffix_op.end != null) after_start_space else Space.None;
 
-                    try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
-                    try renderExpression(allocator, stream, tree, indent, start_col, range.start, after_start_space);
-                    try renderToken(tree, stream, dotdot, indent, start_col, after_op_space); // ..
-                    if (range.end) |end| {
-                        const after_end_space = if (range.sentinel != null) Space.Space else Space.None;
-                        try renderExpression(allocator, stream, tree, indent, start_col, end, after_end_space);
-                    }
-                    if (range.sentinel) |sentinel| {
-                        const colon = tree.prevToken(sentinel.firstToken());
-                        try renderToken(tree, stream, colon, indent, start_col, Space.None); // :
-                        try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
-                    }
-                    return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ]
-                },
+            try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
+            try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.start, after_start_space);
+            try renderToken(tree, stream, dotdot, indent, start_col, after_op_space); // ..
+            if (suffix_op.end) |end| {
+                const after_end_space = if (suffix_op.sentinel != null) Space.Space else Space.None;
+                try renderExpression(allocator, stream, tree, indent, start_col, end, after_end_space);
             }
+            if (suffix_op.sentinel) |sentinel| {
+                const colon = tree.prevToken(sentinel.firstToken());
+                try renderToken(tree, stream, colon, indent, start_col, Space.None); // :
+                try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
+            }
+            return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ]
+        },
+        .Deref => {
+            const suffix_op = base.castTag(.Deref).?;
+
+            try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
+            return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // .*
+        },
+        .UnwrapOptional => {
+            const suffix_op = base.castTag(.UnwrapOptional).?;
+
+            try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
+            try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, start_col, Space.None); // .
+            return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ?
         },
 
         .ControlFlowExpression => {
src-self-hosted/astgen.zig
@@ -34,7 +34,7 @@ pub fn expr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst {
         .LessThan => return cmp(mod, scope, node.castTag(.LessThan).?, .lt),
         .LessOrEqual => return cmp(mod, scope, node.castTag(.LessOrEqual).?, .lte),
         .Period => return field(mod, scope, node.castTag(.Period).?),
-        .SuffixOp => return suffixOp(mod, scope, node.castTag(.SuffixOp).?),
+        .Deref => return deref(mod, scope, node.castTag(.Deref).?),
         .BoolNot => return boolNot(mod, scope, node.castTag(.BoolNot).?),
         else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}),
     }
@@ -73,32 +73,41 @@ fn varDecl(mod: *Module, scope: *Scope, node: *ast.Node.VarDecl) InnerError!Scop
     if (node.getTrailer("align_node")) |align_node| {
         return mod.failNode(scope, align_node, "TODO implement alignment on locals", .{});
     }
-    if (node.getTrailer("type_node")) |type_node| {
-        return mod.failNode(scope, type_node, "TODO implement typed locals", .{});
-    }
     const tree = scope.tree();
     switch (tree.token_ids[node.mut_token]) {
-        .Keyword_const => {},
+        .Keyword_const => {
+            if (node.getTrailer("type_node")) |type_node| {
+                return mod.failNode(scope, type_node, "TODO implement typed const locals", .{});
+            }
+            // Depending on the type of AST the initialization expression is, we may need an lvalue
+            // or an rvalue as a result location. If it is an rvalue, we can use the instruction as
+            // the variable, no memory location needed.
+            const init_node = node.getTrailer("init_node").?;
+            if (nodeMayNeedMemoryLocation(init_node)) {
+                return mod.failNode(scope, init_node, "TODO implement result locations", .{});
+            }
+            const init_inst = try expr(mod, scope, init_node);
+            const ident_name = try identifierTokenString(mod, scope, node.name_token);
+            return Scope.LocalVar{
+                .parent = scope,
+                .gen_zir = scope.getGenZIR(),
+                .name = ident_name,
+                .inst = init_inst,
+            };
+        },
         .Keyword_var => {
-            return mod.failTok(scope, node.mut_token, "TODO implement mutable locals", .{});
+            return mod.failNode(scope, &node.base, "TODO implement local vars", .{});
+            //const src = tree.token_locs[node.name_token].start;
+            //const alloc = mod.addZIRInst(scope, src, zir.Inst.Alloc, .{}, .{});
+            //if (node.getTrailer("type_node")) |type_node| {
+            //    const type_inst = try expr(mod, scope, type_node);
+            //    return mod.failNode(scope, type_node, "TODO implement typed var locals", .{});
+            //} else {
+            //    return mod.failTok(scope, node.mut_token, "TODO implement mutable type-inferred locals", .{});
+            //}
         },
         else => unreachable,
     }
-    // Depending on the type of AST the initialization expression is, we may need an lvalue
-    // or an rvalue as a result location. If it is an rvalue, we can use the instruction as
-    // the variable, no memory location needed.
-    const init_node = node.getTrailer("init_node").?;
-    if (nodeMayNeedMemoryLocation(init_node)) {
-        return mod.failNode(scope, init_node, "TODO implement result locations", .{});
-    }
-    const init_inst = try expr(mod, scope, init_node);
-    const ident_name = try identifierTokenString(mod, scope, node.name_token);
-    return Scope.LocalVar{
-        .parent = scope,
-        .gen_zir = scope.getGenZIR(),
-        .name = ident_name,
-        .inst = init_inst,
-    };
 }
 
 fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst {
@@ -163,18 +172,13 @@ fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!
     return mod.addZIRInst(scope, src, zir.Inst.Deref, .{ .ptr = pointer }, .{});
 }
 
-fn suffixOp(mod: *Module, scope: *Scope, node: *ast.Node.SuffixOp) InnerError!*zir.Inst {
-    switch (node.op) {
-        .Deref => {
-            const tree = scope.tree();
-            const src = tree.token_locs[node.rtoken].start;
+fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
+    const tree = scope.tree();
+    const src = tree.token_locs[node.rtoken].start;
 
-            const lhs = try expr(mod, scope, node.lhs);
+    const lhs = try expr(mod, scope, node.lhs);
 
-            return mod.addZIRInst(scope, src, zir.Inst.Deref, .{ .ptr = lhs }, .{});
-        },
-        else => return mod.failNode(scope, &node.base, "TODO implement astGenExpr for suffixOp {}", .{node.op}),
-    }
+    return mod.addZIRInst(scope, src, zir.Inst.Deref, .{ .ptr = lhs }, .{});
 }
 
 fn add(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
@@ -672,6 +676,9 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
             .Period,
             .Sub,
             .SubWrap,
+            .Slice,
+            .Deref,
+            .ArrayAccess,
             => return false,
 
             // Forward the question to a sub-expression.
@@ -682,6 +689,7 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
             .OrElse => node = node.castTag(.OrElse).?.rhs,
             .Comptime => node = node.castTag(.Comptime).?.expr,
             .Nosuspend => node = node.castTag(.Nosuspend).?.expr,
+            .UnwrapOptional => node = node.castTag(.UnwrapOptional).?.lhs,
 
             // True because these are exactly the expressions we need memory locations for.
             .ArrayInitializer,
@@ -697,7 +705,6 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
             .Switch,
             .Call,
             .BuiltinCall, // TODO some of these can return false
-            .SuffixOp, // TODO this should be split up
             => return true,
 
             // Depending on AST properties, they may need memory locations.
src-self-hosted/translate_c.zig
@@ -2962,9 +2962,9 @@ fn transArrayAccess(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangArrayS
         cast_node.params()[1] = try transExpr(rp, scope, subscr_expr, .used, .r_value);
         cast_node.rparen_token = try appendToken(rp.c, .RParen, ")");
         node.rtoken = try appendToken(rp.c, .RBrace, "]");
-        node.op.ArrayAccess = &cast_node.base;
+        node.index_expr = &cast_node.base;
     } else {
-        node.op.ArrayAccess = try transExpr(rp, scope, subscr_expr, .used, .r_value);
+        node.index_expr = try transExpr(rp, scope, subscr_expr, .used, .r_value);
         node.rtoken = try appendToken(rp.c, .RBrace, "]");
     }
     return maybeSuppressResult(rp, scope, result_used, &node.base);
@@ -4405,9 +4405,9 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a
 fn transCreateNodeUnwrapNull(c: *Context, wrapped: *ast.Node) !*ast.Node {
     _ = try appendToken(c, .Period, ".");
     const qm = try appendToken(c, .QuestionMark, "?");
-    const node = try c.arena.create(ast.Node.SuffixOp);
+    const node = try c.arena.create(ast.Node.SimpleSuffixOp);
     node.* = .{
-        .op = .UnwrapOptional,
+        .base = .{ .tag = .UnwrapOptional },
         .lhs = wrapped,
         .rtoken = qm,
     };
@@ -4567,23 +4567,21 @@ fn transCreateNodeShiftOp(
 }
 
 fn transCreateNodePtrDeref(c: *Context, lhs: *ast.Node) !*ast.Node {
-    const node = try c.arena.create(ast.Node.SuffixOp);
+    const node = try c.arena.create(ast.Node.SimpleSuffixOp);
     node.* = .{
+        .base = .{ .tag = .Deref },
         .lhs = lhs,
-        .op = .Deref,
         .rtoken = try appendToken(c, .PeriodAsterisk, ".*"),
     };
     return &node.base;
 }
 
-fn transCreateNodeArrayAccess(c: *Context, lhs: *ast.Node) !*ast.Node.SuffixOp {
+fn transCreateNodeArrayAccess(c: *Context, lhs: *ast.Node) !*ast.Node.ArrayAccess {
     _ = try appendToken(c, .LBrace, "[");
-    const node = try c.arena.create(ast.Node.SuffixOp);
+    const node = try c.arena.create(ast.Node.ArrayAccess);
     node.* = .{
         .lhs = lhs,
-        .op = .{
-            .ArrayAccess = undefined,
-        },
+        .index_expr = undefined,
         .rtoken = undefined,
     };
     return node;
@@ -6010,7 +6008,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
             },
             .LBracket => {
                 const arr_node = try transCreateNodeArrayAccess(c, node);
-                arr_node.op.ArrayAccess = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+                arr_node.index_expr = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
                 arr_node.rtoken = try appendToken(c, .RBracket, "]");
                 node = &arr_node.base;
                 if (it.next().?.id != .RBracket) {
@@ -6099,7 +6097,6 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
                 };
                 mem.copy(*ast.Node, tuple_node.list(), init_vals.items);
 
-
                 //(@import("std").mem.zeroInit(T, .{x}))
                 const import_fn_call = try c.createBuiltinCall("@import", 1);
                 const std_node = try transCreateNodeStringLiteral(c, "\"std\"");