How to Use pyrefly suppress to Migrate Existing Errors When Upgrading
pyrefly suppress automatically inserts or removes # pyrefly: ignore[…] comments in Python source files, allowing teams to freeze existing diagnostics before upgrading Pyrefly and clean up stale annotations afterward.
When upgrading the facebook/pyrefly type checker, new error kinds or stricter diagnostics often break existing code. Rather than blocking the upgrade, you can use pyrefly suppress to annotate current violations, upgrade safely, and iteratively fix issues while keeping your codebase clean.
The Two Modes of pyrefly suppress
The command operates in two mutually exclusive modes controlled by the --remove-unused flag. Both entry points live in pyrefly/lib/commands/suppress.rs (lines 48–84).
Add Suppressions (default) scans for user-visible errors—excluding directives like reveal_type and already-unused ignores—and inserts ignore comments. Remove Unused Ignores deletes annotations that no longer match any diagnostic or strips only the stale error codes while preserving still-relevant ones.
Architecture and Implementation
CLI Parsing with SuppressArgs
The SuppressArgs struct (derived from clap::Parser) collects configuration options in pyrefly/lib/commands/suppress.rs:
FilesArgsfor target file paths--jsonfor pre-collected error lists--remove-unusedto toggle cleanup mode--comment-locationacceptingline-before(default) orsame-line
#[derive(Clone, Debug, Parser)]
pub struct SuppressArgs { … }
Core Suppression Pipeline
When SuppressArgs::run executes, it delegates to algorithms in pyrefly/lib/error/suppress.rs:
- Error Collection: If
--jsonis provided, errors deserialize intoSerializedErrorstructs; otherwise, the command invokes thechecksub-command directly. - Filtering: Directives and unused ignores are excluded from the add-suppression flow.
- Grouping:
suppress_errorsgroupsSerializedErrorinstances by file path. - Insertion:
add_suppressionsparses each file, merges new error codes with existing ignores viamerge_error_codes, and rewrites the file.
The SerializedError struct provides a cheap, JSON-friendly representation containing the file path, line number, error name, and message.
Edge Case Handling
The implementation in pyrefly/lib/error/suppress.rs guarantees correctness across diverse Python constructs:
- Multiline Strings: The
find_containing_rangehelper remaps errors inside multi-line strings or f-strings to the start line, ensuring comments are placed outside the literal. - Line Endings:
detect_line_endingpreserves CRLF vs. LF to avoid dirtying diffs. - Generated Files: A
GENERATED_TOKENguard skips files marked as auto-generated. - Comment Merging: Existing
# pyrefly: ignorelines are updated rather than duplicated when new error codes apply to the same line.
Removing Stale Ignores
In removal mode (--remove-unused), remove_unused_ignores_from_serialized parses each file and matches error messages starting with "Unused …". It either deletes the entire comment or surgically removes only the unused codes via update_ignore_comment_with_used_codes, respecting string literals and backslash continuations.
Step-by-Step Migration Workflow
Follow this sequence to upgrade Pyrefly without breaking your build:
-
Collect Current Errors
pyrefly check . --output json > errors.json -
Suppress Existing Violations
Choose comment placement based on your style guide:
# Place comments on the line before (default) pyrefly suppress . --json errors.json --comment-location line-before # Or append to the same line pyrefly suppress . --json errors.json --comment-location same-line -
Upgrade Pyrefly
Update your dependency via
pip install -U pyreflyor bump the Cargo revision. -
Iteratively Fix Errors
Address suppressed issues at your own pace while maintaining a green CI.
-
Clean Up Stale Ignores
Once fixes are complete, remove redundant annotations:
pyrefly suppress . --remove-unusedAlternatively, supply a JSON dump of unused-ignore errors:
pyrefly suppress . --json unused_ignores.json --remove-unused
CLI Usage Examples
# Add suppressions for all errors in the project (line-before placement)
pyrefly suppress .
# Use same-line comments for compact diffs
pyrefly suppress . --comment-location same-line
# Process errors from a CI artifact instead of running check again
pyrefly suppress . --json /tmp/errors.json
# Remove ignores that no longer apply
pyrefly suppress . --remove-unused
# Clean up using a pre-generated list of unused ignores
pyrefly suppress . --json /tmp/unused.json --remove-unused
Programmatic Usage in Rust
You can invoke the suppression engine directly from Rust code:
Adding Suppressions:
use pyrefly::error::suppress::{self, CommentLocation, SerializedError};
fn silence_errors(errors: Vec<SerializedError>) {
// Place ignore comments on the line before each error
suppress::suppress_errors(errors, CommentLocation::LineBefore);
}
Removing Unused Ignores:
use pyrefly::error::suppress;
use pyrefly::state;
fn clean_ignores(path: &std::path::Path) {
// Collect unused ignore diagnostics from the type-checking state
let unused = state::errors::collect_unused_ignore_errors(&state);
// Remove them from source files
suppress::remove_unused_ignores(unused);
}
Sample JSON Input:
[
{
"path": "src/example.py",
"line": 12,
"name": "bad-assignment",
"message": "Assignment of type `str` to a variable declared as `int`"
}
]
Running pyrefly suppress with the above produces:
# pyrefly: ignore [bad-assignment]
x: int = "oops"
Summary
-
pyrefly suppresslives inpyrefly/lib/commands/suppress.rsand orchestrates the add/remove flow. -
Add mode inserts
# pyrefly: ignore[code]comments viasuppress_errorsinpyrefly/lib/error/suppress.rs, merging with existing annotations and handling multiline strings. -
Remove mode deletes or trims stale ignores via
remove_unused_ignores_from_serialized, respecting generated file tokens and line endings. -
Migration workflow: Collect errors → Suppress → Upgrade → Fix → Remove unused.
-
Placement options:
line-before(default) orsame-lineviaCommentLocation.
Frequently Asked Questions
What is the difference between adding and removing suppressions?
Adding suppressions (the default) inserts new ignore comments for every current error except directives and already-unused ignores. Removing suppressions (--remove-unused) scans for diagnostics starting with "Unused …" and either deletes the comment entirely or strips only the specific error codes that no longer apply, leaving valid ignores intact.
How does pyrefly suppress handle multiline strings?
The algorithm uses find_containing_range to detect when an error occurs inside a multiline string or f-string literal. It remaps the error location to the start line of the literal, ensuring that # pyrefly: ignore comments are placed outside the string content where they are syntactically valid.
Can I use pyrefly suppress in CI pipelines?
Yes. Run pyrefly check --output json to generate an error artifact, then pass it to pyrefly suppress --json in a subsequent job. This avoids re-running the type checker and ensures consistent suppression generation across different environments.
Where are the core suppression algorithms implemented?
The CLI entry point and argument parsing reside in pyrefly/lib/commands/suppress.rs. The actual logic for adding and removing comments—including SerializedError, suppress_errors, add_suppressions, and remove_unused_ignores_from_serialized—is implemented in pyrefly/lib/error/suppress.rs. Helpers for locating error ranges and collecting unused ignores are found in pyrefly/lib/state/errors.rs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →