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

Fix update build files formatter selection #20580

Conversation

krishnan-chandra
Copy link
Contributor

@krishnan-chandra krishnan-chandra commented Feb 19, 2024

Closes #20576.

Although this is a relatively small change, I think it's good to understand the change in context so I've included the new and old logic below for comparison.

New logic:

formatter_to_request_class: dict[Formatter, type[RewrittenBuildFileRequest]] = {
    Formatter.BLACK: FormatWithBlackRequest,
    Formatter.YAPF: FormatWithYapfRequest,
    Formatter.RUFF: FormatWithRuffRequest,
}
chosen_formatter_request_class = formatter_to_request_class.get(
    update_build_files_subsystem.formatter
)
if not chosen_formatter_request_class:
    raise ValueError(f"Unrecognized formatter: {update_build_files_subsystem.formatter}")

for request in union_membership[RewrittenBuildFileRequest]:
    if update_build_files_subsystem.fmt and request == chosen_formatter_request_class:
        rewrite_request_classes.append(request)

    if update_build_files_subsystem.fix_safe_deprecations and issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)

    # If there are other types of requests that aren't the standard formatter
    # backends or deprecation fixers, add them here.
    if request not in formatter_to_request_class.values() and not issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)

Old logic:

for request in union_membership[RewrittenBuildFileRequest]:
    if issubclass(request, (FormatWithBlackRequest, FormatWithYapfRequest)):
        is_chosen_formatter = issubclass(request, FormatWithBlackRequest) ^ (
            update_build_files_subsystem.formatter == Formatter.YAPF
        )

        if update_build_files_subsystem.fmt and is_chosen_formatter:
            rewrite_request_classes.append(request)
        else:
            continue
        
    if update_build_files_subsystem.fix_safe_deprecations or not issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)

The else: continue in the old logic was load-bearing, because it would skip over the second top-level if statement and move to the next loop iteration in cases where the request class was one of the regular formatters (FormatWithBlackRequest or FormatWithYapfRequest).

The or not issubclass(request, DeprecationFixerRequest) was intended to catch formatters that live outside of the list of formatter backends and deprecation fixers - the last if statement in the new logic is intended to cover this case as well.

@krishnan-chandra krishnan-chandra added category:bugfix Bug fixes for released features backend: Python Python backend-related issues labels Feb 19, 2024
@huonw huonw added this to the 2.20.x milestone Feb 19, 2024
@krishnan-chandra krishnan-chandra marked this pull request as ready for review February 20, 2024 00:37
Copy link
Contributor

@huonw huonw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

rewrite_request_classes.append(request)

