Skip to content

perfectionist: macro_trailing_comma contradicts rustfmt for single-element multi-line vec! #408

@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.

Summary

perfectionist::macro_trailing_comma (introduced in 0.0.0-rc.1+, present in 0.0.0-rc.6) fires on macro invocations whose shape cargo fmt actively reformats away from a trailing comma. There is no source formatting that satisfies both tools.

Two such sites exist in this repository at tests/tree_builder.rs:120-128 and tests/visualizer.rs:911-916. They are the reason #407 had to introduce crate-level suppressions.

Reproductions

Both fire perfectionist::macro_trailing_comma, and both are reverted by cargo fmt if a trailing comma is added manually or the body is broken across additional lines.

// Case 1 — vec! with one multi-line struct literal at a struct field
let value = Outer {
    children: vec![Inner {
        name: "x".to_string(),
        size: 1,
        kids: Vec::new(),
    }],
};
// Case 2 — vec! with one multi-line call as a positional argument
foo(
    "label",
    vec![bar(
        "very long name",
        4069,
    )],
);

Analysis

The rule's docstring says it enforces rustfmt's trailing_comma = "Vertical" setting. In practice rustfmt only adds the trailing comma when each element is on its own line; a single element that itself spans multiple lines is not considered "vertical" by rustfmt, so no trailing comma is inserted, and any manually-inserted one is removed on the next cargo fmt. The rule should mirror that behaviour: only fire when each element of the macro invocation sits on its own line.

Suggested upstream fix

Adjust the macro_trailing_comma pass to skip multi-line macro invocations whose argument list visually has one element with the closing bracket on the same indentation level as that element. That is the exact case rustfmt's Vertical setting does not normalise.

Current local workaround in this repo

PR #407 applies crate-level cfg_attr(dylint_lib = "perfectionist", …)-gated register_tool + expect attributes plus allow(unfulfilled_lint_expectations) to silence the rule only during dylint runs. The workaround is verbose enough that it is itself worth fixing upstream.

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