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:

  • FilesArgs for target file paths
  • --json for pre-collected error lists
  • --remove-unused to toggle cleanup mode
  • --comment-location accepting line-before (default) or same-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:

  1. Error Collection: If --json is provided, errors deserialize into SerializedError structs; otherwise, the command invokes the check sub-command directly.
  2. Filtering: Directives and unused ignores are excluded from the add-suppression flow.
  3. Grouping: suppress_errors groups SerializedError instances by file path.
  4. Insertion: add_suppressions parses each file, merges new error codes with existing ignores via merge_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_range helper remaps errors inside multi-line strings or f-strings to the start line, ensuring comments are placed outside the literal.
  • Line Endings: detect_line_ending preserves CRLF vs. LF to avoid dirtying diffs.
  • Generated Files: A GENERATED_TOKEN guard skips files marked as auto-generated.
  • Comment Merging: Existing # pyrefly: ignore lines 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:

  1. Collect Current Errors

    pyrefly check . --output json > errors.json
  2. 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
  3. Upgrade Pyrefly

    Update your dependency via pip install -U pyrefly or bump the Cargo revision.

  4. Iteratively Fix Errors

    Address suppressed issues at your own pace while maintaining a green CI.

  5. Clean Up Stale Ignores

    Once fixes are complete, remove redundant annotations:

    pyrefly suppress . --remove-unused

    Alternatively, 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 suppress lives in pyrefly/lib/commands/suppress.rs and orchestrates the add/remove flow.

  • Add mode inserts # pyrefly: ignore[code] comments via suppress_errors in pyrefly/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) or same-line via CommentLocation.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →