Skip to content

[formatting] Unify delimiter alignment and block continuation layouts - #636

Open
purefunctor wants to merge 6 commits into
mainfrom
formatting/layout-alignment
Open

purefunctor wants to merge 6 commits into
mainfrom
formatting/layout-alignment

Conversation

@purefunctor

@purefunctor purefunctor commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Make multiline delimiter layouts consistent across terms and types, and keep block continuations readable without unnecessary breaks or excessive rightward indentation.

The initial layout changes are organized into four behavior-focused commits, each with its corresponding fixtures and generated expectations:

  1. Unify delimiter alignment across formatter syntax. Use opener-relative, comma-leading layouts for imports, exports, parenthesized terms, types and binders, and constraint lists. Fully expand exports when they move below the module header, while allowing compact import continuations. Preserve tight constructor-member forms such as Box(First, Second) and Box(..), adding a space before multiline member lists. Move broken superclass lists below class, keeping ) <= Thing together when it fits.
  2. Align data alternatives and constructor arguments. Align the leading = with alternative | separators in broken data declarations. Indent constructor arguments, including record arguments, relative to the constructor rather than the declaration margin.
  3. Break before delimited block expressions. Move parentheses, arrays, records, and record updates containing do or ado below their prefixes in bindings, applications, and operator operands. Keep the first expression beside its opening delimiter and place expression access and block detection on Tree.
  4. Keep case headers compact before splitting scrutinees. Measure the complete case … of header independently of its branches. Keep a fitting header beside its prefix; otherwise move the intact header to a continuation line; split aligned case and of only when the scrutinees require it. Use leading commas for broken multiple scrutinees and preserve comment boundaries.

Follow-up regression and fix commits cover complete-class-head width boundaries: expand the superclass list before wrapping the class parameters, preserve the compact exact-width header, and use the structured list when comments prevent a flat header.

Retain the structured multiline if/then/else layout, the separate let/bindings/in/result lines, and the existing ado result layout (in value).

Fixtures cover exact width boundaries, alternate indentation and Unicode, nested blocks, comments, constructor lists, constraints, and operator continuations. The history rewrite preserves the exact final tree.

Verification

  • just t formatting --verbose: 1,063 passed, no pending snapshots.
  • cargo check -p formatting --tests
  • just format --check
  • git diff --check
  • Pre-push just format and just licenses completed without tracked changes.

Discussion

https://ampcode.com/threads/T-01a11570-3ec2-7539-bc19-3d4825503ee1

@purefunctor

purefunctor Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Granite Review

Council

Role Mode Status Attempt Amp thread
Council member review-gpt-6-luna completed 1 T-01a11661-4ed2-7028-bf41-5f9ec34ee992
Council member review-gpt-6-sol completed 1 T-01a11661-4c0d-7727-842c-9464ff39c9f2
Synthesis review-astra-synthesis completed 1 T-01a11666-1e51-70f9-a798-24f9f9c75ec9

Findings

No actionable findings.

Open this run in Granite

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Compatibility regression report

Package set 81.3.0 for PureScript 0.15.15.

✅ The candidate introduces no compatibility errors.

Diagnostic class Base Candidate Introduced Fixed
Compiler errors 0 0 0 0
Compiler warnings 36 36 0 0
Verifier errors 0 0 0 0

Introduced errors

None.

Fixed errors (0)

None.

Warning changes (0 introduced, 0 fixed)

Introduced

None.

Fixed

None.

Candidate errors (0)

None.

