11
11
12
12
import ast
13
13
import tokenize
14
- < << << << HEAD
15
- from typing import Any , Dict , Iterable , List , NamedTuple , Optional , Set , Tuple , Union
16
- == == == =
17
14
from typing import (
18
15
Any ,
19
16
Dict ,
26
23
Union ,
27
24
cast ,
28
25
)
29
- > >> >> >> trio112_single_statement_nursery
30
26
31
27
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
32
28
__version__ = "22.8.4"
59
55
"`trio.[fail/move_on]_[after/at]` instead"
60
56
),
61
57
"TRIO110" : "`while <condition>: await trio.sleep()` should be replaced by a `trio.Event`." ,
62
- << << << < HEAD
63
- "TRIO302" : "variable {2}, from context manager on line {0}, passed to {3} from nursery opened on {1}, might get closed while in use" ,
64
- == == == =
58
+ "TRIO111" : (
59
+ "variable {2}, from context manager on line {0}, "
60
+ "passed to {3} from nursery opened on {1}, might get closed while in use"
61
+ ),
65
62
"TRIO112" : "Redundant nursery {}, consider replacing with a regular function call" ,
66
- >> >> >> > trio112_single_statement_nursery
67
63
}
68
64
69
65
@@ -182,7 +178,6 @@ def get_state(self, *attrs: str, copy: bool = False) -> Dict[str, Any]:
182
178
value = value .copy ()
183
179
res [attr ] = value
184
180
return res
185
- # return {attr: getattr(self, attr) for attr in attrs if attr != "_problems"}
186
181
187
182
def set_state (self , attrs : Dict [str , Any ], copy : bool = False ):
188
183
for attr , value in attrs .items ():
@@ -195,21 +190,6 @@ def walk(self, *body: ast.AST) -> Iterable[ast.AST]:
195
190
yield from ast .walk (b )
196
191
197
192
198
- < << << << HEAD
199
- def get_trio_scope (node : ast .AST , * names : str ) -> Optional [TrioScope ]:
200
- if (
201
- isinstance (node , ast .Call )
202
- and isinstance (node .func , ast .Attribute )
203
- and isinstance (node .func .value , ast .Name )
204
- and node .func .value .id == "trio"
205
- and (node .func .attr in names or not names )
206
- ):
207
- return TrioScope (node , node .func .attr )
208
- return None
209
-
210
-
211
- == == == =
212
- >> >> >> > trio112_single_statement_nursery
213
193
def has_decorator (decorator_list : List [ast .expr ], * names : str ):
214
194
for dec in decorator_list :
215
195
if (isinstance (dec , ast .Name ) and dec .id in names ) or (
@@ -219,57 +199,68 @@ def has_decorator(decorator_list: List[ast.expr], *names: str):
219
199
return False
220
200
221
201
222
- # handles 100, 101, 106, 109, 110
202
+ # handles 100, 101, 106, 109, 110, 111, 112
223
203
class VisitorMiscChecks (Flake8TrioVisitor ):
204
+ class NurseryCall (NamedTuple ):
205
+ stack_index : int
206
+ name : str
207
+
208
+ class TrioContextManager (NamedTuple ):
209
+ lineno : int
210
+ name : str
211
+ is_nursery : bool
212
+
224
213
def __init__ (self ):
225
214
super ().__init__ ()
226
215
227
216
# 101
228
217
self ._yield_is_error = False
229
218
self ._safe_decorator = False
230
219
231
- # 302
232
- self ._context_manager_stack : List [Tuple [ast .expr , str , bool ]] = []
233
- self ._nursery_call_index : Optional [int ] = None
234
- self ._nursery_call_name : Optional [str ] = None
220
+ # 111
221
+ self ._context_managers : List [VisitorMiscChecks .TrioContextManager ] = []
222
+ self ._nursery_call : Optional [VisitorMiscChecks .NurseryCall ] = None
235
223
236
- self .defaults = self .get_state ()
224
+ self .defaults = self .get_state (copy = True )
237
225
238
- # ---- 100, 101, 302 ----
226
+ # ---- 100, 101, 111, 112 ----
239
227
def visit_With (self , node : Union [ast .With , ast .AsyncWith ]):
240
228
self .check_for_trio100 (node )
241
229
self .check_for_trio112 (node )
242
230
243
- outer = self .get_state ("_yield_is_error" , "_context_manager_stack " , copy = True )
231
+ outer = self .get_state ("_yield_is_error" , "_context_managers " , copy = True )
244
232
245
- # Check for a `with trio.<scope_creater>`
246
233
for item in node .items :
247
234
# 101
248
- if not self ._safe_decorator and not self ._yield_is_error :
249
- if (
250
- < << << << HEAD
251
- get_trio_scope (
252
- item .context_expr , "open_nursery" , * cancel_scope_names
253
- )
254
- == == == =
255
- get_matching_call (item , "open_nursery" , * cancel_scope_names )
256
- >> >> >> > trio112_single_statement_nursery
257
- is not None
258
- ):
259
- self ._yield_is_error = True
260
- # 302
261
- if isinstance (item .optional_vars , ast .Name ) and isinstance (
262
- item .context_expr , ast .Call
235
+ # if there's no safe decorator,
236
+ # and it's not yet been determined that yield is error
237
+ # and this withitem opens a cancelscope:
238
+ # then yielding is unsafe
239
+ if (
240
+ not self ._safe_decorator
241
+ and not self ._yield_is_error
242
+ and get_matching_call (
243
+ item .context_expr , "open_nursery" , * cancel_scope_names
244
+ )
245
+ is not None
263
246
):
264
- is_nursery = (
265
- get_trio_scope (item .context_expr , "open_nursery" ) is not None
247
+ self ._yield_is_error = True
248
+
249
+ # 111
250
+ # if a withitem is saved in a variable,
251
+ # push its line, variable, and whether it's a trio nursery
252
+ # to the _context_managers stack,
253
+ if isinstance (item .optional_vars , ast .Name ):
254
+ self ._context_managers .append (
255
+ self .TrioContextManager (
256
+ item .context_expr .lineno ,
257
+ item .optional_vars .id ,
258
+ get_matching_call (item .context_expr , "open_nursery" )
259
+ is not None ,
260
+ )
266
261
)
267
- poop = (item .context_expr .func , item .optional_vars .id , is_nursery )
268
- self ._context_manager_stack .append (poop )
269
262
270
263
self .generic_visit (node )
271
-
272
- # reset yield_is_error
273
264
self .set_state (outer )
274
265
275
266
visit_AsyncWith = visit_With
@@ -318,8 +309,11 @@ def visit_Yield(self, node: ast.Yield):
318
309
319
310
# ---- 109 ----
320
311
def check_for_trio109 (self , node : ast .AsyncFunctionDef ):
312
+ # pending configuration or a more sophisticated check, ignore
313
+ # all functions with a decorator
321
314
if node .decorator_list :
322
315
return
316
+
323
317
args = node .args
324
318
for arg in (* args .posonlyargs , * args .args , * args .kwonlyargs ):
325
319
if arg .arg == "timeout" :
@@ -351,41 +345,53 @@ def check_for_trio110(self, node: ast.While):
351
345
):
352
346
self .error ("TRIO110" , node )
353
347
354
- < << << << HEAD
348
+ # ---- 111 ----
349
+ # if it's a <X>.start[_soon] call
350
+ # and <X> is a nursery listed in self._context_managers:
351
+ # Save <X>'s index in self._context_managers to guard against cm's higher in the
352
+ # stack being passed as parameters to it. (and save <X> for the error message)
355
353
def visit_Call (self , node : ast .Call ):
356
- outer = self .get_state ("_nursery_call_index" , "_nursery_call_name " )
354
+ outer = self .get_state ("_nursery_call " )
357
355
358
356
if (
359
357
isinstance (node .func , ast .Attribute )
360
358
and isinstance (node .func .value , ast .Name )
361
359
and node .func .attr in ("start" , "start_soon" )
362
360
):
363
- self ._nursery_call_index = None
364
- for i , (_ , cm_name , is_nursery ) in enumerate (self ._context_manager_stack ):
365
- if node .func .value .id == cm_name :
366
- if is_nursery :
367
- self ._nursery_call_index = i
368
- self ._nursery_call_name = node .func .attr
361
+ self ._nursery_call = None
362
+ for i , cm in enumerate (self ._context_managers ):
363
+ if node .func .value .id == cm .name :
364
+ # don't break upon finding a nursery in case there's multiple cm's
365
+ # on the stack with the same name
366
+ if cm .is_nursery :
367
+ self ._nursery_call = self .Nurser
2851
yCall (i , node .func .attr )
369
368
else :
370
- self ._nursery_call_index = self . _nursery_call_name = None
369
+ self ._nursery_call = None
371
370
372
371
self .generic_visit (node )
373
372
self .set_state (outer )
374
373
F438
374
+ # If we're inside a <X>.start[_soon] call (where <X> is a nursery),
375
+ # and we're accessing a variable cm that's on the self._context_managers stack,
376
+ # with a higher index than <X>:
377
+ # Raise error since the scope of cm may close before the function passed to the
378
+ # nursery finishes.
375
379
def visit_Name (self , node : ast .Name ):
376
- if self ._nursery_call_index is not None :
377
- for i , (expr , cm_name , _ ) in enumerate (self ._context_manager_stack ):
378
- if cm_name == node .id and i > self ._nursery_call_index :
379
- self .error (
380
- "TRIO302" ,
381
- node ,
382
- expr .lineno ,
383
- self ._context_manager_stack [self ._nursery_call_index ][0 ].lineno ,
384
- node .id ,
385
- self ._nursery_call_name ,
386
- )
387
380
self .generic_visit (node )
388
- == == == =
381
+ if self ._nursery_call is None :
382
+ return
383
+
384
+ for i , cm in enumerate (self ._context_managers ):
385
+ if cm .name == node .id and i > self ._nursery_call .stack_index :
386
+ self .error (
387
+ "TRIO111" ,
388
+ node ,
389
+ cm .lineno ,
390
+ self ._context_managers [self ._nursery_call .stack_index ].lineno ,
391
+ node .id ,
392
+ self ._nursery_call .name ,
393
+ )
394
+
389
395
# if with has a withitem `trio.open_nursery() as <X>`,
390
396
# and the body is only a single expression <X>.start[_soon](),
391
397
# and does not pass <X> as a parameter to the expression
@@ -415,9 +421,9 @@ def check_for_trio112(self, node: Union[ast.With, ast.AsyncWith]):
415
421
)
416
422
):
417
423
self .error ("TRIO112" , item .context_expr , var_name )
418
- >> >> >> > trio112_single_statement_nursery
419
424
420
425
426
+ # used in 102, 103 and 104
421
427
def critical_except (node : ast .ExceptHandler ) -> Optional [Statement ]:
422
428
def has_exception (node : Optional [ast .expr ]) -> str :
423
429
if isinstance (node , ast .Name ) and node .id == "BaseException" :
0 commit comments