-
-
Notifications
You must be signed in to change notification settings - Fork 2
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
7: async iterables must checkpoint after yields #17
Conversation
bc1351c
to
7b46176
Compare
I'm confused by the lack of a requirement of a checkpoint before the first yield, isn't the point to enforce that a call to an async function will always checkpoint? But a function async def foo():
while True:
yield 1
await trio.sleep(1) isn't a checkpoint the first time it's called. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally seems good to me!
Also fixing a bunch of stuff for 107/108 .. and some other rando stuff too. Messy.
If you can split stuff out in separate PRs that don't seem too prone to merge conflicts, that would make review a bit easier. Or if there are conflicts but we expect to have a day to rebase in between, I guess; that's more of a judgment call.
(nothing jumps out as "should be a second PR" from this one, fwiw, this is a general principle)
And also frustrating to shift a hundred error messages if adding a line at the top of the file - sliiiightly tempted to generate the tests from comments in the files exclusively, but that is likely overkill.
No kill like overkill! I'd do it 😁
I'm confused by the lack of a requirement of a checkpoint before the first yield, isn't the point to enforce that a call to an async function will always checkpoint?
I think you're right, and that example should trigger a lint warning. Quoting the Trio docs:
If you call an async function provided by Trio (await ), and it doesn’t raise an exception, then it always acts as a checkpoint. (If it does raise an exception, it might act as a checkpoint or might not.)
This includes async iterators: If you writeasync for ... in <a trio object>
, then there will be at least one checkpoint before each iteration of the loop and one checkpoint after the last iteration.
# In each example, *all* of the `await`s are required (though async-for or async-with could substitute)
async def minimal_ok():
await ...
yield
await ...
async def multiple_ok():
await ...
yield
await ...
yield
await ...
async def loop_standard_ok():
for _ in range(5):
await ...
yield
await ...
async def loop_reverse_ok():
await ...
for _ in range(5):
yield
await ...
async def same_line_ok():
yield await ...
await ...
async def same_line_yield_from_ok():
yield from (x async for x in ...) # yield from is *only* OK when it's an async iterable
await ...
I'm not proposing to check for exactly one checkpoint after the last iteration, pretty sure that's not actually the meaning (but see python-trio/trio#2388).
Ooh nice, that means I can entirely remove 109 and merge it with 107 - such that async iterables simply are a special case of 107. That should simplify this beast down a little bit~ |
bf98409
to
6cde589
Compare
I made a complete mess in the git history, and almost lost several hours of work today trying to clean that up. But recovered everything and everything is now squashed up into a single commit rebased onto master. The 107 error message might want to be more descriptive, or split back into a 109 for iterables, to be more verbose about needing checkpoints before and after yields. But the large rewrite merging the logic was well worth it regardless, caught some silly stuff with it. |
Ooh and mayhaps even good to give a line indicator to (one of) the offending yields. E.g. |
Added details to TRIO108, if you like it I'll give the same treatment to 107. |
Woke up this morning pretty unwell, unfortunately, so I probably won't review this before next week. Feel free to add new PRs, jump over to pytest-exceptiongroup, or take some time off as suits you 🙂 |
…ces of Generator with Iterable
Aww, hoping for a speedy recovery ❤️ After some sleep I got a good solution for my ugly loop, and thought a bit more about the error codes. I think it largely comes down to if you want more or fewer error codes - I could in theory cut it all down to a single error code, changing TRIO107 from so there's a single error code
Or create up to five error codes:
A reasonable compromise is perhaps to merge 1-2, and 3-5, such that there's one error code for async functions and one for async iterables - but this will depend on your workflow and stuff. |
Another thing I thought about, when there's multiple possible un-checkpointed paths before a yield, I could make it point out all problems (either through multiple problems on the same statement, or with a message that extends to arbitrary size.
Currently it's semi-arbitrary which one it will complain about, usually the most recent one. Though would need to avoid stuff like
Probably something like "if complaining about multiple statements, only point out statements no other problem has complained about" |
Since I've touched on various smaller things in this PR (get/set_state being the most prominent one) there will be merge conflicts & some code needs to be changed for consistency - so probably don't try to merge this one at the same time as any one of the others without giving me an opportunity to rebase/merge inbetween. |
Re multiple possible problematic yields, it might also be nice to try and inform about
silly in this case, but with more complicated stuff (continue/break/etc) and potential bugs in the check or stuff that's intentionally skipped due to complexity, it might be good to get feedback on stuff that a user might think is guaranteed. And reduce the number of instances of a user raising an issue "why doesn't it think this massive function is checkpointed?? It's clearly guaranteed, duhh". Probably a separate PR, would require modifying a couple different things. |
I think that having different codes for async functions vs iterables makes sense, no real opinion on whether that means we should have two or five though. Which I guess suggests two for simplicity, albeit with custom/helpful messages?
I think having multiple warnings on the same statement makes sense, and if each mention the "since xxx" location that should help users understand which code paths are missing checkpoints. Only pointing to the most recent
I think we can probably get away without this - let's leave it out for now and we can revisit if anyone actually does complain. |
929e5f3
to
afc1ee1
Compare
Actually, slightly different request: this continues to be a huge PR (obviously for a nontrivial feature!). Once you've merge in main, let's focus on getting the minimum viable version of this merged; I'm happy to wait for a follow-up before releasing if you think that would leave it unfinished. I just can't meaningfully review a 1.5k-line diff touching several different things 😅 |
Sorry yeah I'll get it in working shape and split off into separate pull requests. Requires so much discipline to work this way! |
Down to 1000 lines added! 🎉 😂 The main stuff is the +210/-58 in flake8_trio.py - tried commenting up stuff so it should be possible to follow the logic (and ofc caught a minor bug in the process). Don't think I can meaningfully split off more since 107&108 are so intertwined now :) I think/hope that transitioning to multiple errors per line is gonna be quick, I'd already started on this branch originally (:flushed:). But branched that off from this one~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Big diff, but all the prep work has paid off 🙂
I continue doing stuff in a bit of an overkill way, but handling continue/break seems kind of important on this one. I gotta rush to a board game evening, so there's some cleanup and missing tests left, and probably needs a bunch more comments - but you can look at overall approach and look at tests.
Also fixing a bunch of stuff for 107/108 .. and some other rando stuff too. Messy.
Also, I should tweak code redundancy and stuff in the tests - started getting seriously irritating to have to care about all other codes so wrote a quick&dirty solution for filtering.
Not loving splitting up the tests, but the diff printer wasn't smart enough to handle small diffs in large lists - might change that / write my own diff printer. And also frustrating to shift a hundred error messages if adding a line at the top of the file (although thank god for vim's Ctrl-A & Ctrl-X) - sliiiightly tempted to generate the tests from comments in the files exclusively, but that is likely overkill.