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

ASYNC912: timeout/cancelscope with only conditional checkpoints #242

Merged
merged 6 commits into from
May 13, 2024

Conversation

jakkdl
Copy link
Member

@jakkdl jakkdl commented May 3, 2024

oh, turns out #183 was already planning on using ASYNC912.

It looks like I broke some stuff, but I gotta go for the day so I thought I'd push in case you have time to review tests/eval_files/async912.py and have any opinions on the behavior in some of the edge cases in there.

tests/eval_files/async912.py Outdated Show resolved Hide resolved
tests/eval_files/async912.py Show resolved Hide resolved
tests/eval_files/async912.py Outdated Show resolved Hide resolved
@jakkdl
Copy link
Member Author

jakkdl commented May 4, 2024

Got the major shit working, but needs some cleanup, comments, documentation and a self-review pass. So no need to review yet.

Comment on lines -101 to +105
elif strip_error_subidentifier(error_code) not in self.options.enabled_codes:
elif (
(ec_no_sub := strip_error_subidentifier(error_code))
not in self.options.enabled_codes
and ec_no_sub not in self.options.autofix_codes
):
Copy link
Member Author

Choose a reason for hiding this comment

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

it's not great that there's duplication of logic between the visitors (it confused me for a bit while developing). I originally expected all visitors to be rewritten to use libcst, but given that's not going to happen anytime soon (or at all), I should probably refactor these two and put common code in a parent class.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense to me 👍

Comment on lines +276 to +279
# ASYNC100
self.has_checkpoint_stack: list[bool] = []
self.node_dict: dict[cst.With, list[AttributeCall]] = {}

Copy link
Member Author

Choose a reason for hiding this comment

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

the primary ASYNC100 logic is simply copy-pasted from visitor100.py

Comment on lines 280 to 288
def should_autofix(self, node: cst.CSTNode, code: str | None = None) -> bool:
if code is None:
code = "ASYNC911" if self.has_yield else "ASYNC910"

return (
not self.noautofix
and super().should_autofix(
node, "ASYNC911" if self.has_yield else "ASYNC910"
)
and super().should_autofix(node, code)
and self.library != ("asyncio",)
)
Copy link
Member Author

@jakkdl jakkdl May 5, 2024

Choose a reason for hiding this comment

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

This change was needed in a previous implementation, but because async100 does work on asyncio (now) and does not care about self.noautofix (which I still haven't figured out why it was introduced), it now doesn't use this method at all. But good to respect an explicitly defined code anyway, which it didn't before.

flake8_async/visitors/visitor91x.py Show resolved Hide resolved
tests/autofix_files/async100.py Show resolved Hide resolved
@jakkdl jakkdl marked this pull request as ready for review May 8, 2024 09:43
@jakkdl jakkdl changed the title WIP: async912 ASYNC912: timeout/cancelscope with only conditional checkpoints May 8, 2024
Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

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

Looks good!

Happy to merge as-is and follow up on comments later if you want.

Comment on lines -101 to +105
elif strip_error_subidentifier(error_code) not in self.options.enabled_codes:
elif (
(ec_no_sub := strip_error_subidentifier(error_code))
not in self.options.enabled_codes
and ec_no_sub not in self.options.autofix_codes
):
Copy link
Member

Choose a reason for hiding this comment

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

Makes sense to me 👍

Comment on lines 355 to 359
if (
isinstance(library_node, cst.Name)
and library_str == library_node.value
):
break
Copy link
Member

Choose a reason for hiding this comment

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

These checks suggest to me that using a libcst matcher might be more efficient.

Could we have a helper function which parses a string ("name", or "x.y", or "x.y.z", or...) into the nodes, and then walks the nodes to return a corresponding matcher?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, though sounds complicated to engineer. I'll give it a try and see if I get anywhere easily, otherwise throw in a TODO and maybe deal with it at a later point.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ended up being straightforward to write, but got sidetracked by Instagram/LibCST#1143

Comment on lines 62 to 63
# We don't know which cancelscope will trigger first, so to avoid false
# positives on tricky-but-valid cases we don't raise any error for the outer one.
Copy link
Member

Choose a reason for hiding this comment

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

nit, b/c clear in this case: I prefer "false alarm" vs "missed alarm", which is immediately descriptive.

By comparison 'false positive' vs 'false negative' is easy to confuse (is detecting a problem positive?), although still better than the abuse 'Type {1,2,I,II} error' terminology which gives absolutely no hints to the listener.

Copy link
Member Author

Choose a reason for hiding this comment

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

Haha, I've seen you point this out about a dozen times and keep misremembering/forgetting which way to do it :D

Fixing

@jakkdl
Copy link
Member Author

jakkdl commented May 10, 2024

Added a couple corner cases, I don't think wrapping calls is terribly common - but if it is feel free to open an issue and I'll make it search for the matching calls differently.

    # wrapped calls do not raise errors
    with customWrapper(trio.fail_at(10)):
        ...
    with (res := trio.fail_at(10)):
        ...
    # but saving with `as` does
    with trio.fail_at(10) as res:  # ASYNC912: 9
        if bar():
            await trio.lowlevel.checkpoint()

@jakkdl jakkdl merged commit d2e1afb into python-trio:main May 13, 2024
10 checks passed
@jakkdl jakkdl deleted the checkpoints_in_suppressed_exception branch May 13, 2024 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants