Skip to content

chore(rust): zero-warning baseline + CI gating for fmt/clippy #245

@TheHypnoo

Description

@TheHypnoo

Summary

The workspace has accumulated Rust hygiene debt that CI currently does not check:

  • cargo fmt --check: 7299 formatting hunks pending (no rustfmt.toml).
  • cargo build warnings: >= 373 (cacheable subset; the real total is higher because UI backends are excluded on the macOS host).
  • cargo clippy: >= 2311 lib warnings (961 with suggested auto-fixes). It has never run in CI; there is no clippy.toml or [workspace.lints] setup.
  • .github/workflows/*.yml: zero mentions of rustfmt/clippy. Jobs only run cargo build --release and cargo test.

External PRs currently compile with hundreds of warnings whose origin is hard to distinguish (pre-existing debt vs. new regression). Without gating and a clean baseline, we cannot enforce the principle of "do not add warnings". Also, because cargo fmt is not applied, each author formats differently, mixing logic changes with noisy reformatting in diffs.

CLAUDE.md (entry v0.5.343) already documented part of this debt and it was never closed:

"The remaining 255 are pre-existing categorical issues (63 unnecessary unsafe block in perry-runtime, 25 unreachable pattern in perry-hir/perry-codegen lowerers, 23 unnecessary transmute, etc. - separate bugs, not session-introduced)".

Baseline (measured on 1d1241d9)

cargo fmt --all -- --check

7299 hunks affected across the workspace.

cargo build --release --workspace (excluding non-host UI backends)

Crate Warnings Suggested auto-fixes
perry-runtime 146 42
perry-stdlib 66 24
perry-ui-macos 54 13
perry (bin) 45 -
perry-hir 21 -
perry-codegen 17 -
perry-codegen-js 7 -
perry-codegen-wasm 7 5
perry-jsruntime 6 -
perry-transform 2 2
perry-doc-tests 1 -
perry-ui-geisterhand 1 -
Total 373 86

cargo clippy --release --workspace --all-targets -- -W clippy::all

Excluding non-host UI backends (same subset as cargo build):

Crate Lib warnings Suggested auto-fixes
perry-runtime 733 198
perry-stdlib 581 76
perry-hir 485 422
perry-codegen 149 87
perry (bin) 119 45
perry-ui-macos 107 48
perry-codegen-wasm 42 30
perry-transform 36 27
perry-jsruntime 21 5
perry-codegen-js 19 12
perry-codegen-swiftui 4 4
perry-doc-tests 3 1
perry-ui-geisterhand 3 2
perry-codegen-glance 2 2
perry-ui (styling-matrix) 2 0
perry-types 1 1
perry-diagnostics 1 0
perry-updater 1 1
perry-ui-testkit 1 0
perry-ui-test 1 0
Total 2311 961

Tests add ~32 additional non-duplicated warnings (perry-runtime tests 27, perry-stdlib tests 4, perry-hir tests 1), for a grand total of approximately 2343 clippy warnings. That is roughly 6.3x the cargo build warning count, which matches expectations because clippy applies additional rules on top of compiler warnings.

Dominant categories

  • 63 unnecessary unsafe block warnings in perry-runtime
  • 25 unreachable pattern warnings in perry-hir/perry-codegen lowerers
  • 23 unnecessary transmute warnings
  • Multiple dead_code warnings (unused fields/functions) in crates/perry/src/commands/{publish,run,setup,typecheck}.rs

Design decisions

  • Refactor scope: cargo fmt + cargo build warnings + default clippy (-W clippy::all, no pedantic/nursery).
  • CI gating: apply RUSTFLAGS="-D warnings" only in the CI lint job. Local builds remain permissive to avoid breaking debugging workflows.
  • Prioritization: work crate by crate, starting with perry-runtime (146 warnings, the dirtiest crate) and continuing by warning count. Each sub-PR focused on a single subdirectory is easier to review for contributors who know that domain.

Proposed refactor plan

Sub-PRs in this order:

PR-A - rustfmt baseline (mechanical)

  • Create a minimal rustfmt.toml (edition = "2021", with no additional options so the first pass does not impose opinionated style choices).
  • Run cargo fmt --all and commit the result.
  • Create .git-blame-ignore-revs with the formatting commit SHA so git blame can skip it. Document local setup in CLAUDE.md: git config blame.ignoreRevsFile .git-blame-ignore-revs.

Large diff (~7299 hunks), but 100% mechanical. Coordinate timing with in-flight PRs to avoid painful rebases.

PR-B - global cargo fix (~86 auto-applicable fixes)

  • cargo fix --workspace --release --allow-dirty --allow-staged.
  • cargo clippy --fix --workspace --all-targets --allow-dirty -- -W clippy::all.
  • Human review before commit (changes around unsafe require care).

PR-C - crate-by-crate refactor

One sub-PR per crate, in this order:

Sub-PR Crate Warnings Expected dominant pattern
C1 perry-runtime 146 unnecessary unsafe (63), unnecessary transmute (23), dead_code
C2 perry-stdlib 66 dead_code, unused_imports
C3 perry-ui-macos 54 unused_imports, dead_code
C4 perry (bin) 45 dead_code in commands/{publish,run,setup,typecheck}.rs
C5 perry-hir 21 unreachable_pattern (careful audit; possible hidden bug)
C6 perry-codegen 17 unreachable_pattern, dead_code
C7 remaining "cleaner" crates (-codegen-js, -codegen-wasm, -jsruntime, -transform, -doc-tests, -ui-geisterhand) ~24 single PR for these 6 crates
C8 non-host UI backends (-ui-{ios,tvos,visionos,watchos,android,gtk4,windows}) unknown sub-PR when the corresponding runner applies

Rules for each sub-PR:

  • dead_code: delete if genuinely unused; use #[allow(dead_code)] with a reason header only if it is API pending wiring.
  • unreachable_pattern: review each case manually. It may hide a bug in a match arm that is reached too early. Treat any finding as a separate bug if it is not trivial.
  • unnecessary unsafe: if cargo fix did not remove it, remove it manually and verify that cargo test remains green.
  • unnecessary transmute: replace with an as cast or From/Into where applicable.
  • Each sub-PR closes with cargo build --release -p <crate> 2>&1 | grep warning -> 0 warnings.

PR-D - enable [workspace.lints] and CI gating

Root Cargo.toml:

[workspace.lints.rust]
warnings = "warn"          # local: warn, not deny; permissive DX

[workspace.lints.clippy]
all = "warn"

Each crates/*/Cargo.toml:

