Commit 33915cb1ed

Isaac Freund <ifreund@ifreund.xyz>
2021-02-06 22:55:29
zig fmt: implement pointer types
rename PtrType => PtrTypeBitRange, SliceType => PtrType This rename was done as the current SliceType is used for non-bitrange pointers as well as slices and because PtrTypeSentinel/PtrTypeAligned are also used for slices. Therefore using the same Ptr prefix for all these pointer/slice nodes is an improvement.
1 parent d898945
Changed files (4)
lib/std/zig/ast.zig
@@ -387,10 +387,22 @@ pub const Tree = struct {
                 }
             },
 
-            .PtrTypeAligned => unreachable, // TODO
-            .PtrTypeSentinel => unreachable, // TODO
-            .PtrType => unreachable, // TODO
-            .SliceType => unreachable, // TODO
+            .PtrTypeAligned,
+            .PtrTypeSentinel,
+            .PtrType,
+            .PtrTypeBitRange,
+            => {
+                const main_token = main_tokens[n];
+                return switch (token_tags[main_token]) {
+                    .Asterisk => switch (token_tags[main_token - 1]) {
+                        .LBrace => main_token - 1,
+                        else => main_token,
+                    },
+                    .LBrace => main_token,
+                    else => unreachable,
+                };
+            },
+
             .SwitchCaseMulti => unreachable, // TODO
             .WhileSimple => unreachable, // TODO
             .WhileCont => unreachable, // TODO
@@ -477,6 +489,10 @@ pub const Tree = struct {
             .IfSimple,
             .WhileSimple,
             .FnDecl,
+            .PtrTypeAligned,
+            .PtrTypeSentinel,
+            .PtrType,
+            .PtrTypeBitRange,
             => n = datas[n].rhs,
 
             .FieldAccess,
@@ -698,10 +714,6 @@ pub const Tree = struct {
             .SwitchRange => unreachable, // TODO
             .ArrayType => unreachable, // TODO
             .ArrayTypeSentinel => unreachable, // TODO
-            .PtrTypeAligned => unreachable, // TODO
-            .PtrTypeSentinel => unreachable, // TODO
-            .PtrType => unreachable, // TODO
-            .SliceType => unreachable, // TODO
             .SwitchCaseMulti => unreachable, // TODO
             .WhileCont => unreachable, // TODO
             .While => unreachable, // TODO
@@ -1028,6 +1040,60 @@ pub const Tree = struct {
         };
     }
 
+    pub fn ptrTypeAligned(tree: Tree, node: Node.Index) Full.PtrType {
+        assert(tree.nodes.items(.tag)[node] == .PtrTypeAligned);
+        const data = tree.nodes.items(.data)[node];
+        return tree.fullPtrType(.{
+            .main_token = tree.nodes.items(.main_token)[node],
+            .align_node = data.lhs,
+            .sentinel = 0,
+            .bit_range_start = 0,
+            .bit_range_end = 0,
+            .child_type = data.rhs,
+        });
+    }
+
+    pub fn ptrTypeSentinel(tree: Tree, node: Node.Index) Full.PtrType {
+        assert(tree.nodes.items(.tag)[node] == .PtrTypeSentinel);
+        const data = tree.nodes.items(.data)[node];
+        return tree.fullPtrType(.{
+            .main_token = tree.nodes.items(.main_token)[node],
+            .align_node = 0,
+            .sentinel = data.lhs,
+            .bit_range_start = 0,
+            .bit_range_end = 0,
+            .child_type = data.rhs,
+        });
+    }
+
+    pub fn ptrType(tree: Tree, node: Node.Index) Full.PtrType {
+        assert(tree.nodes.items(.tag)[node] == .PtrType);
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.lhs, Node.PtrType);
+        return tree.fullPtrType(.{
+            .main_token = tree.nodes.items(.main_token)[node],
+            .align_node = extra.align_node,
+            .sentinel = extra.sentinel,
+            .bit_range_start = 0,
+            .bit_range_end = 0,
+            .child_type = data.rhs,
+        });
+    }
+
+    pub fn ptrTypeBitRange(tree: Tree, node: Node.Index) Full.PtrType {
+        assert(tree.nodes.items(.tag)[node] == .PtrTypeBitRange);
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.lhs, Node.PtrTypeBitRange);
+        return tree.fullPtrType(.{
+            .main_token = tree.nodes.items(.main_token)[node],
+            .align_node = extra.align_node,
+            .sentinel = extra.sentinel,
+            .bit_range_start = extra.bit_range_start,
+            .bit_range_end = extra.bit_range_end,
+            .child_type = data.rhs,
+        });
+    }
+
     pub fn containerDeclTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .ContainerDeclTwo or
             tree.nodes.items(.tag)[node] == .ContainerDeclTwoComma);
