authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2021-03-15 14:41:15+01:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2021-03-15 14:55:34+01:00
log48efa3bcb678ac2f869554b4c5383f865ad73a33
tree15bf7ad888efba6fef3f514f2a501c5129e68ef6
parentb4db03d8bb29f0cded857300df460b4247e85670

zig fmt: simplify and improve consistency of if/for/while handling

The main realization here was that getting rid of the early returns in renderWhile() and rewriting the logic into a mostly unified execution path took things from ~200 lines to ~100 lines and improved consistency by deduplicating code. Also add several test cases and fix a few issues along the way: Fixes https://github.com/ziglang/zig/issues/6114 Fixes https://github.com/ziglang/zig/issues/8022

2 files changed, 296 insertions(+), 147 deletions(-)

lib/std/zig/parser_test.zig+217-1
......@@ -3186,7 +3186,7 @@ test "zig fmt: for" {
31863186
31873187test "zig fmt: for if" {
31883188 try testCanonical(
3189 \\test "for if" {
3189 \\test {
31903190 \\ for (a) |x| if (x) f(x);
31913191 \\
31923192 \\ for (a) |x| if (x)
......@@ -3209,6 +3209,131 @@ test "zig fmt: for if" {
32093209 );
32103210}
32113211
3212test "zig fmt: if for" {
3213 try testCanonical(
3214 \\test {
3215 \\ if (a) for (x) |x| f(x);
3216 \\
3217 \\ if (a) for (x) |x|
3218 \\ f(x);
3219 \\
3220 \\ if (a) for (x) |x| {
3221 \\ f(x);
3222 \\ };
3223 \\
3224 \\ if (a)
3225 \\ for (x) |x|
3226 \\ f(x);
3227 \\
3228 \\ if (a)
3229 \\ for (x) |x| {
3230 \\ f(x);
3231 \\ };
3232 \\}
3233 \\
3234 );
3235}
3236
3237test "zig fmt: while if" {
3238 try testCanonical(
3239 \\test {
3240 \\ while (a) if (x) f(x);
3241 \\
3242 \\ while (a) if (x)
3243 \\ f(x);
3244 \\
3245 \\ while (a) if (x) {
3246 \\ f(x);
3247 \\ };
3248 \\
3249 \\ while (a)
3250 \\ if (x)
3251 \\ f(x);
3252 \\
3253 \\ while (a)
3254 \\ if (x) {
3255 \\ f(x);
3256 \\ };
3257 \\}
3258 \\
3259 );
3260}
3261
3262test "zig fmt: if while" {
3263 try testCanonical(
3264 \\test {
3265 \\ if (a) while (x) : (cont) f(x);
3266 \\
3267 \\ if (a) while (x) : (cont)
3268 \\ f(x);
3269 \\
3270 \\ if (a) while (x) : (cont) {
3271 \\ f(x);
3272 \\ };
3273 \\
3274 \\ if (a)
3275 \\ while (x) : (cont)
3276 \\ f(x);
3277 \\
3278 \\ if (a)
3279 \\ while (x) : (cont) {
3280 \\ f(x);
3281 \\ };
3282 \\}
3283 \\
3284 );
3285}
3286
3287test "zig fmt: while for" {
3288 try testCanonical(
3289 \\test {
3290 \\ while (a) for (x) |x| f(x);
3291 \\
3292 \\ while (a) for (x) |x|
3293 \\ f(x);
3294 \\
3295 \\ while (a) for (x) |x| {
3296 \\ f(x);
3297 \\ };
3298 \\
3299 \\ while (a)
3300 \\ for (x) |x|
3301 \\ f(x);
3302 \\
3303 \\ while (a)
3304 \\ for (x) |x| {
3305 \\ f(x);
3306 \\ };
3307 \\}
3308 \\
3309 );
3310}
3311
3312test "zig fmt: for while" {
3313 try testCanonical(
3314 \\test {
3315 \\ for (a) |a| while (x) |x| f(x);
3316 \\
3317 \\ for (a) |a| while (x) |x|
3318 \\ f(x);
3319 \\
3320 \\ for (a) |a| while (x) |x| {
3321 \\ f(x);
3322 \\ };
3323 \\
3324 \\ for (a) |a|
3325 \\ while (x) |x|
3326 \\ f(x);
3327 \\
3328 \\ for (a) |a|
3329 \\ while (x) |x| {
3330 \\ f(x);
3331 \\ };
3332 \\}
3333 \\
3334 );
3335}
3336
32123337test "zig fmt: if" {
32133338 try testCanonical(
32143339 \\test "if" {
......@@ -3258,6 +3383,82 @@ test "zig fmt: if" {
32583383 );
32593384}
32603385
3386test "zig fmt: fix single statement if/for/while line breaks" {
3387 try testTransform(
3388 \\test {
3389 \\ if (cond) a
3390 \\ else b;
3391 \\
3392 \\ if (cond)
3393 \\ a
3394 \\ else b;
3395 \\
3396 \\ for (xs) |x| foo()
3397 \\ else bar();
3398 \\
3399 \\ for (xs) |x|
3400 \\ foo()
3401 \\ else bar();
3402 \\
3403 \\ while (a) : (b) foo()
3404 \\ else bar();
3405 \\
3406 \\ while (a) : (b)
3407 \\ foo()
3408 \\ else bar();
3409 \\}
3410 \\
3411 ,
3412 \\test {
3413 \\ if (cond) a else b;
3414 \\
3415 \\ if (cond)
3416 \\ a
3417 \\ else
3418 \\ b;
3419 \\
3420 \\ for (xs) |x| foo() else bar();
3421 \\
3422 \\ for (xs) |x|
3423 \\ foo()
3424 \\ else
3425 \\ bar();
3426 \\
3427 \\ while (a) : (b) foo() else bar();
3428 \\
3429 \\ while (a) : (b)
3430 \\ foo()
3431 \\ else
3432 \\ bar();
3433 \\}
3434 \\
3435 );
3436}
3437
3438test "zig fmt: anon struct/array literal in if" {
3439 try testCanonical(
3440 \\test {
3441 \\ const a = if (cond) .{
3442 \\ 1, 2,
3443 \\ 3, 4,
3444 \\ } else .{
3445 \\ 1,
3446 \\ 2,
3447 \\ 3,
3448 \\ };
3449 \\
3450 \\ const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{
3451 \\ .rl = .ref,
3452 \\ .tag = .switchbr_ref,
3453 \\ } else .{
3454 \\ .rl = .none,
3455 \\ .tag = .switchbr,
3456 \\ };
3457 \\}
3458 \\
3459 );
3460}
3461
32613462test "zig fmt: defer" {
32623463 try testCanonical(
32633464 \\test "defer" {
......@@ -3845,6 +4046,7 @@ test "zig fmt: comments in ternary ifs" {
38454046 \\ // Comment
38464047 \\ 1
38474048 \\else
4049 \\ // Comment
38484050 \\ 0;
38494051 \\
38504052 \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
......@@ -3852,6 +4054,20 @@ test "zig fmt: comments in ternary ifs" {
38524054 );
38534055}
38544056
4057test "zig fmt: while statement in blockless if" {
4058 try testCanonical(
4059 \\pub fn main() void {
4060 \\ const zoom_node = if (focused_node == layout_first)
4061 \\ while (it.next()) |node| {
4062 \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
4063 \\ } else null
4064 \\ else
4065 \\ focused_node;
4066 \\}
4067 \\
4068 );
4069}
4070
38554071test "zig fmt: test comments in field access chain" {
38564072 try testCanonical(
38574073 \\pub const str = struct {
lib/std/zig/render.zig+79-146
......@@ -1018,170 +1018,95 @@ fn renderWhile(gpa: *Allocator, ais: *Ais, tree: ast.Tree, while_node: ast.full.
10181018 try renderToken(ais, tree, inline_token, .space); // inline
10191019 }
10201020
1021 try renderToken(ais, tree, while_node.ast.while_token, .space); // if
1021 try renderToken(ais, tree, while_node.ast.while_token, .space); // if/for/while
10221022 try renderToken(ais, tree, while_node.ast.while_token + 1, .none); // lparen
10231023 try renderExpression(gpa, ais, tree, while_node.ast.cond_expr, .none); // condition
10241024
1025 const then_tag = node_tags[while_node.ast.then_expr];
1026 if (nodeIsBlock(then_tag) and !nodeIsIf(then_tag)) {
1027 if (while_node.payload_token) |payload_token| {
1028 const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented())
1029 Space.newline
1030 else
1031 Space.space;
1032 try renderWhilePayload(gpa, ais, tree, payload_token, brace_space);
1033 } else {
1034 const rparen = tree.lastToken(while_node.ast.cond_expr) + 1;
1035 const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented())
1036 Space.newline
1037 else
1038 Space.space;
1039 try renderToken(ais, tree, rparen, brace_space); // rparen
1040 }
1041 if (while_node.ast.cont_expr != 0) {
1042 const rparen = tree.lastToken(while_node.ast.cont_expr) + 1;
1043 const lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
1044 try renderToken(ais, tree, lparen - 1, .space); // :
1045 try renderToken(ais, tree, lparen, .none); // lparen
1046 try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none);
1047 const brace_space: Space = if (ais.isLineOverIndented()) .newline else .space;
1048 try renderToken(ais, tree, rparen, brace_space); // rparen
1049 }
1050 if (while_node.ast.else_expr != 0) {
1051 try renderExpression(gpa, ais, tree, while_node.ast.then_expr, Space.space);
1052 try renderToken(ais, tree, while_node.else_token, .space); // else
1053 if (while_node.error_token) |error_token| {
1054 try renderToken(ais, tree, error_token - 1, .none); // |
1055 try renderToken(ais, tree, error_token, .none); // identifier
1056 try renderToken(ais, tree, error_token + 1, .space); // |
1057 }
1058 return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
1059 } else {
1060 return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
1061 }
1062 }
1063
1064 const rparen = tree.lastToken(while_node.ast.cond_expr) + 1;
1065 const first_then_token = tree.firstToken(while_node.ast.then_expr);
1066 const last_then_token = tree.lastToken(while_node.ast.then_expr);
1067 const src_has_newline = !tree.tokensOnSameLine(rparen, last_then_token);
1025 var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
10681026
1069 if (src_has_newline) {
1070 const newline_before_then_token = !tree.tokensOnSameLine(rparen, first_then_token);
1071 const space_before_then_token: Space = if (newline_before_then_token) .newline else .space;
1072 const indent_expression = !nodeIsIf(then_tag) or newline_before_then_token;
1073
1074 if (while_node.payload_token) |payload_token| {
1075 const after_space: Space = if (while_node.ast.cont_expr != 0) .space else space_before_then_token;
1076 try renderWhilePayload(gpa, ais, tree, payload_token, after_space);
1077 } else {
1078 if (indent_expression) ais.pushIndent();
1079 const after_space: Space = if (while_node.ast.cont_expr != 0) .space else space_before_then_token;
1080 try renderToken(ais, tree, rparen, after_space); // rparen
1081 if (indent_expression) ais.popIndent();
1082 }
1083 if (while_node.ast.cont_expr != 0) {
1084 const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1;
1085 const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
1086 try renderToken(ais, tree, cont_lparen - 1, .space); // :
1087 try renderToken(ais, tree, cont_lparen, .none); // lparen
1088 try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none);
1089 try renderToken(ais, tree, cont_rparen, space_before_then_token); // rparen
1090 }
1091 if (while_node.ast.else_expr != 0) {
1092 if (indent_expression) ais.pushIndent();
1093 try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .newline);
1094 if (indent_expression) ais.popIndent();
1095 const else_is_block = nodeIsBlock(node_tags[while_node.ast.else_expr]);
1096 if (else_is_block) {
1097 try renderToken(ais, tree, while_node.else_token, .space); // else
1098 if (while_node.error_token) |error_token| {
1099 try renderToken(ais, tree, error_token - 1, .none); // |
1100 try renderToken(ais, tree, error_token, .none); // identifier
1101 try renderToken(ais, tree, error_token + 1, .space); // |
1102 }
1103 return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
1027 if (while_node.payload_token) |payload_token| {
1028 try renderToken(ais, tree, last_prefix_token, .space);
1029 try renderToken(ais, tree, payload_token - 1, .none); // |
1030 const ident = blk: {
1031 if (token_tags[payload_token] == .asterisk) {
1032 try renderToken(ais, tree, payload_token, .none); // *
1033 break :blk payload_token + 1;
11041034 } else {
1105 if (while_node.error_token) |error_token| {
1106 try renderToken(ais, tree, while_node.else_token, .space); // else
1107 try renderToken(ais, tree, error_token - 1, .none); // |
1108 try renderToken(ais, tree, error_token, .none); // identifier
1109 try renderToken(ais, tree, error_token + 1, .space); // |
1110 } else {
1111 try renderToken(ais, tree, while_node.else_token, .newline); // else
1112 }
1113 if (indent_expression) {
1114 return renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space);
1115 } else {
1116 return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
1117 }
1035 break :blk payload_token;
11181036 }
1119 } else {
1120 if (indent_expression) {
1121 return renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space);
1037 };
1038 try renderToken(ais, tree, ident, .none); // identifier
1039 const pipe = blk: {
1040 if (token_tags[ident + 1] == .comma) {
1041 try renderToken(ais, tree, ident + 1, .space); // ,
1042 try renderToken(ais, tree, ident + 2, .none); // index
1043 break :blk ident + 3;
11221044 } else {
1123 return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
1045 break :blk ident + 1;
11241046 }
1125 }
1126 }
1127
1128 // Render everything on a single line.
1129
1130 if (while_node.payload_token) |payload_token| {
1131 assert(payload_token - 2 == rparen);
1132 try renderWhilePayload(gpa, ais, tree, payload_token, .space);
1133 } else {
1134 try renderToken(ais, tree, rparen, .space); // )
1047 };
1048 last_prefix_token = pipe;
11351049 }
11361050
11371051 if (while_node.ast.cont_expr != 0) {
1138 const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1;
1139 const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
1140 try renderToken(ais, tree, cont_lparen - 1, .space); // :
1141 try renderToken(ais, tree, cont_lparen, .none); // lparen
1052 try renderToken(ais, tree, last_prefix_token, .space);
1053 const lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
1054 try renderToken(ais, tree, lparen - 1, .space); // :
1055 try renderToken(ais, tree, lparen, .none); // lparen
11421056 try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none);
1143 try renderToken(ais, tree, cont_rparen, .space); // rparen
1057 last_prefix_token = tree.lastToken(while_node.ast.cont_expr) + 1; // rparen
1058 }
1059
1060 const then_expr_is_block = nodeIsBlock(node_tags[while_node.ast.then_expr]);
1061 const indent_then_expr = !then_expr_is_block and
1062 !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(while_node.ast.then_expr));
1063 if (indent_then_expr or (then_expr_is_block and ais.isLineOverIndented())) {
1064 ais.pushIndentNextLine();
1065 try renderToken(ais, tree, last_prefix_token, .newline);
1066 ais.popIndent();
1067 } else {
1068 try renderToken(ais, tree, last_prefix_token, .space);
11441069 }
11451070
11461071 if (while_node.ast.else_expr != 0) {
1147 try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space);
1148 try renderToken(ais, tree, while_node.else_token, .space); // else
1072 const first_else_expr_tok = tree.firstToken(while_node.ast.else_expr);
1073
1074 if (indent_then_expr) {
1075 ais.pushIndent();
1076 try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .newline);
1077 ais.popIndent();
1078 } else {
1079 try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space);
1080 }
1081
1082 var last_else_token = while_node.else_token;
11491083
11501084 if (while_node.error_token) |error_token| {
1085 try renderToken(ais, tree, while_node.else_token, .space); // else
11511086 try renderToken(ais, tree, error_token - 1, .none); // |
11521087 try renderToken(ais, tree, error_token, .none); // identifier
1153 try renderToken(ais, tree, error_token + 1, .space); // |
1088 last_else_token = error_token + 1; // |
11541089 }
11551090
1156 return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
1157 } else {
1158 return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
1159 }
1160}
1161
1162fn renderWhilePayload(gpa: *Allocator, ais: *Ais, tree: ast.Tree, payload_token: ast.TokenIndex, space: Space) Error!void {
1163 const token_tags = tree.tokens.items(.tag);
1164 try renderToken(ais, tree, payload_token - 2, .space); // rparen
1165 try renderToken(ais, tree, payload_token - 1, .none); // |
1166 const ident = blk: {
1167 if (token_tags[payload_token] == .asterisk) {
1168 try renderToken(ais, tree, payload_token, .none); // *
1169 break :blk payload_token + 1;
1091 const indent_else_expr = indent_then_expr and
1092 !nodeIsBlock(node_tags[while_node.ast.else_expr]) and
1093 !nodeIsIfForWhileSwitch(node_tags[while_node.ast.else_expr]);
1094 if (indent_else_expr) {
1095 ais.pushIndentNextLine();
1096 try renderToken(ais, tree, last_else_token, .newline);
1097 ais.popIndent();
1098 try renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space);
11701099 } else {
1171 break :blk payload_token;
1100 try renderToken(ais, tree, last_else_token, .space);
1101 try renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
11721102 }
1173 };
1174 try renderToken(ais, tree, ident, .none); // identifier
1175 const pipe = blk: {
1176 if (token_tags[ident + 1] == .comma) {
1177 try renderToken(ais, tree, ident + 1, .space); // ,
1178 try renderToken(ais, tree, ident + 2, .none); // index
1179 break :blk ident + 3;
1103 } else {
1104 if (indent_then_expr) {
1105 try renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space);
11801106 } else {
1181 break :blk ident + 1;
1107 try renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
11821108 }
1183 };
1184 try renderToken(ais, tree, pipe, space); // |
1109 }
11851110}
11861111
11871112fn renderContainerField(
......@@ -2469,6 +2394,21 @@ fn nodeIsBlock(tag: ast.Node.Tag) bool {
24692394 .block_semicolon,
24702395 .block_two,
24712396 .block_two_semicolon,
2397 .struct_init_dot,
2398 .struct_init_dot_comma,
2399 .struct_init_dot_two,
2400 .struct_init_dot_two_comma,
2401 .array_init_dot,
2402 .array_init_dot_comma,
2403 .array_init_dot_two,
2404 .array_init_dot_two_comma,
2405 => true,
2406 else => false,
2407 };
2408}
2409
2410fn nodeIsIfForWhileSwitch(tag: ast.Node.Tag) bool {
2411 return switch (tag) {
24722412 .@"if",
24732413 .if_simple,
24742414 .@"for",
......@@ -2483,13 +2423,6 @@ fn nodeIsBlock(tag: ast.Node.Tag) bool {
24832423 };
24842424}
24852425
2486fn nodeIsIf(tag: ast.Node.Tag) bool {
2487 return switch (tag) {
2488 .@"if", .if_simple => true,
2489 else => false,
2490 };
2491}
2492
24932426fn nodeCausesSliceOpSpace(tag: ast.Node.Tag) bool {
24942427 return switch (tag) {
24952428 .@"catch",