Skip to content

Commit 4b1fcda

Browse files
cknittclaude
andauthored
Keep doc comments and comments in place in four formatter cases (#9119)
Why: The formatter put doc comments and comments in the wrong place in four of the cases reported in #9107, and in one of them it reordered the attributes in the AST. Doc comments after an attribute were moved in front of all attributes. A single parameter with a doc comment was hugged, so its pattern started at column 0. A comment before a list's tail spread was printed after the dots, in expressions and in patterns. A file with only a line comment got an extra newline. Only the doc comments in front of every other attribute are now printed first; later ones stay in their place, inline. The fifth case, a comment after a parenthesized `switch` or `if` operand that moves into the block, keeps its stable output. AST modified (doc comment after an attribute, after formatting): Example: type u = /** d1 */ @A /** d2 */ {x: int} Before (parse): type_declaration "u" attribute "res.doc" attribute "res.doc" attribute "a" After (parse): type_declaration "u" attribute "res.doc" attribute "a" attribute "res.doc" Format modified (doc comment after an attribute): Example: type u = /** d1 */ @A /** d2 */ {x: int} Before (rescript): /** d1 */ /** d2 */ @A type u = {x: int} After (rescript): /** d1 */ @A /** d2 */ type u = {x: int} Format modified (doc comment on a hugged parameter): Example: let f = (/** r */ {x, y}) => x Before (rescript): let f = (/** r */ {x, y}) => x After (rescript): let f = ( /** r */ {x, y}, ) => x Format modified (comment before a list's tail spread): Example: let l = list{1, /* c */ ...xs} let f = (list{a, /* c */ ...rest}) => a Before (rescript): let l = list{1, .../* c */ xs} let f = (list{a, .../* c */ rest}) => a After (rescript): let l = list{1, /* c */ ...xs} let f = (list{a, /* c */ ...rest}) => a Format modified (file with only a line comment): Example: // c Before (text): "// c\n\n" After (text): "// c\n" Review: Why not fix the comment after a parenthesized operand? It formats stably with the same AST; fixing it needs a token flag on comments or operand locations that include the parentheses. Fixes: #9107 Claude-Session: https://claude.ai/code/session_01Kbf3gGkPma9LAZimuQjnrP Signed-off-by: Christoph Knittel <ck@cca.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3562ce6 commit 4b1fcda

18 files changed

Lines changed: 154 additions & 68 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix the formatter's placement of a doc comment on a single hugged parameter, of doc comments after an attribute, and of a comment before a list's tail spread, and the extra newline after a file with only a line comment.

‎compiler/syntax/src/res_parsetree_viewer.ml‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,13 +366,6 @@ let collect_ternary_parts expr =
366366
in
367367
collect [] expr
368368

369-
let parameters_should_hug parameters =
370-
match parameters with
371-
| [Parameter {attrs = []; lbl = Nolabel; default_expr = None; pat}]
372-
when is_huggable_pattern pat ->
373-
true
374-
| _ -> false
375-
376369
let should_indent_binary_expr expr =
377370
let same_precedence_sub_expression operator sub_expression =
378371
match sub_expression with
@@ -430,8 +423,25 @@ let is_doc_comment_attribute ((id, payload) : Parsetree.attribute) =
430423
true
431424
| _ -> false
432425

426+
(* Only the doc comments before every other attribute: one after an attribute
427+
stays in its place, so that printing keeps the order of the attributes. *)
433428
let partition_doc_comment_attributes attrs =
434-
List.partition is_doc_comment_attribute attrs
429+
let rec split doc_comments = function
430+
| attr :: attrs when is_doc_comment_attribute attr ->
431+
split (attr :: doc_comments) attrs
432+
| attrs -> (List.rev doc_comments, attrs)
433+
in
434+
split [] attrs
435+
436+
(* A doc comment can be printed on its own line, which a hugged parameter
437+
cannot start. *)
438+
let parameters_should_hug parameters =
439+
match parameters with
440+
| [Parameter {attrs = []; lbl = Nolabel; default_expr = None; pat}]
441+
when is_huggable_pattern pat
442+
&& not (List.exists is_doc_comment_attribute pat.ppat_attributes) ->
443+
true
444+
| _ -> false
435445

436446
let rec is_fun_expr expr =
437447
match expr.pexp_desc with

‎compiler/syntax/src/res_parsetree_viewer.mli‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ val should_indent_binary_expr : Parsetree.expression -> bool
8080
val should_inline_rhs_binary_expr : Parsetree.expression -> bool
8181
val mod_expr_has_attributes : Parsetree.module_expr -> bool
8282
val is_doc_comment_attribute : Parsetree.attribute -> bool
83+
84+
(* The doc comments before every other attribute, and the attributes after
85+
them, which may include later doc comments *)
8386
val partition_doc_comment_attributes :
8487
Parsetree.attributes -> Parsetree.attributes * Parsetree.attributes
8588

‎compiler/syntax/src/res_printer.ml‎

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,14 @@ let print_trailing_comment (prev_loc : Location.t) (node_loc : Location.t)
230230
else if not single_line then Doc.concat [Doc.space; content]
231231
else Doc.line_suffix (Doc.concat [Doc.space; content])
232232

233+
let print_comment_content comment =
234+
let txt = Comment.txt comment in
235+
if Comment.is_single_line_comment comment then Doc.text ("//" ^ txt)
236+
else print_multiline_comment_content txt
237+
233238
let print_leading_comment ?next_comment comment =
234239
let single_line = Comment.is_single_line_comment comment in
235-
let content =
236-
let txt = Comment.txt comment in
237-
if single_line then Doc.text ("//" ^ txt)
238-
else print_multiline_comment_content txt
239-
in
240+
let content = print_comment_content comment in
240241
let separator =
241242
Doc.concat
242243
[
@@ -302,7 +303,9 @@ let print_comments_inside_file cmt_tbl =
302303
match comments with
303304
| [] -> Doc.nil
304305
| [comment] ->
305-
let cmt_doc = print_leading_comment comment in
306+
(* Nothing follows the last comment: the line break after a line comment
307+
would add an empty line to the final newline of the file. *)
308+
let cmt_doc = print_comment_content comment in
306309
let doc =
307310
Doc.group (Doc.concat [Doc.concat (List.rev (cmt_doc :: acc))])
308311
in
@@ -1762,25 +1765,31 @@ and print_literal_dict_expr ~state (e : Parsetree.expression) cmt_tbl =
17621765
Doc.soft_line;
17631766
])
17641767

