Skip to content

align-match-case ​

align-match-case folds each single-statement case arm onto its case line and pads the space before the : so consecutive arms share one column, with the patterns flush left and the bodies starting at one column to the right. A match whose arms each contain one statement then reads as a dispatch table, patterns on the left and results on the right, and the reader scans rows rather than tracing each body.

The rule acts only on runs of single-statement arms at the same indentation, and an arm folds only where its one-line form fits within code-line-length. The rule measures that line with the : where the fold writes it, padded out to the run's column one space past the widest pattern, or flush against the pattern of an arm left in a group of its own. It reads the rest of the body's line at the width the other rules leave it, without the padding inside a bracket that strip-stranded-padding removes, with the space around each = at the width align-equals settles it to, and with a trailing comment at the gap and opener align-comments and normalize-comment-spacing settle it to. An arm stays multi-line and ends the run when:

  • It holds more than one statement
  • Its body is a compound statement, or spans several lines other than by hanging from a bracket that ends its case line
  • A comment follows its : on the case line
  • Its one-line form, padded out to the run's column, would cross code-line-length

The arm's : then sits flush against its pattern, and the arms on each side of it align as runs of their own. An arm already folded onto its case line ends the run the same way once its padded line would cross the budget, and a body on one row moves onto the next line. An own-line comment between two arms passes through, and the arms on both sides of it still share one column. A nested match aligns as a group of its own. Pair the rule with strip-stranded-padding, which strips the padding on a one-arm match, and with align-colons, which aligns the separators inside a dict a case body returns.

An arm already folded onto its case line whose body spans rows only inside a bracket that ends that line, with its closing bracket opening the last row at the case indent, still reads as a row of the table. It takes the run's column as the run's last member and ends the run there, so the arms below it align as a run of their own. The rule measures that arm's case line only through the opening bracket, and where the padded line would cross code-line-length the : sits flush against its pattern with the body left on the case line, since moving the body to a row of its own would leave the closing bracket outdented from it.

Configuration ​

KeyTypeDefaultMeaning
enabledbooltrueTurns the rule on or off.
max-shiftpositive int | 0 | false16How far apart the widest and narrowest rows of a run may be for the run to still align on one column. A positive N caps that gap, 0 forbids any padding so every row sits flush, and false removes the cap so a run of any width aligns on one column. A row marked # prose: skip stays out of its group.

max-shift limits how much padding one : may take. The rule reads each run of arms in source order and extends a column while the gap between the widest and narrowest patterns stays within the limit, starting a new column at the first arm that would exceed it. Setting max-shift to false removes the limit, so a run of any width aligns on one column, and 0 forbids padding altogether, so every : sits flush against its pattern. The per-rule facets reference covers the full semantics.

The Canonical Case ​

Every arm body in the match on token is a single statement, spanning pass, continue, break, a raise, a bare log(token) call, and a return. Each folds onto its case line and every : moves to one shared column, with the wildcard _ padded out to the width the string patterns set.

def dispatch(tokens):
    for token in tokens:
        match token:
            case "noop" : pass
            case "skip" : continue
            case "stop" : break
            case "boom" : raise RuntimeError("boom")
            case "echo" : log(token)
            case _      : return None
python

More Examples ​

The own-line comment # legacy synonyms sits between the first two arms of the match on credential.kind. The comment passes through unchanged and the arms on both sides of it still share one : column, so all three bodies fold onto their case lines and "certification" and "program" below the comment pad to the width "apprenticeship" sets above it.

case "tag" carries a trailing comment five spaces past its body, and with align-comments off nothing narrows that gap. Folded with its : padded out to the column "other" sets, the line would reach 90 columns, past the 88-column code-line-length. "tag" keeps its multi-line form, since the rule counts the gap as written when it measures the fold, and "other" folds alone with its : flush against the pattern.

case "apprenticeship" holds two statements, so it stays multi-line and ends the run, yet its : still carries the padding an earlier alignment wrote. The rule removes that padding, leaving the : flush against the pattern, while "certification" and "program" below it stay folded and move their : to the column "certification" sets.

Both arms already sit folded on their case lines, and ("mise", *_) is padded out to the : column ("gh", group, verb, *_) sets. That padding carries its line to 95 columns, past the 88-column code-line-length. The "mise" arm splits onto two lines, its body moving beneath the case and its : sitting flush against the pattern, and ("gh", group, verb, *_), now alone, sheds its padding as well.