@@ -1195,6 +1261,64 @@ pub const Tree = struct {
         return result;
     }
 
+    fn fullPtrType(tree: Tree, info: Full.PtrType.Ast) Full.PtrType {
+        const token_tags = tree.tokens.items(.tag);
+        // TODO: looks like stage1 isn't quite smart enough to handle enum
+        // literals in some places here
+        const Kind = Full.PtrType.Kind;
+        const kind: Kind = switch (token_tags[info.main_token]) {
+            .Asterisk => switch (token_tags[info.main_token + 1]) {
+                .RBracket => .many,
+                .Colon => .sentinel,
+                .Identifier => if (token_tags[info.main_token - 1] == .LBracket) Kind.c else .one,
+                else => .one,
+            },
+            .LBracket => switch (token_tags[info.main_token + 1]) {
+                .RBracket => Kind.slice,
+                .Colon => .slice_sentinel,
+                else => unreachable,
+            },
+            else => unreachable,
+        };
+        var result: Full.PtrType = .{
+            .kind = kind,
+            .allowzero_token = null,
+            .const_token = null,
+            .volatile_token = null,
+            .ast = info,
+        };
+        // We need to be careful that we don't iterate over any sub-expressions
+        // here while looking for modifiers as that could result in false
+        // positives. Therefore, start after a sentinel if there is one and
+        // skip over any align node and bit range nodes.
+        var i = if (kind == .sentinel or kind == .slice_sentinel) blk: {
+            assert(info.sentinel != 0);
+            break :blk tree.lastToken(info.sentinel) + 1;
+        } else blk: {
+            assert(info.sentinel == 0);
+            break :blk info.main_token;
+        };
+        const end = tree.firstToken(info.child_type);
+        while (i < end) : (i += 1) {
+            switch (token_tags[i]) {
+                .Keyword_allowzero => result.allowzero_token = i,
+                .Keyword_const => result.const_token = i,
+                .Keyword_volatile => result.volatile_token = i,
+                .Keyword_align => {
+                    assert(info.align_node != 0);
+                    if (info.bit_range_end != 0) {
+                        assert(info.bit_range_start != 0);
+                        i = tree.lastToken(info.bit_range_end) + 1;
+                    } else {
+                        i = tree.lastToken(info.align_node) + 1;
+                    }
+                },
+                else => {},
+            }
+        }
+        return result;
+    }
+
     fn fullContainerDecl(tree: Tree, info: Full.ContainerDecl.Ast) Full.ContainerDecl {
         const token_tags = tree.tokens.items(.tag);
         var result: Full.ContainerDecl = .{
@@ -1302,6 +1426,32 @@ pub const Full = struct {
         };
     };
 
+    pub const PtrType = struct {
+        kind: Kind,
+        allowzero_token: ?TokenIndex,
+        const_token: ?TokenIndex,
+        volatile_token: ?TokenIndex,
+        ast: Ast,
+
+        pub const Kind = enum {
+            one,
+            many,
+            sentinel,
+            c,
+            slice,
+            slice_sentinel,
+        };
+
+        pub const Ast = struct {
+            main_token: TokenIndex,
+            align_node: Node.Index,
+            sentinel: Node.Index,
+            bit_range_start: Node.Index,
+            bit_range_end: Node.Index,
+            child_type: Node.Index,
+        };
+    };
+
     pub const ContainerDecl = struct {
         layout_token: ?TokenIndex,
         ast: Ast,
@@ -1696,16 +1846,19 @@ pub const Node = struct {
         /// `[*]align(lhs) rhs`. lhs can be omitted.
         /// `*align(lhs) rhs`. lhs can be omitted.
         /// `[]rhs`.
+        /// main_token is the asterisk if a pointer or the lbrace if a slice
         PtrTypeAligned,
         /// `[*:lhs]rhs`. lhs can be omitted.
         /// `*rhs`.
         /// `[:lhs]rhs`.
+        /// main_token is the asterisk if a pointer or the lbrace if a slice
         PtrTypeSentinel,
         /// lhs is index into PtrType. rhs is the element type expression.
+        /// main_token is the asterisk if a pointer or the lbrace if a slice
         PtrType,
-        /// lhs is index into SliceType. rhs is the element type expression.
-        /// Can be pointer or slice, depending on main_token.
-        SliceType,
+        /// lhs is index into PtrTypeBitRange. rhs is the element type expression.
+        /// main_token is the asterisk if a pointer or the lbrace if a slice
+        PtrTypeBitRange,
         /// `lhs[rhs..]`
         /// main_token is the `[`.
         SliceOpen,
@@ -1954,14 +2107,15 @@ pub const Node = struct {
     pub const PtrType = struct {
         sentinel: Index,
         align_node: Index,
-        bit_range_start: Index,
-        bit_range_end: Index,
     };
 
-    pub const SliceType = struct {
+    pub const PtrTypeBitRange = struct {
         sentinel: Index,
         align_node: Index,
+        bit_range_start: Index,
+        bit_range_end: Index,
     };
+
     pub const SubRange = struct {
         /// Index into sub_list.
         start: Index,
lib/std/zig/parse.zig
@@ -1618,10 +1618,10 @@ const Parser = struct {
                     });
                 } else {
                     return p.addNode(.{
-                        .tag = .PtrType,
+                        .tag = .PtrTypeBitRange,
                         .main_token = asterisk,
                         .data = .{
-                            .lhs = try p.addExtra(Node.PtrType{
+                            .lhs = try p.addExtra(Node.PtrTypeBitRange{
                                 .sentinel = 0,
                                 .align_node = mods.align_node,
                                 .bit_range_start = mods.bit_range_start,
@@ -1648,10 +1648,10 @@ const Parser = struct {
                         });
                     } else {
                         break :inner try p.addNode(.{
-                            .tag = .PtrType,
+                            .tag = .PtrTypeBitRange,
                             .main_token = asterisk,
                             .data = .{
-                                .lhs = try p.addExtra(Node.PtrType{
+                                .lhs = try p.addExtra(Node.PtrTypeBitRange{
                                     .sentinel = 0,
                                     .align_node = mods.align_node,
                                     .bit_range_start = mods.bit_range_start,
@@ -1713,10 +1713,10 @@ const Parser = struct {
                             });
                         } else {
                             return p.addNode(.{
-                                .tag = .SliceType,
+                                .tag = .PtrType,
                                 .main_token = asterisk,
                                 .data = .{
-                                    .lhs = try p.addExtra(.{
+                                    .lhs = try p.addExtra(Node.PtrType{
                                         .sentinel = sentinel,
                                         .align_node = mods.align_node,
                                     }),
@@ -1726,10 +1726,10 @@ const Parser = struct {
                         }
                     } else {
                         return p.addNode(.{
-                            .tag = .PtrType,
+                            .tag = .PtrTypeBitRange,
                             .main_token = asterisk,
                             .data = .{
-                                .lhs = try p.addExtra(.{
+                                .lhs = try p.addExtra(Node.PtrTypeBitRange{
                                     .sentinel = sentinel,
                                     .align_node = mods.align_node,
                                     .bit_range_start = mods.bit_range_start,
@@ -1777,10 +1777,10 @@ const Parser = struct {
                             });
                         } else {
                             return p.addNode(.{
-                                .tag = .SliceType,
+                                .tag = .PtrType,
                                 .main_token = lbracket,
                                 .data = .{
-                                    .lhs = try p.addExtra(.{
+                                    .lhs = try p.addExtra(Node.PtrType{
                                         .sentinel = sentinel,
                                         .align_node = mods.align_node,
                                     }),
lib/std/zig/parser_test.zig
@@ -345,7 +345,59 @@ test "zig fmt: builtin call with trailing comma" {
 //        \\
 //    );
 //}
-//
+
+test "zig fmt: pointer-to-one with modifiers" {
+    try testCanonical(
+        \\const x: *u32 = undefined;
+        \\const y: *allowzero align(8) const volatile u32 = undefined;
+        \\const z: *allowzero align(8:4:2) const volatile u32 = undefined;
+        \\
+    );
+}
+
+test "zig fmt: pointer-to-many with modifiers" {
+    try testCanonical(
+        \\const x: [*]u32 = undefined;
+        \\const y: [*]allowzero align(8) const volatile u32 = undefined;
+        \\const z: [*]allowzero align(8:4:2) const volatile u32 = undefined;
+        \\
+    );
+}
+
+test "zig fmt: sentinel pointer with modifiers" {
+    try testCanonical(
+        \\const x: [*:42]u32 = undefined;
+        \\const y: [*:42]allowzero align(8) const volatile u32 = undefined;
+        \\const y: [*:42]allowzero align(8:4:2) const volatile u32 = undefined;
+        \\
+    );
+}
+
+test "zig fmt: c pointer with modifiers" {
+    try testCanonical(
+        \\const x: [*c]u32 = undefined;
+        \\const y: [*c]allowzero align(8) const volatile u32 = undefined;
+        \\const z: [*c]allowzero align(8:4:2) const volatile u32 = undefined;
+        \\
+    );
+}
+
+test "zig fmt: slice with modifiers" {
+    try testCanonical(
+        \\const x: []u32 = undefined;
+        \\const y: []allowzero align(8) const volatile u32 = undefined;
+        \\
+    );
+}
+
+test "zig fmt: sentinel slice with modifiers" {
+    try testCanonical(
+        \\const x: [:42]u32 = undefined;
+        \\const y: [:42]allowzero align(8) const volatile u32 = undefined;
+        \\
+    );
+}
+
 //test "zig fmt: anon literal in array" {
 //    try testCanonical(
 //        \\var arr: [2]Foo = .{
lib/std/zig/render.zig
@@ -370,120 +370,10 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         .ArrayType => return renderArrayType(ais, tree, tree.arrayType(node), space),
         .ArrayTypeSentinel => return renderArrayType(ais, tree, tree.arrayTypeSentinel(node), space),
 
-        .PtrType => unreachable, // TODO
-        .PtrTypeAligned => unreachable, // TODO
-        .PtrTypeSentinel => unreachable, // TODO
-        //.PtrType => {
-        //    const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base);
-        //    const op_tok_id = tree.token_tags[ptr_type.op_token];
-        //    switch (op_tok_id) {
-        //        .Asterisk, .AsteriskAsterisk => try ais.writer().writeByte('*'),
-        //        .LBracket => if (tree.token_tags[ptr_type.op_token + 2] == .Identifier)
-        //            try ais.writer().writeAll("[*c")
-        //        else
-        //            try ais.writer().writeAll("[*"),
-        //        else => unreachable,
-        //    }
-        //    if (ptr_type.ptr_info.sentinel) |sentinel| {
-        //        const colon_token = tree.prevToken(sentinel.firstToken());
-        //        try renderToken(ais, tree, colon_token, Space.None); // :
-        //        const sentinel_space = switch (op_tok_id) {
-        //            .LBracket => Space.None,
-        //            else => Space.Space,
-        //        };
-        //        try renderExpression(ais, tree, sentinel, sentinel_space);
-        //    }
-        //    switch (op_tok_id) {
-        //        .Asterisk, .AsteriskAsterisk => {},
-        //        .LBracket => try ais.writer().writeByte(']'),
-        //        else => unreachable,
-        //    }
-        //    if (ptr_type.ptr_info.allowzero_token) |allowzero_token| {
-        //        try renderToken(ais, tree, allowzero_token, Space.Space); // allowzero
-        //    }
-        //    if (ptr_type.ptr_info.align_info) |align_info| {
-        //        const lparen_token = tree.prevToken(align_info.node.firstToken());
-        //        const align_token = tree.prevToken(lparen_token);
-
-        //        try renderToken(ais, tree, align_token, Space.None); // align
-        //        try renderToken(ais, tree, lparen_token, Space.None); // (
-
-        //        try renderExpression(ais, tree, align_info.node, Space.None);
-
-        //        if (align_info.bit_range) |bit_range| {
-        //            const colon1 = tree.prevToken(bit_range.start.firstToken());
-        //            const colon2 = tree.prevToken(bit_range.end.firstToken());
-
-        //            try renderToken(ais, tree, colon1, Space.None); // :
-        //            try renderExpression(ais, tree, bit_range.start, Space.None);
-        //            try renderToken(ais, tree, colon2, Space.None); // :
-        //            try renderExpression(ais, tree, bit_range.end, Space.None);
-
-        //            const rparen_token = tree.nextToken(bit_range.end.lastToken());
-        //            try renderToken(ais, tree, rparen_token, Space.Space); // )
-        //        } else {
-        //            const rparen_token = tree.nextToken(align_info.node.lastToken());
-        //            try renderToken(ais, tree, rparen_token, Space.Space); // )
-        //        }
-        //    }
-        //    if (ptr_type.ptr_info.const_token) |const_token| {
-        //        try renderToken(ais, tree, const_token, Space.Space); // const
-        //    }
-        //    if (ptr_type.ptr_info.volatile_token) |volatile_token| {
-        //        try renderToken(ais, tree, volatile_token, Space.Space); // volatile
-        //    }
-        //    return renderExpression(ais, tree, ptr_type.rhs, space);
-        //},
-
-        .SliceType => unreachable, // TODO
-        //.SliceType => {
-        //    const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base);
-        //    try renderToken(ais, tree, slice_type.op_token, Space.None); // [
-        //    if (slice_type.ptr_info.sentinel) |sentinel| {
-        //        const colon_token = tree.prevToken(sentinel.firstToken());
-        //        try renderToken(ais, tree, colon_token, Space.None); // :
-        //        try renderExpression(ais, tree, sentinel, Space.None);
-        //        try renderToken(ais, tree, tree.nextToken(sentinel.lastToken()), Space.None); // ]
-        //    } else {
-        //        try renderToken(ais, tree, tree.nextToken(slice_type.op_token), Space.None); // ]
-        //    }
-
-        //    if (slice_type.ptr_info.allowzero_token) |allowzero_token| {
-        //        try renderToken(ais, tree, allowzero_token, Space.Space); // allowzero
-        //    }
-        //    if (slice_type.ptr_info.align_info) |align_info| {
-        //        const lparen_token = tree.prevToken(align_info.node.firstToken());
-        //        const align_token = tree.prevToken(lparen_token);
-
-        //        try renderToken(ais, tree, align_token, Space.None); // align
-        //        try renderToken(ais, tree, lparen_token, Space.None); // (
-
-        //        try renderExpression(ais, tree, align_info.node, Space.None);
-
-        //        if (align_info.bit_range) |bit_range| {
-        //            const colon1 = tree.prevToken(bit_range.start.firstToken());
-        //            const colon2 = tree.prevToken(bit_range.end.firstToken());
-
-        //            try renderToken(ais, tree, colon1, Space.None); // :
-        //            try renderExpression(ais, tree, bit_range.start, Space.None);
-        //            try renderToken(ais, tree, colon2, Space.None); // :
-        //            try renderExpression(ais, tree, bit_range.end, Space.None);
-
-        //            const rparen_token = tree.nextToken(bit_range.end.lastToken());
-        //            try renderToken(ais, tree, rparen_token, Space.Space); // )
-        //        } else {
-        //            const rparen_token = tree.nextToken(align_info.node.lastToken());
-        //            try renderToken(ais, tree, rparen_token, Space.Space); // )
-        //        }
-        //    }
-        //    if (slice_type.ptr_info.const_token) |const_token| {
-        //        try renderToken(ais, tree, const_token, Space.Space);
-        //    }
-        //    if (slice_type.ptr_info.volatile_token) |volatile_token| {
-        //        try renderToken(ais, tree, volatile_token, Space.Space);
-        //    }
-        //    return renderExpression(ais, tree, slice_type.rhs, space);
-        //},
+        .PtrTypeAligned => return renderPtrType(ais, tree, tree.ptrTypeAligned(node), space),
+        .PtrTypeSentinel => return renderPtrType(ais, tree, tree.ptrTypeSentinel(node), space),
+        .PtrType => return renderPtrType(ais, tree, tree.ptrType(node), space),
+        .PtrTypeBitRange => return renderPtrType(ais, tree, tree.ptrTypeBitRange(node), space),
 
         .ArrayInitOne => {
             var elements: [1]ast.Node.Index = undefined;
@@ -1180,6 +1070,78 @@ fn renderArrayType(
     return renderExpression(ais, tree, array_type.ast.elem_type, space);
 }
 
+fn renderPtrType(
+    ais: *Ais,
+    tree: ast.Tree,
+    ptr_type: ast.Full.PtrType,
+    space: Space,
+) Error!void {
+    switch (ptr_type.kind) {
+        .one => {
+            try renderToken(ais, tree, ptr_type.ast.main_token, .None); // asterisk
+        },
+        .many => {
+            try renderToken(ais, tree, ptr_type.ast.main_token - 1, .None); // lbracket
+            try renderToken(ais, tree, ptr_type.ast.main_token, .None); // asterisk
+            try renderToken(ais, tree, ptr_type.ast.main_token + 1, .None); // rbracket
+        },
+        .sentinel => {
+            try renderToken(ais, tree, ptr_type.ast.main_token - 1, .None); // lbracket
+            try renderToken(ais, tree, ptr_type.ast.main_token, .None); // asterisk
+            try renderToken(ais, tree, ptr_type.ast.main_token + 1, .None); // colon
+            try renderExpression(ais, tree, ptr_type.ast.sentinel, .None);
+            try renderToken(ais, tree, tree.lastToken(ptr_type.ast.sentinel) + 1, .None); // rbracket
+        },
+        .c => {
+            try renderToken(ais, tree, ptr_type.ast.main_token - 1, .None); // lbracket
+            try renderToken(ais, tree, ptr_type.ast.main_token, .None); // asterisk
+            try renderToken(ais, tree, ptr_type.ast.main_token + 1, .None); // c
+            try renderToken(ais, tree, ptr_type.ast.main_token + 2, .None); // rbracket
+        },
+        .slice => {
+            try renderToken(ais, tree, ptr_type.ast.main_token, .None); // lbracket
+            try renderToken(ais, tree, ptr_type.ast.main_token + 1, .None); // rbracket
+        },
+        .slice_sentinel => {
+            try renderToken(ais, tree, ptr_type.ast.main_token, .None); // lbracket
+            try renderToken(ais, tree, ptr_type.ast.main_token + 1, .None); // colon
+            try renderExpression(ais, tree, ptr_type.ast.sentinel, .None);
+            try renderToken(ais, tree, tree.lastToken(ptr_type.ast.sentinel) + 1, .None); // rbracket
+        },
+    }
+
+    if (ptr_type.allowzero_token) |allowzero_token| {
+        try renderToken(ais, tree, allowzero_token, .Space);
+    }
+
+    if (ptr_type.ast.align_node != 0) {
+        const align_first = tree.firstToken(ptr_type.ast.align_node);
+        try renderToken(ais, tree, align_first - 2, .None); // align
+        try renderToken(ais, tree, align_first - 1, .None); // lparen
+        try renderExpression(ais, tree, ptr_type.ast.align_node, .None);
+        if (ptr_type.ast.bit_range_start != 0) {
+            assert(ptr_type.ast.bit_range_end != 0);
+            try renderToken(ais, tree, tree.firstToken(ptr_type.ast.bit_range_start) - 1, .None); // colon
+            try renderExpression(ais, tree, ptr_type.ast.bit_range_start, .None);
+            try renderToken(ais, tree, tree.firstToken(ptr_type.ast.bit_range_end) - 1, .None); // colon
+            try renderExpression(ais, tree, ptr_type.ast.bit_range_end, .None);
+            try renderToken(ais, tree, tree.lastToken(ptr_type.ast.bit_range_end) + 1, .Space); // rparen
+        } else {
+            try renderToken(ais, tree, tree.lastToken(ptr_type.ast.align_node) + 1, .Space); // rparen
+        }
+    }
+
+    if (ptr_type.const_token) |const_token| {
+        try renderToken(ais, tree, const_token, .Space);
+    }
+
+    if (ptr_type.volatile_token) |volatile_token| {
+        try renderToken(ais, tree, volatile_token, .Space);
+    }
+
+    try renderExpression(ais, tree, ptr_type.ast.child_type, space);
+}
+
 fn renderAsmOutput(
     allocator: *mem.Allocator,
     ais: *Ais,