Skip to content

Run Pyrefly alongside Mypy - #232

Merged
janluke merged 7 commits into
masterfrom
codex/add-pyrefly-check
Sep 11, 2026
Merged

Run Pyrefly alongside Mypy#232
janluke merged 7 commits into
masterfrom
codex/add-pyrefly-check

Conversation

@janluke

@janluke janluke commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Add Pyrefly to the Hatch typing workflow and every CI test environment, using its default preset across package sources, tests, and examples. Hatch, CI, and the plain Task typing workflow run Pyrefly before Mypy for fast failure; the full Task matrix runs both checkers concurrently for throughput. Unannotated test helper bodies retain Mypy's existing treatment, while annotated tests remain fully checked.

The integration also makes option-group help collection defer to Click's Option.get_help_record() contract, including custom options that omit a help row, and records the intentional checker differences around Cloup's fixed class factories, narrower group decorator API, and sequence narrowing. The individual commit messages explain the motivation and tradeoffs for each change in detail.

Closes #233.

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.68%. Comparing base (9e02a13) to head (b403bd8).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #232   +/-   ##
=======================================
  Coverage   96.68%   96.68%           
=======================================
  Files          22       22           
  Lines        1540     1540           
=======================================
  Hits         1489     1489           
  Misses         51       51           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Click makes Option.get_help_record() responsible for deciding whether an
option contributes a row to help output: its return type is a tuple or None.
Click currently returns None only for hidden options, which Cloup already
filtered before calling it, so this does not fix an observed runtime bug.

Custom Option subclasses, or a future Click implementation, may omit a row
for other reasons. Build the result from every non-None help record so
OptionGroup honors that upstream contract and its tuple-only return annotation
remains accurate. The assignment expression evaluates get_help_record() once
and makes the separate hidden check unnecessary.

Cover the extension point with a visible custom option that returns None.
@janluke
janluke force-pushed the codex/add-pyrefly-check branch from 54c27a6 to dd7147f Compare September 10, 2026 22:58
Click exposes Command.context_class and Context.formatter_class as writable
class attributes typed as subclasses of its Context and HelpFormatter bases.
Cloup narrows them because its command and formatting implementations rely on
the corresponding Cloup extensions.

That narrowing violates the Liskov substitution principle at the type level.
Code accepting a Click base class may assign any compatible Click factory,
including one that does not produce a Cloup subclass. The same assignment is
not valid for Cloup, so a mutable override cannot safely narrow its type and
Pyrefly is correct to reject it.

These settings should have been modeled as read-only class-level accessors.
In the absence of class properties, a class method returning the factory type
would let each subclass override it with a covariant return type while keeping
clients from assigning a different factory. Final is not an alternative
because it would also forbid subclasses from specializing the setting.

Click's public API and internals already use attributes, so Cloup cannot adopt
the class-method design without breaking compatibility. Retain the established
runtime contract and suppress only these two deliberate mutable-override
diagnostics.
Click's Group.command() and Group.group() methods accept bare decorator use and
additional positional forms. Cloup deliberately requires parentheses and
makes the class and Cloup-specific settings keyword-only. This narrower API is
documented and tested, but it is not substitutable for every invocation
accepted by the Click base methods.

Keep Cloup's existing overloads and runtime behavior. Suppress Pyrefly's
bad-override diagnostic at the two declarations, alongside the existing Mypy
override suppression, rather than widening the public signatures to advertise
forms that Cloup rejects.
Constraint.check() accepts either a sequence of parameter names or a sequence
of Click Parameter objects. After the first element is shown not to be a
string, Pyrefly narrows the union to the Parameter sequence and considers the
existing cast redundant. Mypy does not generalize that element check to the
sequence variant and still needs the cast in strict mode.

Keep the cast so Mypy can verify the downstream calls, explain why it remains,
and suppress only Pyrefly's redundant-cast warning at that expression.
@janluke
janluke force-pushed the codex/add-pyrefly-check branch 2 times, most recently from 9b1b1b9 to 1e6075d Compare September 11, 2026 00:44
Add Pyrefly to the shared development environment. In Hatch's aggregate typing
script, run it before Mypy so it provides fast feedback, while the subsequent
Mypy runs retain compatibility with the project's established checker and its
strict source policy. These commands intentionally stop after the first
failure.

Split type checking into named Pyrefly and Mypy Hatch scripts, then compose
them in that order in the aggregate typing script. Expose the same scripts as
individual Task targets. The plain Task workflow also runs Pyrefly before Mypy,
while the full matrix workflow runs both checkers and all prepared environments
concurrently for throughput. Require the shared Hatch runner's inputs so
malformed internal calls fail clearly.

Run the checker-specific Hatch scripts as separate sequential CI steps in each
supported Python and Click environment. This keeps their results visible in
the GitHub job while preserving the same Pyrefly-first failure behavior.

Select Pyrefly's default preset explicitly instead of relying on automatic
migration from the small Mypy configuration. Start with package sources and
the dedicated public typing tests, and make unused Pyrefly suppressions errors
so checker-specific exceptions cannot silently become stale.
The command decorator overloads now preserve the concrete cloup.Command type
for the two test helpers. Their casts predate that typing improvement and no
longer communicate a real uncertainty: Pyrefly reports them as redundant, and
Mypy accepts the direct return values as well.

Return the decorated commands directly and remove the unused cast imports.
Expand Pyrefly's project inputs to the same source, test, and example trees
covered by the existing Mypy commands. Keep Pyrefly's default analysis and
return inference for examples.

Unlike Mypy's default mode, Pyrefly normally checks bodies of completely
unannotated functions. Much of the test suite intentionally uses dynamic
fixtures, monkey-patching, and invalid calls to exercise runtime errors. Disable
body checking only for unannotated test helpers to match the existing Mypy
policy; annotated tests, including the public typing-contract tests, remain
fully checked.

Add explicit roots for the src layout, the tests package, and the standalone
Manim example modules. Ignore only the optional click_default_group import when
it is unavailable, instead of hiding unrelated missing dependencies.
@janluke
janluke force-pushed the codex/add-pyrefly-check branch from 1e6075d to b403bd8 Compare September 11, 2026 00:46
@janluke
janluke merged commit f3630fd into master Sep 11, 2026
15 checks passed
@janluke
janluke deleted the codex/add-pyrefly-check branch September 11, 2026 01:26
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.

Add Pyrefly alongside Mypy and make the code pass its check using the "default" preset

1 participant