The first arm's tuple pattern spreads 1 and 2 over parenthesized lines, so its : sits on a different line from its case. That arm passes through unchanged, whereas case other folds handle2() onto its line with the : flush against the pattern, because a pattern spanning several lines disqualifies the arm and no other folded arm remains to align with.

case "fail" holds one assignment whose right-hand side spreads base + extra over parenthesized lines. That arm passes through unchanged even though it holds a single statement, because a body spanning several lines disqualifies the arm, whereas case "ok" folds result = simple onto its line and, alone in its group, takes no padding.

case "certification" holds two statements, icon = "scroll" and notes.append("verified"), between three one-statement arms. The two-statement arm stays multi-line and splits the match into two groups, so case "apprenticeship" above it folds alone with its : flush against the pattern, and "program" and _ below it fold together on the column "program" sets.

case "wrap" holds a whole match inner: as its body, and case "skip" holds value = 0. The inner match aligns as its own group, folding "alpha", "beta", and "gamma" onto their case lines with their : on one column, whereas the outer case "wrap" stays multi-line because a compound body cannot fold, and case "skip" folds with its : flush against the pattern because it aligns alone.

case "tag" holds a return whose line carries a trailing comment and fits at 86 columns as written. Folding the arm would bring that comment up onto the case line, which would then reach 94 columns even with its : flush against the pattern, past the 88-column code-line-length. "tag" keeps its multi-line form, because the rule counts the comment toward the line's width when it measures the fold, and "other" folds alone with its : flush against the pattern.

The arms match | alternations of differing width, "ok" | "pass", "warn", and "fail" | "error" | "panic". The widest arm folds but drops out of the shared column, its : flush against the pattern, because padding "warn" out to "fail" | "error" | "panic" would exceed max-shift, whereas "ok" | "pass" and "warn" fold and share one column.

Five arms each hold one assignment, with patterns running from "under_88_columns" to "kind_with_descriptive_long_label". Only "under_88_columns" and "exactly_88_columns" fold onto their case lines and align their :, because an arm folds only where its one-line form fits within the 88-column code-line-length.

"longer_pattern_label" would reach 89 columns, "kind_with_descriptive_long_label" runs wider still, and the if some_long_predicate_check(event.kind) guard counts toward its arm's width, so those three arms stay multi-line.

case "install" sits folded with its call's argument on a row of its own and the ) back at the case indent, so its body spans three rows only inside the bracket ending its case row. The arm pads its : to the column "uninstall" sets for "build" and "uninstall" above it, and ends the run there, so "format" and "lint-everything" below align as a run of their own rather than sharing the column "lint-everything" would set for all five.

Two arms each hold one return, and case ("gh", group, verb, *_) runs eleven columns wider than case ("mise", *_). Folded alone, the "mise" arm fits at 81 columns, whereas padding its : out to the column ("gh", group, verb, *_) sets would carry the line to 93, past the 88-column code-line-length. The "mise" arm keeps its multi-line form and ends the run, so ("gh", group, verb, *_) folds alone with its : flush against the pattern.

Five arms each hold one assignment, and case "moved" carries a call that fits folded alone but not once its : pads out to the column "deleted_forever" sets. "moved" keeps its multi-line form, and the arms on either side of it fold as two runs of their own, "created" and "archived" sharing one column and "deleted_forever" and "restored" sharing another.

case "rebuilt" is the only arm in its match, folded with its return onto a line that reaches 95 columns even with its : flush against the pattern, past the 88-column code-line-length. The body moves onto its own line beneath the case, the one form of the arm that fits.

case "b" holds an assignment that fits at 86 columns folded with its : flush, whereas padding its : out to the column "an_arm_past_the_shift_cap" sets would carry the line to 111. Both arms fold with their : flush against their patterns, because the two patterns differ by more than max-shift, so the run splits before any shared column forms and "b" never takes that padding.

  1. The "alpha" and "beta" arms of dispatch each return a long inline dict, and the wildcard arm returns None. Each dict explodes to one entry per line, its keys sort so "comment_text" leads, and the : of each entry pads into one column within its arm, while align-match-case joins the wildcard's return None onto its case _: line.

  2. Three methods of Dispatcher sit out of order, and handle_alpha contains a match whose arms each return a single value. The methods sort so handle_alpha leads, align-match-case joins each return onto its case line and pads the : into one column, and space-statements writes one blank line between the methods.

  3. dispatch opens with the assignments x, yz, and qrs above a match whose arms each return one of those bindings. align-equals pads the three = into one column one space past qrs, and align-match-case joins each single return onto its case line with every arm : padded one space past the widest pattern, "alpha".