1768+
(* [...expr] in an array, list or dict. The leading comments of [expr] go
1769+
before the dots, which are part of the spread and not of [expr]. *)
1770+
and print_spread_with_comments ~state expr cmt_tbl =
1771+
let leading_comments_doc =
1772+
print_leading_comments Doc.nil cmt_tbl.Comment_table.leading
1773+
expr.Parsetree.pexp_loc
1774+
in
1775+
let doc =
1776+
let doc = print_expression ~state expr cmt_tbl in
1777+
match Parens.expr_allowing_coercion expr with
1778+
| Parens.Parenthesized -> add_parens doc
1779+
| Braced braces -> print_braces doc expr braces
1780+
| Nothing -> doc
1781+
in
1782+
Doc.concat
1783+
[
1784+
leading_comments_doc;
1785+
Doc.dotdotdot;
1786+
print_trailing_comments doc cmt_tbl.Comment_table.trailing
1787+
expr.Parsetree.pexp_loc;
1788+
]
1789+
17651790
and print_spread_dict_expr ~state parts (expr : Parsetree.expression) cmt_tbl =
17661791
let print_spread_part spread_expr =
1767-
let leading_comments_doc =
1768-
print_leading_comments Doc.nil cmt_tbl.Comment_table.leading
1769-
spread_expr.Parsetree.pexp_loc
1770-
in
1771-
let spread_doc =
1772-
let doc = print_expression ~state spread_expr cmt_tbl in
1773-
match Parens.expr_allowing_coercion spread_expr with
1774-
| Parens.Parenthesized -> add_parens doc
1775-
| Braced braces -> print_braces doc spread_expr braces
1776-
| Nothing -> doc
1777-
in
1778-
let spread_with_trailing_comments =
1779-
print_trailing_comments spread_doc cmt_tbl.Comment_table.trailing
1780-
spread_expr.Parsetree.pexp_loc
1781-
in
1782-
Doc.concat
1783-
[leading_comments_doc; Doc.dotdotdot; spread_with_trailing_comments]
1792+
print_spread_with_comments ~state spread_expr cmt_tbl
17841793
in
17851794
let parts_doc =
17861795
Doc.join
@@ -2843,11 +2852,18 @@ and print_pattern ~state (p : Parsetree.pattern) cmt_tbl =
28432852
(match tail.Parsetree.ppat_desc with
28442853
| Ppat_construct ({txt = Longident.Lident "[]"}, _) -> Doc.nil
28452854
| _ ->
2846-
let doc =
2847-
Doc.concat [Doc.text "..."; print_pattern ~state tail cmt_tbl]
2855+
(* Taken before [print_pattern] prints the other comments *)
2856+
let leading =
2857+
print_leading_comments Doc.nil cmt_tbl.leading tail.ppat_loc
28482858
in
2849-
let tail = print_comments doc cmt_tbl tail.ppat_loc in
2850-
Doc.concat [Doc.text ","; Doc.line; tail]);
2859+
Doc.concat
2860+
[
2861+
Doc.text ",";
2862+
Doc.line;
2863+
leading;
2864+
Doc.text "...";
2865+
print_pattern ~state tail cmt_tbl;
2866+
]);
28512867
]
28522868
in
28532869
Doc.group
@@ -3380,12 +3396,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl =
33803396
[
33813397
Doc.text ",";
33823398
Doc.line;
3383-
Doc.dotdotdot;
3384-
(let doc = print_expression_with_comments ~state expr cmt_tbl in
3385-
match Parens.expr_allowing_coercion expr with
3386-
| Parens.Parenthesized -> add_parens doc
3387-
| Braced braces -> print_braces doc expr braces
3388-
| Nothing -> doc);
3399+
print_spread_with_comments ~state expr cmt_tbl;
33893400
]
33903401
| None -> Doc.nil
33913402
in
@@ -4345,18 +4356,7 @@ and print_spread_expr ~state kind elements cmt_tbl =
43454356
let element_doc = function
43464357
| Parsetree.Pspread_item expr ->
43474358
print_comments (expr_doc expr) cmt_tbl expr.pexp_loc
4348-
| Pspread expr ->
4349-
(* The leading comments go before the dots, so they're taken before the
4350-
expression is printed *)
4351-
let leading_comments_doc =
4352-
print_leading_comments Doc.nil cmt_tbl.Comment_table.leading
4353-
expr.pexp_loc
4354-
in
4355-
let expr_doc =
4356-
print_trailing_comments (expr_doc expr) cmt_tbl.Comment_table.trailing
4357-
expr.pexp_loc
4358-
in
4359-
Doc.concat [leading_comments_doc; Doc.dotdotdot; expr_doc]
4359+
| Pspread expr -> print_spread_with_comments ~state expr cmt_tbl
43604360
in
43614361
Doc.group
43624362
(Doc.concat
@@ -5968,10 +5968,11 @@ and print_attributes ?loc ?(inline = false) ~state
59685968
| _ ->
59695969
Doc.concat
59705970
[
5971+
(* A doc comment here follows an attribute and stays on its line *)
59715972
Doc.group
5972-
(Doc.join_with_sep
5973+
(Doc.join ~sep:Doc.line
59735974
(List.map
5974-
(fun attr -> print_attribute ~state attr cmt_tbl)
5975+
(fun attr -> fst (print_attribute ~state attr cmt_tbl))
59755976
attrs));
59765977
(if inline then Doc.space else line_break);
59775978
]

‎tests/syntax_tests/data/printer/comments/array.res‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,15 @@ let _ = list{
6161
/* comment 2 */ ...ys /* comment 3 */,
6262
b,
6363
}
64+
65+
// A comment before a list's tail spread
66+
let _ = list{1, /* c0 */ ...xs}
67+
let _ = list{1, /* c0 */ ...xs /* c1 */}
68+
let _ = list{1, /* c0 */ ...(xs :> list<int>) /* c1 */}
69+
let _ = list{
70+
1,
71+
// c0
72+
...xs,
73+
}
74+
let f = (list{a, /* c0 */ ...rest}) => a
75+
let f = (list{a, /* c0 */ ...rest /* c1 */}) => a

‎tests/syntax_tests/data/printer/comments/docCommentAttributes.res‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,15 @@ Normal comment Normal comment Normal comment Normal comment Normal comment
1919
*/
2020
@a @b
2121
external f: unit => unit = "f"
22+
23+
// A doc comment after an attribute stays after it
24+
@a /** Doc comment */ @b
25+
external f: unit => unit = "f"
26+
27+
/** Doc comment 1 */ @a /** Doc comment 2 */
28+
let x = 1
29+
30+
type t = | @a /** Doc comment */ A | B
31+
type r = {@a /** Doc comment */ x: int}
32+
type p = [@a /** Doc comment */ #A | #B]
33+
type q = @a /** Doc comment */ int

‎tests/syntax_tests/data/printer/comments/docCommentParameters.res‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@ let f = (/** The runner */ run: string) => run
33
let f = (/** Positional */ x, /** Labelled */ ~y, /** Typed */ ~z: int) => x + y + z
44
let f = (/** Optional */ ~a=?, /** Default */ ~b=1, /** Aliased */ ~c as d) => d
55
let f = (/** Locally abstract */ type a, x: a) => x
6+
7+
// A doc comment on a single parameter that would be hugged
8+
let f = (/** Record */ {x, y}) => x
9+
let f = (/** Array */ [a]) => a
10+
let f = (/** Tuple */ (a, b)) => a
11+
let f = (/** Unit */ ()) => 1
12+
let f = (@a /** After an attribute */ {x, y}) => x
13+

‎tests/syntax_tests/data/printer/comments/expected/array.res.txt‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,15 @@ let _ = list{
5858
/* comment 2 */ ...ys /* comment 3 */,
5959
b,
6060
}
61+
62+
// A comment before a list's tail spread
63+
let _ = list{1, /* c0 */ ...xs}
64+
let _ = list{1, /* c0 */ ...xs /* c1 */}
65+
let _ = list{1, /* c0 */ ...xs :> list<int> /* c1 */}
66+
let _ = list{
67+
1,
68+
// c0
69+
...xs,
70+
}
71+
let f = (list{a, /* c0 */ ...rest}) => a
72+
let f = (list{a, /* c0 */ ...rest /* c1 */}) => a

‎tests/syntax_tests/data/printer/comments/expected/docCommentAttributes.res.txt‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,16 @@ Normal comment Normal comment Normal comment Normal comment Normal comment
1919
*/
2020
@a @b
2121
external f: unit => unit = "f"
22+
23+
// A doc comment after an attribute stays after it
24+
@a /** Doc comment */ @b
25+
external f: unit => unit = "f"
26+
27+
/** Doc comment 1 */
28+
@a /** Doc comment 2 */
29+
let x = 1
30+
31+
type t = | @a /** Doc comment */ A | B
32+
type r = {@a /** Doc comment */ x: int}
33+
type p = [@a /** Doc comment */ #A | #B]
34+
type q = @a /** Doc comment */ int

‎tests/syntax_tests/data/printer/comments/expected/docCommentParameters.res.txt‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,22 @@ let f = (
2727
type a,
2828
x: a,
2929
) => x
30+
31+
// A doc comment on a single parameter that would be hugged
32+
let f = (
33+
/** Record */
34+
{x, y},
35+
) => x
36+
let f = (
37+
/** Array */
38+
[a],
39+
) => a
40+
let f = (
41+
/** Tuple */
42+
(a, b),
43+
) => a
44+
let f = (
45+
/** Unit */
46+
(),
47+
) => 1
48+
let f = (@a /** After an attribute */ {x, y}) => x

0 commit comments

Comments
 (0)