if update_build_files_subsystem.fix_safe_deprecations or not issubclass(
if update_build_files_subsystem.fix_safe_deprecations and issubclass(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is subtle, and I think it was due to my suggestion from before. Thank you for debugging it, and sorry for the incorrect idea.

What do you think about flipping the ifs so we can get an if/else chain based on the request class and so they become "obviously" mutually exclusive?

if request == chosen_formatter_request_class:
    if update_build_files_subsystem.fmt:
        rewrite_request_classes.append(request)
else if issubclass(request, DeprecationFixerRequest):
    if update_build_files_subsystem.fix_safe_deprecations:
        rewrite_request_classes.append(request)
else:
    rewrite_request_classes.append(request)

I guess the next step in DRYing it might be something like:

if request == chosen_formatter_request_class:
    request_enabled = update_build_files_subsystem.fmt
else if issubclass(request, DeprecationFixerRequest):
    request_enabled = update_build_files_subsystem.fix_safe_deprecations
else:
    request_enabled = True

if request_enabled:
    rewrite_request_classes.append(request)

But I dno if that's any better (and, as we've learned, don't trust my ideas without double checking them!).

Copy link
Contributor Author

@krishnan-chandra krishnan-chandra Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both suggestions have a bug in them:

if request == chosen_formatter_request_class:
    if update_build_files_subsystem.fmt:
        rewrite_request_classes.append(request)
else if issubclass(request, DeprecationFixerRequest):
    if update_build_files_subsystem.fix_safe_deprecations:
        rewrite_request_classes.append(request)
else:
    rewrite_request_classes.append(request)

If Black is the chosen formatter and FormatWithRuffRequest is the request being checked here, it would fall through to the final else clause and get added incorrectly.

Copy link
Contributor Author

@krishnan-chandra krishnan-chandra Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't say that the version of the logic added in this PR is the best version, but it's the one that made the most sense in my head and seemed simpler to debug later if needed 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Black is the chosen formatter and FormatWithRuffRequest is the request being checked here, it would fall through to the final else clause and get added incorrectly.

Oh! You're right. Too hard for me, it seems 🤯

@huonw huonw merged commit 110ad38 into pantsbuild:main Feb 20, 2024
24 checks passed
WorkerPants pushed a commit that referenced this pull request Feb 20, 2024
Closes #20576.

Although this is a relatively small change, I think it's good to
understand the change in context so I've included the new and old logic
below for comparison.

New logic:

```py
formatter_to_request_class: dict[Formatter, type[RewrittenBuildFileRequest]] = {
    Formatter.BLACK: FormatWithBlackRequest,
    Formatter.YAPF: FormatWithYapfRequest,
    Formatter.RUFF: FormatWithRuffRequest,
}
chosen_formatter_request_class = formatter_to_request_class.get(
    update_build_files_subsystem.formatter
)
if not chosen_formatter_request_class:
    raise ValueError(f"Unrecognized formatter: {update_build_files_subsystem.formatter}")

for request in union_membership[RewrittenBuildFileRequest]:
    if update_build_files_subsystem.fmt and request == chosen_formatter_request_class:
        rewrite_request_classes.append(request)

    if update_build_files_subsystem.fix_safe_deprecations and issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)

    # If there are other types of requests that aren't the standard formatter
    # backends or deprecation fixers, add them here.
    if request not in formatter_to_request_class.values() and not issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)
```

Old logic:

```py
for request in union_membership[RewrittenBuildFileRequest]:
    if issubclass(request, (FormatWithBlackRequest, FormatWithYapfRequest)):
        is_chosen_formatter = issubclass(request, FormatWithBlackRequest) ^ (
            update_build_files_subsystem.formatter == Formatter.YAPF
        )

        if update_build_files_subsystem.fmt and is_chosen_formatter:
            rewrite_request_classes.append(request)
        else:
            continue
        
    if update_build_files_subsystem.fix_safe_deprecations or not issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)
```

The `else: continue` in the old logic was load-bearing, because it would
skip over the second top-level `if` statement and move to the next loop
iteration in cases where the request class was one of the regular
formatters (`FormatWithBlackRequest` or `FormatWithYapfRequest`).

The `or not issubclass(request, DeprecationFixerRequest)` was intended
to catch formatters that live outside of the list of formatter backends
and deprecation fixers - the last `if` statement in the new logic is
intended to cover this case as well.
@WorkerPants
Copy link
Member

I tried to automatically cherry-pick this change back to each relevant milestone, so that it is available in those older releases of Pants.

✔️ 2.20.x

Successfully opened #20582.


Thanks again for your contributions!

🤖 Beep Boop here's my run link

@krishnan-chandra krishnan-chandra deleted the fix-update-build-files-formatter-selection branch February 20, 2024 01:26
huonw pushed a commit that referenced this pull request Feb 20, 2024
…20582)

Closes #20576.

Although this is a relatively small change, I think it's good to
understand the change in context so I've included the new and old logic
below for comparison.

New logic:

```py
formatter_to_request_class: dict[Formatter, type[RewrittenBuildFileRequest]] = {
    Formatter.BLACK: FormatWithBlackRequest,
    Formatter.YAPF: FormatWithYapfRequest,
    Formatter.RUFF: FormatWithRuffRequest,
}
chosen_formatter_request_class = formatter_to_request_class.get(
    update_build_files_subsystem.formatter
)
if not chosen_formatter_request_class:
    raise ValueError(f"Unrecognized formatter: {update_build_files_subsystem.formatter}")

for request in union_membership[RewrittenBuildFileRequest]:
    if update_build_files_subsystem.fmt and request == chosen_formatter_request_class:
        rewrite_request_classes.append(request)

    if update_build_files_subsystem.fix_safe_deprecations and issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)

    # If there are other types of requests that aren't the standard formatter
    # backends or deprecation fixers, add them here.
    if request not in formatter_to_request_class.values() and not issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)
```

Old logic:

```py
for request in union_membership[RewrittenBuildFileRequest]:
    if issubclass(request, (FormatWithBlackRequest, FormatWithYapfRequest)):
        is_chosen_formatter = issubclass(request, FormatWithBlackRequest) ^ (
            update_build_files_subsystem.formatter == Formatter.YAPF
        )

        if update_build_files_subsystem.fmt and is_chosen_formatter:
            rewrite_request_classes.append(request)
        else:
            continue
        
    if update_build_files_subsystem.fix_safe_deprecations or not issubclass(
        request, DeprecationFixerRequest
    ):
        rewrite_request_classes.append(request)
```

The `else: continue` in the old logic was load-bearing, because it would
skip over the second top-level `if` statement and move to the next loop
iteration in cases where the request class was one of the regular
formatters (`FormatWithBlackRequest` or `FormatWithYapfRequest`).

The `or not issubclass(request, DeprecationFixerRequest)` was intended
to catch formatters that live outside of the list of formatter backends
and deprecation fixers - the last `if` statement in the new logic is
intended to cover this case as well.

Co-authored-by: Krishnan Chandra <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend: Python Python backend-related issues category:bugfix Bug fixes for released features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2.20 runs ruff and yapf on BUILD files without opting in to them
3 participants