Skip to content

tyc fmt

tyc fmt [PATHS]... [--check]

Parses .ty files, runs the in-process whitespace normaliser, then — when ruff is on PATH and the post-normalised buffer contains no Typhon-only tokens — pipes the result through ruff format for spacing around :, =, ->, line wrapping, and quote normalisation. If ruff is missing or fails, the in-process output is kept.

The composition is deliberate: the in-process pass strips every Typhon-only keyword (let, mut, interface, model, gather, go, lazy, comptime, unsafe, extend, frozen, class!, plain class, ? operator, etc.) before ruff sees the buffer, so ruff never encounters syntax it can’t parse.

Examples

Terminal window
tyc fmt src/ # format every .ty in src/
tyc fmt src/main.ty # format one file
tyc fmt --check src/ # exit 1 if anything would change (CI mode)

What the in-process pass changes

  • Interior whitespace — runs of 2+ spaces past the leading indent are collapsed to one (def main → def main).
  • Bracket adjacency — ( x → (x, x ) → x), x , → x,.
  • Comma spacing — a run of whitespace after , is normalised to a single space (a, b → a, b).
  • Comment hash spacing — #foo → # foo. Shebangs (#!…) and section-marker double-hash comments (## …) are left alone.
  • Leading tab expansion — a tab character at the start of a line is expanded to four spaces.
  • Blank-line runs — three or more consecutive blank lines are collapsed to two.
  • Trailing whitespace — removed (including from whitespace-only lines, which become empty).
  • Line endings — \n.

These rules all skip text inside '…', "…", and triple-quoted regions, so docstrings and embedded code stay verbatim.

What the ruff format wrap adds

When ruff is on PATH and the post-normalised buffer contains no Typhon-only tokens, the buffer is piped through ruff format --stdin-filename <path>. This brings ruff’s standard formatter rules to Typhon source for free:

  • Spacing around :, =, -> with bracket-depth awareness (slice vs annotation distinction).
  • Long-line wrapping with consistent break points.
  • Quote normalisation per ruff format’s configured style.

If ruff is absent, returns non-zero, or the buffer still contains a Typhon-only token after the in-process pass, the in-process output is kept and a single warning is printed. The wrap is best-effort by design — tyc fmt always produces a valid, parseable result.

What it preserves

  • Indentation depth — the leading run of spaces is kept; only the characters change (tab → four spaces).
  • Typhon-only syntax — let / mut / ? / class! / plain class / frozen / gather / go / lazy / comptime / unsafe / extend / impl / interface / model are recognised and round-tripped unchanged.
  • Comment text — content after the normalised # prefix is kept verbatim; shebangs and ## … lines are passed through unchanged.

Flags

FlagEffect
--check (-c)Exit 1 if any file would change. No writes.

--check is the only flag. There is no --diff or --quiet; to see what would change, run tyc fmt --check, or format a copy and diff it against the original.

CI integration

- name: Format check
run: tyc fmt --check src/

Pre-commit

.pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: tyc-fmt
name: tyc fmt
entry: tyc fmt
language: system
files: \.ty$

Files the formatter cannot parse

A file that does not parse is reported and skipped — the walk carries on through the rest of the tree, and the command exits non-zero at the end with a count (N files could not be formatted). One work-in-progress file therefore never stops tyc fmt src/ from formatting everything else, and tyc fmt --check in CI surfaces every failure in a single run rather than one per invocation.

The set of files the formatter rejects is the set tyc check rejects: both run the same sugar-expansion chain over the original source before parsing.

Guarantees

Three properties hold over the whole .ty corpus and are gated by the test suite:

  • Convergence — a second tyc fmt over already-formatted output is a no-op. tyc fmt --check after tyc fmt always passes.
  • Acceptance parity — the formatter accepts exactly what tyc check accepts.
  • Meaning is preserved — tyc build emits byte-identical Python before and after formatting. The one thing that can move is a generated __typhon_guard_N temporary, whose number is derived from the source line it came from; source maps are regenerated for the new line numbers.

tyc fmt never rewrites a file that resolves outside the project it was pointed at (the nearest directory above the path with a typhon.toml). A checked-out src -> ../elsewhere link, or a link inside the tree that leaves it, is skipped with a warning instead of being written through; a link that stays inside the project is followed. A file that is itself a symlink is never written through either.

What about tyc fmt on .dty?

Not today — tyc fmt collects .ty files only; a .dty path, or the .dty files under a directory, are skipped untouched. Stubs are short signature lists, so hand-formatting them is cheap, and tyc check --stubs still validates them.

Where next