[lints]
workspace = true

New lint job in .github/workflows/test.yml (parallel to test/parity/compile-smoke/doc-tests):

lint:
  runs-on: macos-14
  timeout-minutes: 20
  steps:
    - uses: actions/checkout@v4
    - uses: dtolnay/rust-toolchain@stable
      with:
        components: rustfmt, clippy
    - uses: Swatinem/rust-cache@v2
    - run: cargo fmt --all -- --check
    - run: cargo clippy --release --workspace --all-targets -- -D warnings
      env:
        RUSTFLAGS: "-D warnings"

RUSTFLAGS="-D warnings" applies only in this CI job, not in other cargo build jobs or local builds. Exclude UI backends that do not compile on the host (same --exclude pattern as the test job).

PR-E - documentation

  • Add a "Workflow Requirements" section to CLAUDE.md: run cargo fmt --all and cargo clippy --workspace --all-targets before commit.
  • Add an "External contributor PRs" section: PRs must pass the lint job (cargo fmt --check + cargo clippy -D warnings).
  • Mention .git-blame-ignore-revs and the local activation command.

Checklist

  • PR-A - rustfmt baseline
  • PR-B - global cargo fix
  • PR-C1 - perry-runtime cleanup (146 -> 0)
  • PR-C2 - perry-stdlib cleanup (66 -> 0)
  • PR-C3 - perry-ui-macos cleanup (54 -> 0)
  • PR-C4 - perry (bin) cleanup (45 -> 0)
  • PR-C5 - perry-hir cleanup (21 -> 0): audit unreachable_pattern
  • PR-C6 - perry-codegen cleanup (17 -> 0)
  • PR-C7 - remaining "cleaner" crates
  • PR-C8 - non-host UI backends
  • PR-D - [workspace.lints] + CI lint job
  • PR-E - documentation

Risks

  • PR-A diff of 7299 hunks: reviewable because it is 100% mechanical. Coordinate with in-flight PRs.
  • -D warnings in CI can break builds when future compiler/clippy versions add lints. Mitigation: pin rust-toolchain.toml in PR-D if it does not already exist.
  • PR-C5/C6 (unreachable_pattern): may reveal real matching bugs. Treat findings as separate issues if they are not trivial.
  • C8 UI backends are cross-compile only; some can only be linted on the corresponding runner (Linux for gtk4, Windows for windows). PR-D must either extend the job to other runners or document the limitation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions