Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-idempotent comment movement for ((x) # comment here ...) with parens and 3+ lines #16151

Closed
huonw opened this issue Feb 14, 2025 · 2 comments · Fixed by #16187
Closed

Non-idempotent comment movement for ((x) # comment here ...) with parens and 3+ lines #16151

huonw opened this issue Feb 14, 2025 · 2 comments · Fixed by #16187
Assignees
Labels
bug Something isn't working formatter Related to the formatter great writeup A wonderful example of a quality contribution

Comments

@huonw
Copy link

huonw commented Feb 14, 2025

Description

Context

We have some code along the lines of:

result = (
   (await query_the_thing(mypy_doesnt_understand))  # type: ignore[x]
   .foo()
   .bar()
)

Switching from black to ruff format results in the comment moving:

  • in a non-idempotent way, requiring two ruff format invocations
  • to a 'strange' location

Reproducer

Formatting this code requires multiple invocations of ruff format to be "stable"/reach a fixed point:

  1. Input:

    (
        (x)  # a
        .y()
        .z()
    )
  2. Output of formatting 1 (change: comment is shifted to next line):

    (
        (x)
          # a
        .y()
        .z()
    )
  3. Output of formatting 2 (change: comment is unindented):

    (
        (x)
        # a
        .y()
        .z()
    )

Playground links:

Expected behaviour

ruff format should be idempotent: the output of formatting should not have any changes if formatted again.

That is, the first format call (step 1 -> 2) should give the final result with the comment in its final position (unindented), and the second (step 2 -> 3) should not change the code.

Alternatively, the formatting for this circumstance changed to sidestep the issue. It's seems weird to me that this comment is being wrapped. It moving the comment to the next line also problems if the comment is a # type: ... or # noqa pragma.

See also "Ablations".

Ablations

This stops reproducing with these variations of the Input (1):

  1. Only one extra line after the comment (no change: comment remains where it is)

    (
        (x)  # a
        .y()
    )
  2. No parens around x (becomes x.y().z() # a)

    (
        x  # a
        .y()
        .z()
    )
  3. Comment on a different line (no change):

    (
        (x)
        .y()  # a
        .z()
    )

Metadata

Ruff version: 0.9.6

Settings (default play.ruff.rs ones):

{
  "preview": false,
  "builtins": [],
  "target-version": "py312",
  "line-length": 88,
  "indent-width": 4,
  "lint": {
    "allowed-confusables": [],
    "dummy-variable-rgx": "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$",
    "extend-select": [],
    "extend-fixable": [],
    "external": [],
    "ignore": [],
    "select": [
      "ALL"
    ]
  },
  "format": {
    "indent-style": "space",
    "quote-style": "double"
  }
}

Search keywords: idempotent, idempotency, reformat, repeated invocations, fixed point, stable


Thanks for ruff!

@dylwil3 dylwil3 added bug Something isn't working formatter Related to the formatter labels Feb 14, 2025
@MichaReiser MichaReiser self-assigned this Feb 14, 2025
@MichaReiser MichaReiser added the great writeup A wonderful example of a quality contribution label Feb 14, 2025
@MichaReiser
Copy link
Member

Thanks for this great write up! I hope to get time to look into this next week.

@MichaReiser
Copy link
Member

In case you're blocked by this. You can work around this bug if you move the type[ignore] into the parenthesized range

result = (
    (
        await query_the_thing(mypy_doesnt_understand)  # type: ignore[x]
    )
    .foo()
    .bar()
)

It definetely is less elegant than the fixed formatting:

result = (
    (await query_the_thing(mypy_doesnt_understand))  # type: ignore[x]
    .foo()
    .bar()
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working formatter Related to the formatter great writeup A wonderful example of a quality contribution
Projects
None yet
3 participants