Skip to content

perfectionist: macro_trailing_comma short-circuits on crate-level #[expect], leaving the expectation unfulfilled #409

@KSXGitHub

Description

@KSXGitHub

Filed here because the MCP scope used by the assistant session that produced #407 cannot write to KSXGitHub/perfectionist. Forward upstream when convenient. See #408 for the underlying rustfmt contradiction that forced us to suppress this lint at all.

Summary

When perfectionist::macro_trailing_comma is suppressed at crate scope with #[expect], the rule does not register a fulfillment, so rustc's unfulfilled_lint_expectations lint fires. This makes the user-preferred #[expect] form unusable on its own, even though violations exist in the crate.

Reproduction

In a file with at least one vec! invocation that would trigger the rule (see #408 for the shapes that fire):

#![cfg_attr(dylint_lib = "perfectionist", feature(register_tool))]
#![cfg_attr(dylint_lib = "perfectionist", register_tool(perfectionist))]
#![cfg_attr(
    dylint_lib = "perfectionist",
    expect(
        perfectionist::macro_trailing_comma,
        reason = "rustfmt collapses single-element multi-line vec!"
    )
)]

Running RUSTFLAGS="-D warnings" cargo dylint --all -- --all-features --all-targets reports:

error: this lint expectation is unfulfilled
  --> tests/tree_builder.rs:6:9
   |
 6 |         perfectionist::macro_trailing_comma,
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The same crate without the #[expect] reports the underlying macro_trailing_comma violations, so the diagnostic is suppressible — but suppression appears to short-circuit the rule entirely instead of letting rustc track the fulfillment.

Expected behaviour

macro_trailing_comma should always emit its diagnostic with the appropriate span. rustc's #[expect] machinery is responsible for hiding the visible output and marking the expectation fulfilled. The rule should not early-exit when it sees a crate-level #[allow]/#[expect] of itself.

Current workaround in this repo

PR #407 pairs the #[expect] with #[allow(unfulfilled_lint_expectations, reason = "perfectionist's macro_trailing_comma short-circuits when its lint is allowed/expected at crate scope")], which defeats the point of preferring #[expect] over #[allow].

Documentation gap, additionally

The working suppression incantation is non-obvious and required several iterations during #407:

#![cfg_attr(dylint_lib = "perfectionist", feature(register_tool))]
#![cfg_attr(dylint_lib = "perfectionist", register_tool(perfectionist))]
#![cfg_attr(
    dylint_lib = "perfectionist",
    expect(perfectionist::<rule>, reason = "…")
)]
#![allow(unexpected_cfgs, reason = "dylint_lib is not in --check-cfg")]

Without register_tool, stable consumers hit error[E0710]: unknown tool name 'perfectionist'. Without the dylint_lib cfg gate, every non-dylint cargo check fails. A README section describing this recipe would save downstream consumers a lot of debugging.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions