From 939f26b139fcb6caa6d0c77d2ef2430e7bfdff48 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 29 Oct 2026 17:59:25 +0000 Subject: [PATCH] Keep the comments around a list pattern's only spread when formatting Why: `list{...r}` parses to the pattern `r` itself, but the parser gave that pattern the location of the whole `list{...}`. A comment around the spread was then inside the location of the leaf `r`, where comment attachment leaves it unattached, so formatting dropped it. The pattern now keeps the location of `r`, like `list{...e}` in expressions, and the comments become leading or trailing comments of `r`. Format modified (comment around a list pattern's only spread): Example: let f = x => switch x { | list{/* d */ ...r} => r } let g = (list{...r /* t */}) => r Before (rescript): let f = x => switch x { | r => r } let g = r => r After (rescript): let f = x => switch x { /* d */ | r => r } let g = r /* t */ => r Review: Why is the comment printed before `|`? That is where the formatter already puts a leading comment of a case pattern, as for `| /* d */ r`. Fixes: #9123 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Kbf3gHnoR8Goys4AMxET4q Signed-off-by: Christoph Knittel --- changes/list-pattern-spread-comment.fix.md | 1 + compiler/syntax/src/res_core.ml | 5 +++++ .../data/printer/comments/expected/pattern.res.txt | 10 ++++++++++ tests/syntax_tests/data/printer/comments/pattern.res | 10 ++++++++++ 4 files changed, 26 insertions(+) create mode 100644 changes/list-pattern-spread-comment.fix.md diff --git a/changes/list-pattern-spread-comment.fix.md b/changes/list-pattern-spread-comment.fix.md new file mode 100644 index 0000000000..4ae6827e8b --- /dev/null +++ b/changes/list-pattern-spread-comment.fix.md @@ -0,0 +1 @@ +- Keep the comments around the spread of a list pattern without other items when formatting, as in `list{/* c */ ...r}`. diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index d260fa90ef..0f6aeb23de 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -1659,6 +1659,11 @@ and parse_list_pattern ~start_pos ~attrs p = else pattern in match list_patterns with + | [(true, pattern)] -> + (* [list{...p}] is [p] itself. Like [list{...e}] in expressions, it keeps + the location of [p], so that the comments around the spread stay + outside it and are printed with [p]. *) + {pattern with ppat_attributes = attrs} | (true, pattern) :: patterns -> let patterns = patterns |> List.map filter_spread |> List.rev in let pat = make_list_pattern loc patterns (Some pattern) in diff --git a/tests/syntax_tests/data/printer/comments/expected/pattern.res.txt b/tests/syntax_tests/data/printer/comments/expected/pattern.res.txt index 2bd71d1da1..2fc74875c1 100644 --- a/tests/syntax_tests/data/printer/comments/expected/pattern.res.txt +++ b/tests/syntax_tests/data/printer/comments/expected/pattern.res.txt @@ -47,6 +47,16 @@ let list{/* inside */} = list{} let /* before */ list{/* a1 */ a /* a2 */, /* b1 */ b /* b2 */} /* after */ = list{1, 2} let /* before */ list{/* a1 */ a /* a2 */, /* b1 */ ...b /* b2 */} /* after */ = list{1, 2} +// A list pattern that is only a spread is the spread's operand +let /* d */ r = list{1, 2} +let r /* after */ = list{1, 2} +let /* d1 */ /* d2 */ /* e */ r = list{1, 2} +let f = /* d */ r => r +let f = x => + switch x { + /* d */ | r => r + } + // Ppat_record let /* before */ {/* c0 */ name /* c1 */, /* c2 */ age /* c3 */} /* after */ = { name: "steve", diff --git a/tests/syntax_tests/data/printer/comments/pattern.res b/tests/syntax_tests/data/printer/comments/pattern.res index 4617bda799..b1a55413b4 100644 --- a/tests/syntax_tests/data/printer/comments/pattern.res +++ b/tests/syntax_tests/data/printer/comments/pattern.res @@ -43,6 +43,16 @@ let list{/* inside */} = list{} let /* before */ list{/* a1 */ a /* a2 */, /* b1 */ b /* b2 */} /* after */ = list{1, 2} let /* before */ list{/* a1 */ a /* a2 */, /* b1 */ ...b /* b2 */} /* after */ = list{1, 2} +// A list pattern that is only a spread is the spread's operand +let list{/* d */ ...r} = list{1, 2} +let list{...r /* after */} = list{1, 2} +let list{/* d1 */ /* d2 */ ...list{/* e */ ...r}} = list{1, 2} +let f = (list{/* d */ ...r}) => r +let f = x => + switch x { + | list{/* d */ ...r} => r + } + // Ppat_record let /* before */ {/* c0 */ name /* c1 */, /* c2 */ age /* c3 */} /* after */ = { name: "steve",