Candidate warnings (36)
  • deno@0.0.5/src/Deno.purs:38:17 — CustomWarning (checking): Data.Map's `Semigroup` instance is now unbiased and differs from the left-biased instance defined in PureScript releases <= 0.13.x.
  • deno@0.0.5/src/Deno/Dotenv.purs:41:20 — CustomWarning (checking) × 2: Data.Map's `Semigroup` instance is now unbiased and differs from the left-biased instance defined in PureScript releases <= 0.13.x.
  • deno@0.0.5/src/Deno/Http/Request.purs:47:25 — CustomWarning (checking): Data.Map's `Semigroup` instance is now unbiased and differs from the left-biased instance defined in PureScript releases <= 0.13.x.
  • literals@1.0.2/src/Literals/Null.purs:11:1 — UnparseableFFIModule (javascript): Oxc could not parse the JavaScript FFI module. Fix the invalid or unsupported JavaScript syntax; Iris treated the module as opaque and skipped export-name validation: Unexpected token
  • react-basic-dom-beta@0.1.1/src/Beta/DOM.purs:33:31 — DuplicateImport (indexing): Import list contains multiple references to 'Proxy'
  • sparse-polynomials@3.0.1/src/Data/Sparse/Polynomial.purs:1048:1 — MissingPatterns (checking) × 2: Pattern match is not exhaustive. Missing: _
  • text-formatting@0.1.0/src/Data/Text/Format/Dodo/Printer.purs:64:25 — CustomWarning (checking) × 23: Debug function usage
  • trivial-unfold@0.5.0/src/Data/Unfoldable1/Trivial1.purs:150:17 — MissingPatterns (checking): Pattern match is not exhaustive. Missing: Right _
  • xterm@1.0.0/src/XTerm/UnicodeHandling.purs:15:1 — UnparseableFFIModule (javascript) × 2: Oxc could not parse the JavaScript FFI module. Fix the invalid or unsupported JavaScript syntax; Iris treated the module as opaque and skipped export-name validation: Expected a semicolon or an implicit semicolon after a statement, but found none
  • yoga-react-dom@2.0.1/src/Yoga/React/DOM.purs:34:31 — DuplicateImport (indexing): Import list contains multiple references to 'Proxy'
  • yoga-tree-utils@1.0.0/src/Yoga/Tree/Extended/Path.purs:20:72 — DuplicateImport (indexing): Import list contains multiple references to 'snoc'

View workflow run

@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review summary

I found no issues I'm confident are real, so there are no inline comments.

