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
This commit is contained in:
Isaac Freund 2021-03-15 14:41:15 +01:00
parent b4db03d8bb
commit 48efa3bcb6
2 changed files with 301 additions and 152 deletions

View File

@ -3186,7 +3186,7 @@ test "zig fmt: for" {
test "zig fmt: for if" {
try testCanonical(
\\test "for if" {
\\test {
\\ for (a) |x| if (x) f(x);
\\
\\ for (a) |x| if (x)
@ -3209,6 +3209,131 @@ test "zig fmt: for if" {
);
}
test "zig fmt: if for" {
try testCanonical(
\\test {
\\ if (a) for (x) |x| f(x);
\\
\\ if (a) for (x) |x|
\\ f(x);
\\
\\ if (a) for (x) |x| {
\\ f(x);
\\ };
\\
\\ if (a)
\\ for (x) |x|
\\ f(x);
\\
\\ if (a)
\\ for (x) |x| {
\\ f(x);
\\ };
\\}
\\
);
}
test "zig fmt: while if" {
try testCanonical(
\\test {
\\ while (a) if (x) f(x);
\\
\\ while (a) if (x)
\\ f(x);
\\
\\ while (a) if (x) {
\\ f(x);
\\ };
\\
\\ while (a)
\\ if (x)
\\ f(x);
\\
\\ while (a)
\\ if (x) {
\\ f(x);
\\ };
\\}
\\
);
}
test "zig fmt: if while" {
try testCanonical(
\\test {
\\ if (a) while (x) : (cont) f(x);
\\
\\ if (a) while (x) : (cont)
\\ f(x);
\\
\\ if (a) while (x) : (cont) {
\\ f(x);
\\ };
\\
\\ if (a)
\\ while (x) : (cont)
\\ f(x);
\\
\\ if (a)
\\ while (x) : (cont) {
\\ f(x);
\\ };
\\}
\\
);
}
test "zig fmt: while for" {
try testCanonical(
\\test {
\\ while (a) for (x) |x| f(x);
\\
\\ while (a) for (x) |x|
\\ f(x);
\\
\\ while (a) for (x) |x| {
\\ f(x);
\\ };
\\
\\ while (a)
\\ for (x) |x|
\\ f(x);
\\
\\ while (a)
\\ for (x) |x| {
\\ f(x);
\\ };
\\}
\\
);
}
test "zig fmt: for while" {
try testCanonical(
\\test {
\\ for (a) |a| while (x) |x| f(x);
\\
\\ for (a) |a| while (x) |x|
\\ f(x);
\\
\\ for (a) |a| while (x) |x| {
\\ f(x);
\\ };
\\
\\ for (a) |a|
\\ while (x) |x|
\\ f(x);
\\
\\ for (a) |a|
\\ while (x) |x| {
\\ f(x);
\\ };
\\}
\\
);
}
test "zig fmt: if" {
try testCanonical(
\\test "if" {
@ -3258,6 +3383,82 @@ test "zig fmt: if" {
);
}
test "zig fmt: fix single statement if/for/while line breaks" {
try testTransform(
\\test {
\\ if (cond) a
\\ else b;
\\
\\ if (cond)
\\ a
\\ else b;
\\
\\ for (xs) |x| foo()
\\ else bar();
\\
\\ for (xs) |x|
\\ foo()
\\ else bar();
\\
\\ while (a) : (b) foo()
\\ else bar();
\\
\\ while (a) : (b)
\\ foo()
\\ else bar();
\\}
\\
,
\\test {
\\ if (cond) a else b;
\\
\\ if (cond)
\\ a
\\ else
\\ b;
\\
\\ for (xs) |x| foo() else bar();
\\
\\ for (xs) |x|
\\ foo()
\\ else
\\ bar();
\\
\\ while (a) : (b) foo() else bar();
\\
\\ while (a) : (b)
\\ foo()
\\ else
\\ bar();
\\}
\\
);
}
test "zig fmt: anon struct/array literal in if" {
try testCanonical(
\\test {
\\ const a = if (cond) .{
\\ 1, 2,
\\ 3, 4,
\\ } else .{
\\ 1,
\\ 2,
\\ 3,
\\ };
\\
\\ const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{
\\ .rl = .ref,
\\ .tag = .switchbr_ref,
\\ } else .{
\\ .rl = .none,
\\ .tag = .switchbr,
\\ };
\\}
\\
);
}
test "zig fmt: defer" {
try testCanonical(
\\test "defer" {
@ -3845,6 +4046,7 @@ test "zig fmt: comments in ternary ifs" {
\\ // Comment
\\ 1
\\else
\\ // Comment
\\ 0;
\\
\\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
@ -3852,6 +4054,20 @@ test "zig fmt: comments in ternary ifs" {
);
}
test "zig fmt: while statement in blockless if" {
try testCanonical(
\\pub fn main() void {
\\ const zoom_node = if (focused_node == layout_first)
\\ while (it.next()) |node| {
\\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
\\ } else null
\\ else
\\ focused_node;
\\}
\\
);
}
test "zig fmt: test comments in field access chain" {
try testCanonical(
\\pub const str = struct {

View File

@ -1018,172 +1018,97 @@ fn renderWhile(gpa: *Allocator, ais: *Ais, tree: ast.Tree, while_node: ast.full.
try renderToken(ais, tree, inline_token, .space); // inline
}
try renderToken(ais, tree, while_node.ast.while_token, .space); // if
try renderToken(ais, tree, while_node.ast.while_token, .space); // if/for/while
try renderToken(ais, tree, while_node.ast.while_token + 1, .none); // lparen
try renderExpression(gpa, ais, tree, while_node.ast.cond_expr, .none); // condition
const then_tag = node_tags[while_node.ast.then_expr];
if (nodeIsBlock(then_tag) and !nodeIsIf(then_tag)) {
if (while_node.payload_token) |payload_token| {
const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented())
Space.newline
else
Space.space;
try renderWhilePayload(gpa, ais, tree, payload_token, brace_space);
} else {
const rparen = tree.lastToken(while_node.ast.cond_expr) + 1;
const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented())
Space.newline
else
Space.space;
try renderToken(ais, tree, rparen, brace_space); // rparen
}
if (while_node.ast.cont_expr != 0) {
const rparen = tree.lastToken(while_node.ast.cont_expr) + 1;
const lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
try renderToken(ais, tree, lparen - 1, .space); // :
try renderToken(ais, tree, lparen, .none); // lparen
try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none);
const brace_space: Space = if (ais.isLineOverIndented()) .newline else .space;
try renderToken(ais, tree, rparen, brace_space); // rparen
}
if (while_node.ast.else_expr != 0) {
try renderExpression(gpa, ais, tree, while_node.ast.then_expr, Space.space);
try renderToken(ais, tree, while_node.else_token, .space); // else
if (while_node.error_token) |error_token| {
try renderToken(ais, tree, error_token - 1, .none); // |
try renderToken(ais, tree, error_token, .none); // identifier
try renderToken(ais, tree, error_token + 1, .space); // |
}
return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
} else {
return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
}
}
const rparen = tree.lastToken(while_node.ast.cond_expr) + 1;
const first_then_token = tree.firstToken(while_node.ast.then_expr);
const last_then_token = tree.lastToken(while_node.ast.then_expr);
const src_has_newline = !tree.tokensOnSameLine(rparen, last_then_token);
if (src_has_newline) {
const newline_before_then_token = !tree.tokensOnSameLine(rparen, first_then_token);
const space_before_then_token: Space = if (newline_before_then_token) .newline else .space;
const indent_expression = !nodeIsIf(then_tag) or newline_before_then_token;
if (while_node.payload_token) |payload_token| {
const after_space: Space = if (while_node.ast.cont_expr != 0) .space else space_before_then_token;
try renderWhilePayload(gpa, ais, tree, payload_token, after_space);
} else {
if (indent_expression) ais.pushIndent();
const after_space: Space = if (while_node.ast.cont_expr != 0) .space else space_before_then_token;
try renderToken(ais, tree, rparen, after_space); // rparen
if (indent_expression) ais.popIndent();
}
if (while_node.ast.cont_expr != 0) {
const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1;
const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
try renderToken(ais, tree, cont_lparen - 1, .space); // :
try renderToken(ais, tree, cont_lparen, .none); // lparen
try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none);
try renderToken(ais, tree, cont_rparen, space_before_then_token); // rparen
}
if (while_node.ast.else_expr != 0) {
if (indent_expression) ais.pushIndent();
try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .newline);
if (indent_expression) ais.popIndent();
const else_is_block = nodeIsBlock(node_tags[while_node.ast.else_expr]);
if (else_is_block) {
try renderToken(ais, tree, while_node.else_token, .space); // else
if (while_node.error_token) |error_token| {
try renderToken(ais, tree, error_token - 1, .none); // |
try renderToken(ais, tree, error_token, .none); // identifier
try renderToken(ais, tree, error_token + 1, .space); // |
}
return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
} else {
if (while_node.error_token) |error_token| {
try renderToken(ais, tree, while_node.else_token, .space); // else
try renderToken(ais, tree, error_token - 1, .none); // |
try renderToken(ais, tree, error_token, .none); // identifier
try renderToken(ais, tree, error_token + 1, .space); // |
} else {
try renderToken(ais, tree, while_node.else_token, .newline); // else
}
if (indent_expression) {
return renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space);
} else {
return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
}
}
} else {
if (indent_expression) {
return renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space);
} else {
return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
}
}
}
// Render everything on a single line.
var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
if (while_node.payload_token) |payload_token| {
assert(payload_token - 2 == rparen);
try renderWhilePayload(gpa, ais, tree, payload_token, .space);
} else {
try renderToken(ais, tree, rparen, .space); // )
try renderToken(ais, tree, last_prefix_token, .space);
try renderToken(ais, tree, payload_token - 1, .none); // |
const ident = blk: {
if (token_tags[payload_token] == .asterisk) {
try renderToken(ais, tree, payload_token, .none); // *
break :blk payload_token + 1;
} else {
break :blk payload_token;
}
};
try renderToken(ais, tree, ident, .none); // identifier
const pipe = blk: {
if (token_tags[ident + 1] == .comma) {
try renderToken(ais, tree, ident + 1, .space); // ,
try renderToken(ais, tree, ident + 2, .none); // index
break :blk ident + 3;
} else {
break :blk ident + 1;
}
};
last_prefix_token = pipe;
}
if (while_node.ast.cont_expr != 0) {
const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1;
const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
try renderToken(ais, tree, cont_lparen - 1, .space); // :
try renderToken(ais, tree, cont_lparen, .none); // lparen
try renderToken(ais, tree, last_prefix_token, .space);
const lparen = tree.firstToken(while_node.ast.cont_expr) - 1;
try renderToken(ais, tree, lparen - 1, .space); // :
try renderToken(ais, tree, lparen, .none); // lparen
try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none);
try renderToken(ais, tree, cont_rparen, .space); // rparen
last_prefix_token = tree.lastToken(while_node.ast.cont_expr) + 1; // rparen
}
const then_expr_is_block = nodeIsBlock(node_tags[while_node.ast.then_expr]);
const indent_then_expr = !then_expr_is_block and
!tree.tokensOnSameLine(last_prefix_token, tree.firstToken(while_node.ast.then_expr));
if (indent_then_expr or (then_expr_is_block and ais.isLineOverIndented())) {
ais.pushIndentNextLine();
try renderToken(ais, tree, last_prefix_token, .newline);
ais.popIndent();
} else {
try renderToken(ais, tree, last_prefix_token, .space);
}
if (while_node.ast.else_expr != 0) {
try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space);
try renderToken(ais, tree, while_node.else_token, .space); // else
const first_else_expr_tok = tree.firstToken(while_node.ast.else_expr);
if (indent_then_expr) {
ais.pushIndent();
try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .newline);
ais.popIndent();
} else {
try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space);
}
var last_else_token = while_node.else_token;
if (while_node.error_token) |error_token| {
try renderToken(ais, tree, while_node.else_token, .space); // else
try renderToken(ais, tree, error_token - 1, .none); // |
try renderToken(ais, tree, error_token, .none); // identifier
try renderToken(ais, tree, error_token + 1, .space); // |
last_else_token = error_token + 1; // |
}
return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
const indent_else_expr = indent_then_expr and
!nodeIsBlock(node_tags[while_node.ast.else_expr]) and
!nodeIsIfForWhileSwitch(node_tags[while_node.ast.else_expr]);
if (indent_else_expr) {
ais.pushIndentNextLine();
try renderToken(ais, tree, last_else_token, .newline);
ais.popIndent();
try renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space);
} else {
try renderToken(ais, tree, last_else_token, .space);
try renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
}
} else {
return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
if (indent_then_expr) {
try renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space);
} else {
try renderExpression(gpa, ais, tree, while_node.ast.then_expr, space);
}
}
}
fn renderWhilePayload(gpa: *Allocator, ais: *Ais, tree: ast.Tree, payload_token: ast.TokenIndex, space: Space) Error!void {
const token_tags = tree.tokens.items(.tag);
try renderToken(ais, tree, payload_token - 2, .space); // rparen
try renderToken(ais, tree, payload_token - 1, .none); // |
const ident = blk: {
if (token_tags[payload_token] == .asterisk) {
try renderToken(ais, tree, payload_token, .none); // *
break :blk payload_token + 1;
} else {
break :blk payload_token;
}
};
try renderToken(ais, tree, ident, .none); // identifier
const pipe = blk: {
if (token_tags[ident + 1] == .comma) {
try renderToken(ais, tree, ident + 1, .space); // ,
try renderToken(ais, tree, ident + 2, .none); // index
break :blk ident + 3;
} else {
break :blk ident + 1;
}
};
try renderToken(ais, tree, pipe, space); // |
}
fn renderContainerField(
gpa: *Allocator,
ais: *Ais,
@ -2469,6 +2394,21 @@ fn nodeIsBlock(tag: ast.Node.Tag) bool {
.block_semicolon,
.block_two,
.block_two_semicolon,
.struct_init_dot,
.struct_init_dot_comma,
.struct_init_dot_two,
.struct_init_dot_two_comma,
.array_init_dot,
.array_init_dot_comma,
.array_init_dot_two,
.array_init_dot_two_comma,
=> true,
else => false,
};
}
fn nodeIsIfForWhileSwitch(tag: ast.Node.Tag) bool {
return switch (tag) {
.@"if",
.if_simple,
.@"for",
@ -2483,13 +2423,6 @@ fn nodeIsBlock(tag: ast.Node.Tag) bool {
};
}
fn nodeIsIf(tag: ast.Node.Tag) bool {
return switch (tag) {
.@"if", .if_simple => true,
else => false,
};
}
fn nodeCausesSliceOpSpace(tag: ast.Node.Tag) bool {
return switch (tag) {
.@"catch",