The change is in the layer that owns it. The delimiter-alignment choice moved into Printer::delimited, which now decides it from the tree kind. has_inline_block extends the existing do/ado attachment rule to case instead of adding a special case at each call site. The data chain now starts at = instead of after it. All coverage is in tests-integration formatting fixtures, which is the level AGENTS.md assigns to formatter behaviour. I read every changed snapshot against the PR description, and they match the intended layout:

  • opener-relative ( item / , item lists
  • fully expanded exports when the module header wraps
  • Box(First, Second) kept compact; Box ( First only when the list is multiline
  • case headers attached after =, a lambda, or an operator
  • a leading = aligned with the | alternatives

Note, not a blocker: parenthesised do/ado bodies now hang from the opener column, like records and arrays already do. In narrow layouts this costs width; for example, applicative in 1791044520_delimited_do_headers at width 30 and at width 40/indent 4 now puts each ado statement's right-hand side and its in result on separate lines. That seems to follow from the alignment design, so I'm noting it in case it wasn't intended.

Checks run

Check Result
cargo check -p formatting --tests ✅ passes
just t formatting (no filters) ✅ all tests passed, no pending snapshots
just format --check ✅ no changes
just licenses, then git status ✅ THIRDPARTY.toml unchanged
git status after the integration run ✅ clean, no .snap.new files

I didn't rerun the full workspace suite because CI's Cargo Build & Test covers it.

@purefunctor

purefunctor Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Granite Review

Council review superseded by a newer revision

Progress ███████░░░ 2/3

Role Mode Status Attempt Amp thread
Council member review-gpt-6-luna Complete 1 T-01a11673-9e5a-757f-995d-1a1103450515
Council member review-gpt-6-sol Complete 1 T-01a11673-9e88-7636-ab46-79d1543aa82b
Synthesis review-astra-synthesis Cancelled 1 —

Open this run in Granite

@purefunctor

purefunctor Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Granite Review

Council

Role Mode Status Attempt Amp thread
Council member review-gpt-6-luna completed 1 T-01a11678-2f32-71dd-8931-63656f1317f1
Council member review-gpt-6-sol completed 1 T-01a11678-2eb7-70fd-b211-70bc1db5fe20
Synthesis review-astra-synthesis completed 1 T-01a1167a-d13b-771d-8e74-a6a921c38640

Findings

No actionable findings.

Open this run in Granite

@purefunctor
purefunctor force-pushed the formatting/layout-alignment branch from 0da7d37 to ad517a8 Compare October 7, 2026 14:28
@purefunctor

purefunctor Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Granite Review

Council

Role Mode Status Attempt Amp thread
Council member review-gpt-6-luna completed 1 T-01a116c6-06cc-75e2-a9e7-0c05395ce98a
Council member review-gpt-6-sol completed 1 T-01a116c6-0767-70b8-a666-964e8b647245
Synthesis review-astra-synthesis completed 1 T-01a116cc-1802-700b-934b-cf5e4083f8c8

Findings

No actionable findings.

Open this run in Granite

⇐ Ord value

class (Eq value, Show value) ⇐ Thing
value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This accepted expectation breaks the class head Thing value itself rather than the superclass list:

class (Eq value, Show value) ⇐ Thing
    value

The flat line class (Eq value, Show value) ⇐ Thing value is 42 columns, over the 40-column limit. The commit message for this change says broken superclass lists move below class "while allowing the closing parenthesis, constraint arrow, and class head to fit together". That would give:

class
    ( Eq value
    , Show value
    ) ⇐ Thing value

At width 30 / indent 2, this same declaration does take that layout (lines 45–49). At width 40 / indent 4 it doesn't. The likely cause is that the prefix group built by self.delimited(constraints, head_context, Some(keyword)) in printer.rs:475 decides whether to break by looking only as far as the next soft break inside the class head. class (Eq value, Show value) ⇐ Thing fits in 40 columns, so the list stays flat and the head application breaks instead.

AGENTS.md: "Snapshots record observed behavior; passing or accepting them does not establish semantic correctness. Check the result against the intended behavior." This snapshot contradicts the stated intent, so either the layout decision should take the whole class head into account, or the expectation should be explained.

@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review checks

Ran against the PR merge commit (head ad517a8):

  • cargo check -p formatting --tests: passed.
  • cargo nextest run -p formatting: the crate has no unit tests ("no tests to run"), as expected for formatter behavior that lives in fixtures.
  • just t formatting (unfiltered): all tests passed, no pending snapshots. git status was clean afterwards, with no .snap.new files.
  • just format --check: clean.
  • just licenses: THIRDPARTY.toml unchanged.

Tests are at the level AGENTS.md assigns: the source-level formatter behavior is covered by tests-integration fixtures, and no duplicate unit tests were added. I read every regenerated snapshot against the stated intent. Most changes match the PR description: opener-relative delimiters, = aligned with | in data declarations, and case headers attached compactly.

There is one inline finding. In 1791046620_constraint_continuation_comments, the accepted width-40 / indent-4 expectation breaks the class head (⇐ Thing / value) instead of moving the superclass list below class. That contradicts the commit's stated intent, and the same declaration does get the intended layout at width 30.

I couldn't build the CLI in this environment to probe further cases by hand, so the finding rests on the committed snapshot.

🤖 Generated with Claude Code

@purefunctor purefunctor changed the title [formatting] Align delimiters and block headers [formatting] Unify delimiter alignment and block continuation layouts Oct 7, 2026
@purefunctor

purefunctor Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Granite Review

Council

Role Mode Status Attempt Amp thread
Council member review-gpt-6-luna completed 1 T-01a1172b-1e42-777e-bbb5-9cd41516b3ef
Council member review-gpt-6-sol completed 1 T-01a1172b-204f-7747-868d-5ed64a4065aa
Synthesis review-astra-synthesis completed 1 T-01a11734-c9dc-723e-a5ed-64d5872bedb6

Findings

No actionable findings.

Open this run in Granite

@claude

claude Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review checks (head 5869c9c)

This re-review covers the two follow-up commits, Record superclass layout at complete-head width boundaries and Fit complete class heads before breaking superclass lists, which address the earlier finding about the width-40 / indent-4 class head.

  • cargo check -p formatting --tests: passed.
  • just t formatting (unfiltered): all tests passed, no pending snapshots.
  • just format --check: clean.
  • just licenses: THIRDPARTY.toml unchanged. git status was clean afterwards, with no .snap.new files.

In 1791046620_constraint_continuation_comments, the superclass list now moves below class, keeping ) ⇐ Thing value together, at widths 20, 30, 40 and 41. The compact head class (Eq value, Show value) ⇐ Thing value is exactly 42 columns and stays on one line at width 42. When a comment prevents a flat header, the structured list is used. This matches the stated intent, so the earlier finding is resolved.

The tests are at the level AGENTS.md assigns: they are formatter fixtures in tests-integration, with no duplicated unit tests. I have no new findings.

🤖 Generated with Claude Code

@purefunctor
purefunctor force-pushed the formatting/layout-alignment branch from 5869c9c to ece7842 Compare October 8, 2026 00:53
@purefunctor

purefunctor Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Granite Review

Council

Role Mode Status Attempt Amp thread
Council member review-gpt-6-luna completed 1 T-01a11900-e31d-720e-9acc-161e564cc494
Council member review-gpt-6-sol completed 1 T-01a11900-df68-7781-ac9c-0b840c0bc2ad
Synthesis review-astra-synthesis completed 1 T-01a11902-7efa-710e-8483-082bbdd64d87

Findings

No actionable findings.

Open this run in Granite

@claude

claude Bot commented Oct 8, 2026

Copy link
Copy Markdown

Review checks (head ece7842)

This re-review covers the history rewrite since 5869c9c. It focuses on the two new commits, Flatten direct else-if chains into one layout group (c74d2dd) and Destructure validated formatter syntax with slice patterns (ece7842).

  • cargo check -p formatting --tests: passed.
  • just t formatting (unfiltered): all tests passed, no pending snapshots.
  • just format --check: clean.
  • just licenses: THIRDPARTY.toml unchanged. git status was clean afterwards, with no .snap.new files.

Else-if chains. I checked 1791417240_else_if_chains at every configured width and indentation.

  • Direct else if alternatives share one margin and one compact/broken decision.
  • Parenthesized alternatives and line-forcing comments (-- and standalone {- -}) keep their nested continuation boundary.
  • Conditionals nested under then stay nested.
  • Operator operands align under the operand.

This matches what the commit says it does. The fixture lives in tests-integration, which is the right level.

Slice-pattern refactor. The case, let/ado, where and instance-chain else paths now destructure with slice patterns. No expectations changed, and the full formatting category still passes, so behavior is preserved as claimed.

Description note (not a code defect). The PR body still lists four layout commits. It also says the change will "Retain the structured multiline if/then/else layout", but c74d2dd now flattens direct else-if chains. Please update the description so it covers the else-if change.

I have no new inline findings.

🤖 Generated with Claude Code

purefunctor and others added 6 commits October 8, 2026 10:36
Use opener-relative, comma-leading layout for imports, exports,
parenthesized terms, types and binders, and constraint lists. Preserve
compact constructor-member spelling and fully expand wrapped exports.

Fit complete superclass headers before breaking their lists below
class, keeping the closing parenthesis, arrow, and class head together
when possible. Preserve comment boundaries and exclude class bodies
from the fit decision.

Include width-boundary, exact class-head fit, comment, constructor-member,
and binder fixtures with regenerated expectations.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a11570-3ec2-7539-bc19-3d4825503ee1
Place a broken data equation’s leading equals sign alongside its alternative pipes. Anchor constructor arguments to the constructor column instead of the declaration margin.

Regenerate declaration, record-constructor, and commented-separator expectations at the existing widths and indentation settings.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a11570-3ec2-7539-bc19-3d4825503ee1
Place parentheses, arrays, records, and record updates containing do or ado blocks below their prefixes. Apply the same rule to bindings, arguments, and operator operands instead of letting block bodies drift rightward.

Keep expression access and block detection on Tree, and include regenerated delimited-block and continuation-boundary fixtures.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a11570-3ec2-7539-bc19-3d4825503ee1
Treat case expressions as inline blocks and measure complete headers
independently of their branches. Keep a fitting header beside its prefix,
move it intact when necessary, and split case and of only when the
scrutinees require continuation layout.

Cover width boundaries, multiple and nested scrutinees, comments,
parentheses, operator operands, and nested block continuations.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a11570-3ec2-7539-bc19-3d4825503ee1
Keep clauses at one margin and decide compact versus multiline layout
for the complete chain. Reuse continuation formatting for wrapped
conditions, branch expressions, and blocks. Preserve parentheses and
line-forcing comment boundaries, and retain nested then conditionals.

Destructure validated conditional syntax with slice patterns. Cover
compact and broken chains, wrapped conditions, block branches, comment
boundaries, nested conditionals, parentheses, and operator operands.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a11570-3ec2-7539-bc19-3d4825503ee1
Replace positional syntax accesses for case, let/ado, where, and forall
with named structural patterns. Bind nonempty chain tails and leading
else declarations directly instead of repeating indexing assumptions.

Preserve formatting behavior, including empty ado bodies and optional
where bindings. Keep position-driven delimiter and operator iteration
unchanged. Existing formatting expectations require no updates.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a11570-3ec2-7539-bc19-3d4825503ee1
@purefunctor
purefunctor force-pushed the formatting/layout-alignment branch from ece7842 to aad73df Compare October 8, 2026 10:39
@purefunctor

purefunctor Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Granite Review

Council

Role Mode Status Attempt Amp thread
Council member review-gpt-6-luna completed 1 T-01a11b1a-9c03-742d-9839-070e7e1500b1
Council member review-gpt-6-sol completed 1 T-01a11b1a-9a30-708c-bf63-873ab0d912b8
Synthesis review-astra-synthesis completed 1 T-01a11b21-175b-7721-8345-d84c188ad0cf

Findings

No actionable findings.

Open this run in Granite

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant