]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """ |
2 | Main module. | |
3 | ||
4 | Implement the central Checker class. | |
5 | Also, it models the Bindings and Scopes. | |
6 | """ | |
7 | import __future__ | |
8 | import builtins | |
9 | import ast | |
10 | import collections | |
11 | import contextlib | |
12 | import doctest | |
13 | import functools | |
14 | import os | |
15 | import re | |
16 | import string | |
17 | import sys | |
18 | import warnings | |
19 | ||
20 | from pyflakes import messages | |
21 | ||
22 | PYPY = hasattr(sys, 'pypy_version_info') | |
23 | ||
24 | builtin_vars = dir(builtins) | |
25 | ||
26 | parse_format_string = string.Formatter().parse | |
27 | ||
28 | ||
29 | def getAlternatives(n): | |
30 | if isinstance(n, ast.If): | |
31 | return [n.body] | |
32 | elif isinstance(n, ast.Try): | |
33 | return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] | |
34 | elif sys.version_info >= (3, 10) and isinstance(n, ast.Match): | |
35 | return [mc.body for mc in n.cases] | |
36 | ||
37 | ||
38 | FOR_TYPES = (ast.For, ast.AsyncFor) | |
39 | ||
40 | ||
41 | def _is_singleton(node): # type: (ast.AST) -> bool | |
42 | return ( | |
43 | isinstance(node, ast.Constant) and | |
44 | isinstance(node.value, (bool, type(Ellipsis), type(None))) | |
45 | ) | |
46 | ||
47 | ||
48 | def _is_tuple_constant(node): # type: (ast.AST) -> bool | |
49 | return ( | |
50 | isinstance(node, ast.Tuple) and | |
51 | all(_is_constant(elt) for elt in node.elts) | |
52 | ) | |
53 | ||
54 | ||
55 | def _is_constant(node): | |
56 | return isinstance(node, ast.Constant) or _is_tuple_constant(node) | |
57 | ||
58 | ||
59 | def _is_const_non_singleton(node): # type: (ast.AST) -> bool | |
60 | return _is_constant(node) and not _is_singleton(node) | |
61 | ||
62 | ||
63 | def _is_name_or_attr(node, name): # type: (ast.AST, str) -> bool | |
64 | return ( | |
65 | (isinstance(node, ast.Name) and node.id == name) or | |
66 | (isinstance(node, ast.Attribute) and node.attr == name) | |
67 | ) | |
68 | ||
69 | ||
70 | MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') | |
71 | CONVERSION_FLAG_RE = re.compile('[#0+ -]*') | |
72 | WIDTH_RE = re.compile(r'(?:\*|\d*)') | |
73 | PRECISION_RE = re.compile(r'(?:\.(?:\*|\d*))?') | |
74 | LENGTH_RE = re.compile('[hlL]?') | |
75 | # https://docs.python.org/3/library/stdtypes.html#old-string-formatting | |
76 | VALID_CONVERSIONS = frozenset('diouxXeEfFgGcrsa%') | |
77 | ||
78 | ||
79 | def _must_match(regex, string, pos): | |
80 | match = regex.match(string, pos) | |
81 | assert match is not None | |
82 | return match | |
83 | ||
84 | ||
85 | def parse_percent_format(s): | |
86 | """Parses the string component of a `'...' % ...` format call | |
87 | ||
88 | Copied from https://github.com/asottile/pyupgrade at v1.20.1 | |
89 | """ | |
90 | ||
91 | def _parse_inner(): | |
92 | string_start = 0 | |
93 | string_end = 0 | |
94 | in_fmt = False | |
95 | ||
96 | i = 0 | |
97 | while i < len(s): | |
98 | if not in_fmt: | |
99 | try: | |
100 | i = s.index('%', i) | |
101 | except ValueError: # no more % fields! | |
102 | yield s[string_start:], None | |
103 | return | |
104 | else: | |
105 | string_end = i | |
106 | i += 1 | |
107 | in_fmt = True | |
108 | else: | |
109 | key_match = MAPPING_KEY_RE.match(s, i) | |
110 | if key_match: | |
111 | key = key_match.group(1) | |
112 | i = key_match.end() | |
113 | else: | |
114 | key = None | |
115 | ||
116 | conversion_flag_match = _must_match(CONVERSION_FLAG_RE, s, i) | |
117 | conversion_flag = conversion_flag_match.group() or None | |
118 | i = conversion_flag_match.end() | |
119 | ||
120 | width_match = _must_match(WIDTH_RE, s, i) | |
121 | width = width_match.group() or None | |
122 | i = width_match.end() | |
123 | ||
124 | precision_match = _must_match(PRECISION_RE, s, i) | |
125 | precision = precision_match.group() or None | |
126 | i = precision_match.end() | |
127 | ||
128 | # length modifier is ignored | |
129 | i = _must_match(LENGTH_RE, s, i).end() | |
130 | ||
131 | try: | |
132 | conversion = s[i] | |
133 | except IndexError: | |
134 | raise ValueError('end-of-string while parsing format') | |
135 | i += 1 | |
136 | ||
137 | fmt = (key, conversion_flag, width, precision, conversion) | |
138 | yield s[string_start:string_end], fmt | |
139 | ||
140 | in_fmt = False | |
141 | string_start = i | |
142 | ||
143 | if in_fmt: | |
144 | raise ValueError('end-of-string while parsing format') | |
145 | ||
146 | return tuple(_parse_inner()) | |
147 | ||
148 | ||
149 | class _FieldsOrder(dict): | |
150 | """Fix order of AST node fields.""" | |
151 | ||
152 | def _get_fields(self, node_class): | |
153 | # handle iter before target, and generators before element | |
154 | fields = node_class._fields | |
155 | if 'iter' in fields: | |
156 | key_first = 'iter'.find | |
157 | elif 'generators' in fields: | |
158 | key_first = 'generators'.find | |
159 | else: | |
160 | key_first = 'value'.find | |
161 | return tuple(sorted(fields, key=key_first, reverse=True)) | |
162 | ||
163 | def __missing__(self, node_class): | |
164 | self[node_class] = fields = self._get_fields(node_class) | |
165 | return fields | |
166 | ||
167 | ||
168 | def counter(items): | |
169 | """ | |
170 | Simplest required implementation of collections.Counter. Required as 2.6 | |
171 | does not have Counter in collections. | |
172 | """ | |
173 | results = {} | |
174 | for item in items: | |
175 | results[item] = results.get(item, 0) + 1 | |
176 | return results | |
177 | ||
178 | ||
179 | def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): | |
180 | """ | |
181 | Yield all direct child nodes of *node*, that is, all fields that | |
182 | are nodes and all items of fields that are lists of nodes. | |
183 | ||
184 | :param node: AST node to be iterated upon | |
185 | :param omit: String or tuple of strings denoting the | |
186 | attributes of the node to be omitted from | |
187 | further parsing | |
188 | :param _fields_order: Order of AST node fields | |
189 | """ | |
190 | for name in _fields_order[node.__class__]: | |
191 | if omit and name in omit: | |
192 | continue | |
193 | field = getattr(node, name, None) | |
194 | if isinstance(field, ast.AST): | |
195 | yield field | |
196 | elif isinstance(field, list): | |
197 | for item in field: | |
198 | if isinstance(item, ast.AST): | |
199 | yield item | |
200 | ||
201 | ||
202 | def convert_to_value(item): | |
203 | if isinstance(item, ast.Constant): | |
204 | return item.value | |
205 | elif isinstance(item, ast.Tuple): | |
206 | return tuple(convert_to_value(i) for i in item.elts) | |
207 | elif isinstance(item, ast.Name): | |
208 | return VariableKey(item=item) | |
209 | else: | |
210 | return UnhandledKeyType() | |
211 | ||
212 | ||
213 | def is_notimplemented_name_node(node): | |
214 | return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented' | |
215 | ||
216 | ||
217 | class Binding: | |
218 | """ | |
219 | Represents the binding of a value to a name. | |
220 | ||
221 | The checker uses this to keep track of which names have been bound and | |
222 | which names have not. See L{Assignment} for a special type of binding that | |
223 | is checked with stricter rules. | |
224 | ||
225 | @ivar used: pair of (L{Scope}, node) indicating the scope and | |
226 | the node that this binding was last used. | |
227 | """ | |
228 | ||
229 | def __init__(self, name, source): | |
230 | self.name = name | |
231 | self.source = source | |
232 | self.used = False | |
233 | ||
234 | def __str__(self): | |
235 | return self.name | |
236 | ||
237 | def __repr__(self): | |
238 | return '<{} object {!r} from line {!r} at 0x{:x}>'.format( | |
239 | self.__class__.__name__, | |
240 | self.name, | |
241 | self.source.lineno, | |
242 | id(self), | |
243 | ) | |
244 | ||
245 | def redefines(self, other): | |
246 | return isinstance(other, Definition) and self.name == other.name | |
247 | ||
248 | ||
249 | class Definition(Binding): | |
250 | """ | |
251 | A binding that defines a function or a class. | |
252 | """ | |
253 | def redefines(self, other): | |
254 | return ( | |
255 | super().redefines(other) or | |
256 | (isinstance(other, Assignment) and self.name == other.name) | |
257 | ) | |
258 | ||
259 | ||
260 | class Builtin(Definition): | |
261 | """A definition created for all Python builtins.""" | |
262 | ||
263 | def __init__(self, name): | |
264 | super().__init__(name, None) | |
265 | ||
266 | def __repr__(self): | |
267 | return '<{} object {!r} at 0x{:x}>'.format( | |
268 | self.__class__.__name__, | |
269 | self.name, | |
270 | id(self) | |
271 | ) | |
272 | ||
273 | ||
274 | class UnhandledKeyType: | |
275 | """ | |
276 | A dictionary key of a type that we cannot or do not check for duplicates. | |
277 | """ | |
278 | ||
279 | ||
280 | class VariableKey: | |
281 | """ | |
282 | A dictionary key which is a variable. | |
283 | ||
284 | @ivar item: The variable AST object. | |
285 | """ | |
286 | def __init__(self, item): | |
287 | self.name = item.id | |
288 | ||
289 | def __eq__(self, compare): | |
290 | return ( | |
291 | compare.__class__ == self.__class__ and | |
292 | compare.name == self.name | |
293 | ) | |
294 | ||
295 | def __hash__(self): | |
296 | return hash(self.name) | |
297 | ||
298 | ||
299 | class Importation(Definition): | |
300 | """ | |
301 | A binding created by an import statement. | |
302 | ||
303 | @ivar fullName: The complete name given to the import statement, | |
304 | possibly including multiple dotted components. | |
305 | @type fullName: C{str} | |
306 | """ | |
307 | ||
308 | def __init__(self, name, source, full_name=None): | |
309 | self.fullName = full_name or name | |
310 | self.redefined = [] | |
311 | super().__init__(name, source) | |
312 | ||
313 | def redefines(self, other): | |
314 | if isinstance(other, SubmoduleImportation): | |
315 | # See note in SubmoduleImportation about RedefinedWhileUnused | |
316 | return self.fullName == other.fullName | |
317 | return isinstance(other, Definition) and self.name == other.name | |
318 | ||
319 | def _has_alias(self): | |
320 | """Return whether importation needs an as clause.""" | |
321 | return not self.fullName.split('.')[-1] == self.name | |
322 | ||
323 | @property | |
324 | def source_statement(self): | |
325 | """Generate a source statement equivalent to the import.""" | |
326 | if self._has_alias(): | |
327 | return f'import {self.fullName} as {self.name}' | |
328 | else: | |
329 | return 'import %s' % self.fullName | |
330 | ||
331 | def __str__(self): | |
332 | """Return import full name with alias.""" | |
333 | if self._has_alias(): | |
334 | return self.fullName + ' as ' + self.name | |
335 | else: | |
336 | return self.fullName | |
337 | ||
338 | ||
339 | class SubmoduleImportation(Importation): | |
340 | """ | |
341 | A binding created by a submodule import statement. | |
342 | ||
343 | A submodule import is a special case where the root module is implicitly | |
344 | imported, without an 'as' clause, and the submodule is also imported. | |
345 | Python does not restrict which attributes of the root module may be used. | |
346 | ||
347 | This class is only used when the submodule import is without an 'as' clause. | |
348 | ||
349 | pyflakes handles this case by registering the root module name in the scope, | |
350 | allowing any attribute of the root module to be accessed. | |
351 | ||
352 | RedefinedWhileUnused is suppressed in `redefines` unless the submodule | |
353 | name is also the same, to avoid false positives. | |
354 | """ | |
355 | ||
356 | def __init__(self, name, source): | |
357 | # A dot should only appear in the name when it is a submodule import | |
358 | assert '.' in name and (not source or isinstance(source, ast.Import)) | |
359 | package_name = name.split('.')[0] | |
360 | super().__init__(package_name, source) | |
361 | self.fullName = name | |
362 | ||
363 | def redefines(self, other): | |
364 | if isinstance(other, Importation): | |
365 | return self.fullName == other.fullName | |
366 | return super().redefines(other) | |
367 | ||
368 | def __str__(self): | |
369 | return self.fullName | |
370 | ||
371 | @property | |
372 | def source_statement(self): | |
373 | return 'import ' + self.fullName | |
374 | ||
375 | ||
376 | class ImportationFrom(Importation): | |
377 | ||
378 | def __init__(self, name, source, module, real_name=None): | |
379 | self.module = module | |
380 | self.real_name = real_name or name | |
381 | ||
382 | if module.endswith('.'): | |
383 | full_name = module + self.real_name | |
384 | else: | |
385 | full_name = module + '.' + self.real_name | |
386 | ||
387 | super().__init__(name, source, full_name) | |
388 | ||
389 | def __str__(self): | |
390 | """Return import full name with alias.""" | |
391 | if self.real_name != self.name: | |
392 | return self.fullName + ' as ' + self.name | |
393 | else: | |
394 | return self.fullName | |
395 | ||
396 | @property | |
397 | def source_statement(self): | |
398 | if self.real_name != self.name: | |
399 | return f'from {self.module} import {self.real_name} as {self.name}' | |
400 | else: | |
401 | return f'from {self.module} import {self.name}' | |
402 | ||
403 | ||
404 | class StarImportation(Importation): | |
405 | """A binding created by a 'from x import *' statement.""" | |
406 | ||
407 | def __init__(self, name, source): | |
408 | super().__init__('*', source) | |
409 | # Each star importation needs a unique name, and | |
410 | # may not be the module name otherwise it will be deemed imported | |
411 | self.name = name + '.*' | |
412 | self.fullName = name | |
413 | ||
414 | @property | |
415 | def source_statement(self): | |
416 | return 'from ' + self.fullName + ' import *' | |
417 | ||
418 | def __str__(self): | |
419 | # When the module ends with a ., avoid the ambiguous '..*' | |
420 | if self.fullName.endswith('.'): | |
421 | return self.source_statement | |
422 | else: | |
423 | return self.name | |
424 | ||
425 | ||
426 | class FutureImportation(ImportationFrom): | |
427 | """ | |
428 | A binding created by a from `__future__` import statement. | |
429 | ||
430 | `__future__` imports are implicitly used. | |
431 | """ | |
432 | ||
433 | def __init__(self, name, source, scope): | |
434 | super().__init__(name, source, '__future__') | |
435 | self.used = (scope, source) | |
436 | ||
437 | ||
438 | class Argument(Binding): | |
439 | """ | |
440 | Represents binding a name as an argument. | |
441 | """ | |
442 | ||
443 | ||
444 | class Assignment(Binding): | |
445 | """ | |
446 | Represents binding a name with an explicit assignment. | |
447 | ||
448 | The checker will raise warnings for any Assignment that isn't used. Also, | |
449 | the checker does not consider assignments in tuple/list unpacking to be | |
450 | Assignments, rather it treats them as simple Bindings. | |
451 | """ | |
452 | ||
453 | ||
454 | class NamedExprAssignment(Assignment): | |
455 | """ | |
456 | Represents binding a name with an assignment expression. | |
457 | """ | |
458 | ||
459 | ||
460 | class Annotation(Binding): | |
461 | """ | |
462 | Represents binding a name to a type without an associated value. | |
463 | ||
464 | As long as this name is not assigned a value in another binding, it is considered | |
465 | undefined for most purposes. One notable exception is using the name as a type | |
466 | annotation. | |
467 | """ | |
468 | ||
469 | def redefines(self, other): | |
470 | """An Annotation doesn't define any name, so it cannot redefine one.""" | |
471 | return False | |
472 | ||
473 | ||
474 | class FunctionDefinition(Definition): | |
475 | pass | |
476 | ||
477 | ||
478 | class ClassDefinition(Definition): | |
479 | pass | |
480 | ||
481 | ||
482 | class ExportBinding(Binding): | |
483 | """ | |
484 | A binding created by an C{__all__} assignment. If the names in the list | |
485 | can be determined statically, they will be treated as names for export and | |
486 | additional checking applied to them. | |
487 | ||
488 | The only recognized C{__all__} assignment via list/tuple concatenation is in the | |
489 | following format: | |
490 | ||
491 | __all__ = ['a'] + ['b'] + ['c'] | |
492 | ||
493 | Names which are imported and not otherwise used but appear in the value of | |
494 | C{__all__} will not have an unused import warning reported for them. | |
495 | """ | |
496 | ||
497 | def __init__(self, name, source, scope): | |
498 | if '__all__' in scope and isinstance(source, ast.AugAssign): | |
499 | self.names = list(scope['__all__'].names) | |
500 | else: | |
501 | self.names = [] | |
502 | ||
503 | def _add_to_names(container): | |
504 | for node in container.elts: | |
505 | if isinstance(node, ast.Constant) and isinstance(node.value, str): | |
506 | self.names.append(node.value) | |
507 | ||
508 | if isinstance(source.value, (ast.List, ast.Tuple)): | |
509 | _add_to_names(source.value) | |
510 | # If concatenating lists or tuples | |
511 | elif isinstance(source.value, ast.BinOp): | |
512 | currentValue = source.value | |
513 | while isinstance(currentValue.right, (ast.List, ast.Tuple)): | |
514 | left = currentValue.left | |
515 | right = currentValue.right | |
516 | _add_to_names(right) | |
517 | # If more lists are being added | |
518 | if isinstance(left, ast.BinOp): | |
519 | currentValue = left | |
520 | # If just two lists are being added | |
521 | elif isinstance(left, (ast.List, ast.Tuple)): | |
522 | _add_to_names(left) | |
523 | # All lists accounted for - done | |
524 | break | |
525 | # If not list concatenation | |
526 | else: | |
527 | break | |
528 | super().__init__(name, source) | |
529 | ||
530 | ||
531 | class Scope(dict): | |
532 | importStarred = False # set to True when import * is found | |
533 | ||
534 | def __repr__(self): | |
535 | scope_cls = self.__class__.__name__ | |
536 | return f'<{scope_cls} at 0x{id(self):x} {dict.__repr__(self)}>' | |
537 | ||
538 | ||
539 | class ClassScope(Scope): | |
540 | pass | |
541 | ||
542 | ||
543 | class FunctionScope(Scope): | |
544 | """ | |
545 | I represent a name scope for a function. | |
546 | ||
547 | @ivar globals: Names declared 'global' in this function. | |
548 | """ | |
549 | usesLocals = False | |
550 | alwaysUsed = {'__tracebackhide__', '__traceback_info__', | |
551 | '__traceback_supplement__'} | |
552 | ||
553 | def __init__(self): | |
554 | super().__init__() | |
555 | # Simplify: manage the special locals as globals | |
556 | self.globals = self.alwaysUsed.copy() | |
557 | self.returnValue = None # First non-empty return | |
558 | ||
559 | def unused_assignments(self): | |
560 | """ | |
561 | Return a generator for the assignments which have not been used. | |
562 | """ | |
563 | for name, binding in self.items(): | |
564 | if (not binding.used and | |
565 | name != '_' and # see issue #202 | |
566 | name not in self.globals and | |
567 | not self.usesLocals and | |
568 | isinstance(binding, Assignment)): | |
569 | yield name, binding | |
570 | ||
571 | def unused_annotations(self): | |
572 | """ | |
573 | Return a generator for the annotations which have not been used. | |
574 | """ | |
575 | for name, binding in self.items(): | |
576 | if not binding.used and isinstance(binding, Annotation): | |
577 | yield name, binding | |
578 | ||
579 | ||
580 | class TypeScope(Scope): | |
581 | pass | |
582 | ||
583 | ||
584 | class GeneratorScope(Scope): | |
585 | pass | |
586 | ||
587 | ||
588 | class ModuleScope(Scope): | |
589 | """Scope for a module.""" | |
590 | _futures_allowed = True | |
591 | _annotations_future_enabled = False | |
592 | ||
593 | ||
594 | class DoctestScope(ModuleScope): | |
595 | """Scope for a doctest.""" | |
596 | ||
597 | ||
598 | class DetectClassScopedMagic: | |
599 | names = dir() | |
600 | ||
601 | ||
602 | # Globally defined names which are not attributes of the builtins module, or | |
603 | # are only present on some platforms. | |
604 | _MAGIC_GLOBALS = ['__file__', '__builtins__', '__annotations__', 'WindowsError'] | |
605 | ||
606 | ||
607 | def getNodeName(node): | |
608 | # Returns node.id, or node.name, or None | |
609 | if hasattr(node, 'id'): # One of the many nodes with an id | |
610 | return node.id | |
611 | if hasattr(node, 'name'): # an ExceptHandler node | |
612 | return node.name | |
613 | if hasattr(node, 'rest'): # a MatchMapping node | |
614 | return node.rest | |
615 | ||
616 | ||
617 | TYPING_MODULES = frozenset(('typing', 'typing_extensions')) | |
618 | ||
619 | ||
620 | def _is_typing_helper(node, is_name_match_fn, scope_stack): | |
621 | """ | |
622 | Internal helper to determine whether or not something is a member of a | |
623 | typing module. This is used as part of working out whether we are within a | |
624 | type annotation context. | |
625 | ||
626 | Note: you probably don't want to use this function directly. Instead see the | |
627 | utils below which wrap it (`_is_typing` and `_is_any_typing_member`). | |
628 | """ | |
629 | ||
630 | def _bare_name_is_attr(name): | |
631 | for scope in reversed(scope_stack): | |
632 | if name in scope: | |
633 | return ( | |
634 | isinstance(scope[name], ImportationFrom) and | |
635 | scope[name].module in TYPING_MODULES and | |
636 | is_name_match_fn(scope[name].real_name) | |
637 | ) | |
638 | ||
639 | return False | |
640 | ||
641 | def _module_scope_is_typing(name): | |
642 | for scope in reversed(scope_stack): | |
643 | if name in scope: | |
644 | return ( | |
645 | isinstance(scope[name], Importation) and | |
646 | scope[name].fullName in TYPING_MODULES | |
647 | ) | |
648 | ||
649 | return False | |
650 | ||
651 | return ( | |
652 | ( | |
653 | isinstance(node, ast.Name) and | |
654 | _bare_name_is_attr(node.id) | |
655 | ) or ( | |
656 | isinstance(node, ast.Attribute) and | |
657 | isinstance(node.value, ast.Name) and | |
658 | _module_scope_is_typing(node.value.id) and | |
659 | is_name_match_fn(node.attr) | |
660 | ) | |
661 | ) | |
662 | ||
663 | ||
664 | def _is_typing(node, typing_attr, scope_stack): | |
665 | """ | |
666 | Determine whether `node` represents the member of a typing module specified | |
667 | by `typing_attr`. | |
668 | ||
669 | This is used as part of working out whether we are within a type annotation | |
670 | context. | |
671 | """ | |
672 | return _is_typing_helper(node, lambda x: x == typing_attr, scope_stack) | |
673 | ||
674 | ||
675 | def _is_any_typing_member(node, scope_stack): | |
676 | """ | |
677 | Determine whether `node` represents any member of a typing module. | |
678 | ||
679 | This is used as part of working out whether we are within a type annotation | |
680 | context. | |
681 | """ | |
682 | return _is_typing_helper(node, lambda x: True, scope_stack) | |
683 | ||
684 | ||
685 | def is_typing_overload(value, scope_stack): | |
686 | return ( | |
687 | isinstance(value.source, (ast.FunctionDef, ast.AsyncFunctionDef)) and | |
688 | any( | |
689 | _is_typing(dec, 'overload', scope_stack) | |
690 | for dec in value.source.decorator_list | |
691 | ) | |
692 | ) | |
693 | ||
694 | ||
695 | class AnnotationState: | |
696 | NONE = 0 | |
697 | STRING = 1 | |
698 | BARE = 2 | |
699 | ||
700 | ||
701 | def in_annotation(func): | |
702 | @functools.wraps(func) | |
703 | def in_annotation_func(self, *args, **kwargs): | |
704 | with self._enter_annotation(): | |
705 | return func(self, *args, **kwargs) | |
706 | return in_annotation_func | |
707 | ||
708 | ||
709 | def in_string_annotation(func): | |
710 | @functools.wraps(func) | |
711 | def in_annotation_func(self, *args, **kwargs): | |
712 | with self._enter_annotation(AnnotationState.STRING): | |
713 | return func(self, *args, **kwargs) | |
714 | return in_annotation_func | |
715 | ||
716 | ||
717 | class Checker: | |
718 | """I check the cleanliness and sanity of Python code.""" | |
719 | ||
720 | _ast_node_scope = { | |
721 | ast.Module: ModuleScope, | |
722 | ast.ClassDef: ClassScope, | |
723 | ast.FunctionDef: FunctionScope, | |
724 | ast.AsyncFunctionDef: FunctionScope, | |
725 | ast.Lambda: FunctionScope, | |
726 | ast.ListComp: GeneratorScope, | |
727 | ast.SetComp: GeneratorScope, | |
728 | ast.GeneratorExp: GeneratorScope, | |
729 | ast.DictComp: GeneratorScope, | |
730 | } | |
731 | ||
732 | nodeDepth = 0 | |
733 | offset = None | |
734 | _in_annotation = AnnotationState.NONE | |
735 | ||
736 | builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) | |
737 | _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') | |
738 | if _customBuiltIns: | |
739 | builtIns.update(_customBuiltIns.split(',')) | |
740 | del _customBuiltIns | |
741 | ||
742 | def __init__(self, tree, filename='(none)', builtins=None, | |
743 | withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): | |
744 | self._nodeHandlers = {} | |
745 | self._deferred = collections.deque() | |
746 | self.deadScopes = [] | |
747 | self.messages = [] | |
748 | self.filename = filename | |
749 | if builtins: | |
750 | self.builtIns = self.builtIns.union(builtins) | |
751 | self.withDoctest = withDoctest | |
752 | self.exceptHandlers = [()] | |
753 | self.root = tree | |
754 | ||
755 | self.scopeStack = [] | |
756 | try: | |
757 | scope_tp = Checker._ast_node_scope[type(tree)] | |
758 | except KeyError: | |
759 | raise RuntimeError('No scope implemented for the node %r' % tree) | |
760 | ||
761 | with self.in_scope(scope_tp): | |
762 | for builtin in self.builtIns: | |
763 | self.addBinding(None, Builtin(builtin)) | |
764 | self.handleChildren(tree) | |
765 | self._run_deferred() | |
766 | ||
767 | self.checkDeadScopes() | |
768 | ||
769 | if file_tokens: | |
770 | warnings.warn( | |
771 | '`file_tokens` will be removed in a future version', | |
772 | stacklevel=2, | |
773 | ) | |
774 | ||
775 | def deferFunction(self, callable): | |
776 | """ | |
777 | Schedule a function handler to be called just before completion. | |
778 | ||
779 | This is used for handling function bodies, which must be deferred | |
780 | because code later in the file might modify the global scope. When | |
781 | `callable` is called, the scope at the time this is called will be | |
782 | restored, however it will contain any new bindings added to it. | |
783 | """ | |
784 | self._deferred.append((callable, self.scopeStack[:], self.offset)) | |
785 | ||
786 | def _run_deferred(self): | |
787 | orig = (self.scopeStack, self.offset) | |
788 | ||
789 | while self._deferred: | |
790 | handler, scope, offset = self._deferred.popleft() | |
791 | self.scopeStack, self.offset = scope, offset | |
792 | handler() | |
793 | ||
794 | self.scopeStack, self.offset = orig | |
795 | ||
796 | def _in_doctest(self): | |
797 | return (len(self.scopeStack) >= 2 and | |
798 | isinstance(self.scopeStack[1], DoctestScope)) | |
799 | ||
800 | @property | |
801 | def futuresAllowed(self): | |
802 | if not all(isinstance(scope, ModuleScope) | |
803 | for scope in self.scopeStack): | |
804 | return False | |
805 | ||
806 | return self.scope._futures_allowed | |
807 | ||
808 | @futuresAllowed.setter | |
809 | def futuresAllowed(self, value): | |
810 | assert value is False | |
811 | if isinstance(self.scope, ModuleScope): | |
812 | self.scope._futures_allowed = False | |
813 | ||
814 | @property | |
815 | def annotationsFutureEnabled(self): | |
816 | scope = self.scopeStack[0] | |
817 | if not isinstance(scope, ModuleScope): | |
818 | return False | |
819 | return scope._annotations_future_enabled | |
820 | ||
821 | @annotationsFutureEnabled.setter | |
822 | def annotationsFutureEnabled(self, value): | |
823 | assert value is True | |
824 | assert isinstance(self.scope, ModuleScope) | |
825 | self.scope._annotations_future_enabled = True | |
826 | ||
827 | @property | |
828 | def scope(self): | |
829 | return self.scopeStack[-1] | |
830 | ||
831 | @contextlib.contextmanager | |
832 | def in_scope(self, cls): | |
833 | self.scopeStack.append(cls()) | |
834 | try: | |
835 | yield | |
836 | finally: | |
837 | self.deadScopes.append(self.scopeStack.pop()) | |
838 | ||
839 | def checkDeadScopes(self): | |
840 | """ | |
841 | Look at scopes which have been fully examined and report names in them | |
842 | which were imported but unused. | |
843 | """ | |
844 | for scope in self.deadScopes: | |
845 | # imports in classes are public members | |
846 | if isinstance(scope, ClassScope): | |
847 | continue | |
848 | ||
849 | if isinstance(scope, FunctionScope): | |
850 | for name, binding in scope.unused_assignments(): | |
851 | self.report(messages.UnusedVariable, binding.source, name) | |
852 | for name, binding in scope.unused_annotations(): | |
853 | self.report(messages.UnusedAnnotation, binding.source, name) | |
854 | ||
855 | all_binding = scope.get('__all__') | |
856 | if all_binding and not isinstance(all_binding, ExportBinding): | |
857 | all_binding = None | |
858 | ||
859 | if all_binding: | |
860 | all_names = set(all_binding.names) | |
861 | undefined = [ | |
862 | name for name in all_binding.names | |
863 | if name not in scope | |
864 | ] | |
865 | else: | |
866 | all_names = undefined = [] | |
867 | ||
868 | if undefined: | |
869 | if not scope.importStarred and \ | |
870 | os.path.basename(self.filename) != '__init__.py': | |
871 | # Look for possible mistakes in the export list | |
872 | for name in undefined: | |
873 | self.report(messages.UndefinedExport, | |
874 | scope['__all__'].source, name) | |
875 | ||
876 | # mark all import '*' as used by the undefined in __all__ | |
877 | if scope.importStarred: | |
878 | from_list = [] | |
879 | for binding in scope.values(): | |
880 | if isinstance(binding, StarImportation): | |
881 | binding.used = all_binding | |
882 | from_list.append(binding.fullName) | |
883 | # report * usage, with a list of possible sources | |
884 | from_list = ', '.join(sorted(from_list)) | |
885 | for name in undefined: | |
886 | self.report(messages.ImportStarUsage, | |
887 | scope['__all__'].source, name, from_list) | |
888 | ||
889 | # Look for imported names that aren't used. | |
890 | for value in scope.values(): | |
891 | if isinstance(value, Importation): | |
892 | used = value.used or value.name in all_names | |
893 | if not used: | |
894 | messg = messages.UnusedImport | |
895 | self.report(messg, value.source, str(value)) | |
896 | for node in value.redefined: | |
897 | if isinstance(self.getParent(node), FOR_TYPES): | |
898 | messg = messages.ImportShadowedByLoopVar | |
899 | elif used: | |
900 | continue | |
901 | else: | |
902 | messg = messages.RedefinedWhileUnused | |
903 | self.report(messg, node, value.name, value.source) | |
904 | ||
905 | def report(self, messageClass, *args, **kwargs): | |
906 | self.messages.append(messageClass(self.filename, *args, **kwargs)) | |
907 | ||
908 | def getParent(self, node): | |
909 | # Lookup the first parent which is not Tuple, List or Starred | |
910 | while True: | |
911 | node = node._pyflakes_parent | |
912 | if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): | |
913 | return node | |
914 | ||
915 | def getCommonAncestor(self, lnode, rnode, stop): | |
916 | if ( | |
917 | stop in (lnode, rnode) or | |
918 | not ( | |
919 | hasattr(lnode, '_pyflakes_parent') and | |
920 | hasattr(rnode, '_pyflakes_parent') | |
921 | ) | |
922 | ): | |
923 | return None | |
924 | if lnode is rnode: | |
925 | return lnode | |
926 | ||
927 | if (lnode._pyflakes_depth > rnode._pyflakes_depth): | |
928 | return self.getCommonAncestor(lnode._pyflakes_parent, rnode, stop) | |
929 | if (lnode._pyflakes_depth < rnode._pyflakes_depth): | |
930 | return self.getCommonAncestor(lnode, rnode._pyflakes_parent, stop) | |
931 | return self.getCommonAncestor( | |
932 | lnode._pyflakes_parent, | |
933 | rnode._pyflakes_parent, | |
934 | stop, | |
935 | ) | |
936 | ||
937 | def descendantOf(self, node, ancestors, stop): | |
938 | for a in ancestors: | |
939 | if self.getCommonAncestor(node, a, stop): | |
940 | return True | |
941 | return False | |
942 | ||
943 | def _getAncestor(self, node, ancestor_type): | |
944 | parent = node | |
945 | while True: | |
946 | if parent is self.root: | |
947 | return None | |
948 | parent = self.getParent(parent) | |
949 | if isinstance(parent, ancestor_type): | |
950 | return parent | |
951 | ||
952 | def getScopeNode(self, node): | |
953 | return self._getAncestor(node, tuple(Checker._ast_node_scope.keys())) | |
954 | ||
955 | def differentForks(self, lnode, rnode): | |
956 | """True, if lnode and rnode are located on different forks of IF/TRY""" | |
957 | ancestor = self.getCommonAncestor(lnode, rnode, self.root) | |
958 | parts = getAlternatives(ancestor) | |
959 | if parts: | |
960 | for items in parts: | |
961 | if self.descendantOf(lnode, items, ancestor) ^ \ | |
962 | self.descendantOf(rnode, items, ancestor): | |
963 | return True | |
964 | return False | |
965 | ||
966 | def addBinding(self, node, value): | |
967 | """ | |
968 | Called when a binding is altered. | |
969 | ||
970 | - `node` is the statement responsible for the change | |
971 | - `value` is the new value, a Binding instance | |
972 | """ | |
973 | # assert value.source in (node, node._pyflakes_parent): | |
974 | for scope in self.scopeStack[::-1]: | |
975 | if value.name in scope: | |
976 | break | |
977 | existing = scope.get(value.name) | |
978 | ||
979 | if (existing and not isinstance(existing, Builtin) and | |
980 | not self.differentForks(node, existing.source)): | |
981 | ||
982 | parent_stmt = self.getParent(value.source) | |
983 | if isinstance(existing, Importation) and isinstance(parent_stmt, FOR_TYPES): | |
984 | self.report(messages.ImportShadowedByLoopVar, | |
985 | node, value.name, existing.source) | |
986 | ||
987 | elif scope is self.scope: | |
988 | if ( | |
989 | (not existing.used and value.redefines(existing)) and | |
990 | (value.name != '_' or isinstance(existing, Importation)) and | |
991 | not is_typing_overload(existing, self.scopeStack) | |
992 | ): | |
993 | self.report(messages.RedefinedWhileUnused, | |
994 | node, value.name, existing.source) | |
995 | ||
996 | elif isinstance(existing, Importation) and value.redefines(existing): | |
997 | existing.redefined.append(node) | |
998 | ||
999 | if value.name in self.scope: | |
1000 | # then assume the rebound name is used as a global or within a loop | |
1001 | value.used = self.scope[value.name].used | |
1002 | ||
1003 | # don't treat annotations as assignments if there is an existing value | |
1004 | # in scope | |
1005 | if value.name not in self.scope or not isinstance(value, Annotation): | |
1006 | cur_scope_pos = -1 | |
1007 | # As per PEP 572, use scope in which outermost generator is defined | |
1008 | while ( | |
1009 | isinstance(value, NamedExprAssignment) and | |
1010 | isinstance(self.scopeStack[cur_scope_pos], GeneratorScope) | |
1011 | ): | |
1012 | cur_scope_pos -= 1 | |
1013 | self.scopeStack[cur_scope_pos][value.name] = value | |
1014 | ||
1015 | def _unknown_handler(self, node): | |
1016 | # this environment variable configures whether to error on unknown | |
1017 | # ast types. | |
1018 | # | |
1019 | # this is silent by default but the error is enabled for the pyflakes | |
1020 | # testsuite. | |
1021 | # | |
1022 | # this allows new syntax to be added to python without *requiring* | |
1023 | # changes from the pyflakes side. but will still produce an error | |
1024 | # in the pyflakes testsuite (so more specific handling can be added if | |
1025 | # needed). | |
1026 | if os.environ.get('PYFLAKES_ERROR_UNKNOWN'): | |
1027 | raise NotImplementedError(f'Unexpected type: {type(node)}') | |
1028 | else: | |
1029 | self.handleChildren(node) | |
1030 | ||
1031 | def getNodeHandler(self, node_class): | |
1032 | try: | |
1033 | return self._nodeHandlers[node_class] | |
1034 | except KeyError: | |
1035 | nodeType = node_class.__name__.upper() | |
1036 | self._nodeHandlers[node_class] = handler = getattr( | |
1037 | self, nodeType, self._unknown_handler, | |
1038 | ) | |
1039 | return handler | |
1040 | ||
1041 | def handleNodeLoad(self, node, parent): | |
1042 | name = getNodeName(node) | |
1043 | if not name: | |
1044 | return | |
1045 | ||
1046 | # only the following can access class scoped variables (since classes | |
1047 | # aren't really a scope) | |
1048 | # - direct accesses (not within a nested scope) | |
1049 | # - generators | |
1050 | # - type annotations (for generics, etc.) | |
1051 | can_access_class_vars = None | |
1052 | importStarred = None | |
1053 | ||
1054 | # try enclosing function scopes and global scope | |
1055 | for scope in self.scopeStack[-1::-1]: | |
1056 | if isinstance(scope, ClassScope): | |
1057 | if name == '__class__': | |
1058 | return | |
1059 | elif can_access_class_vars is False: | |
1060 | # only generators used in a class scope can access the | |
1061 | # names of the class. this is skipped during the first | |
1062 | # iteration | |
1063 | continue | |
1064 | ||
1065 | binding = scope.get(name, None) | |
1066 | if isinstance(binding, Annotation) and not self._in_postponed_annotation: | |
1067 | scope[name].used = (self.scope, node) | |
1068 | continue | |
1069 | ||
1070 | if name == 'print' and isinstance(binding, Builtin): | |
1071 | if (isinstance(parent, ast.BinOp) and | |
1072 | isinstance(parent.op, ast.RShift)): | |
1073 | self.report(messages.InvalidPrintSyntax, node) | |
1074 | ||
1075 | try: | |
1076 | scope[name].used = (self.scope, node) | |
1077 | ||
1078 | # if the name of SubImportation is same as | |
1079 | # alias of other Importation and the alias | |
1080 | # is used, SubImportation also should be marked as used. | |
1081 | n = scope[name] | |
1082 | if isinstance(n, Importation) and n._has_alias(): | |
1083 | try: | |
1084 | scope[n.fullName].used = (self.scope, node) | |
1085 | except KeyError: | |
1086 | pass | |
1087 | except KeyError: | |
1088 | pass | |
1089 | else: | |
1090 | return | |
1091 | ||
1092 | importStarred = importStarred or scope.importStarred | |
1093 | ||
1094 | if can_access_class_vars is not False: | |
1095 | can_access_class_vars = isinstance( | |
1096 | scope, (TypeScope, GeneratorScope), | |
1097 | ) | |
1098 | ||
1099 | if importStarred: | |
1100 | from_list = [] | |
1101 | ||
1102 | for scope in self.scopeStack[-1::-1]: | |
1103 | for binding in scope.values(): | |
1104 | if isinstance(binding, StarImportation): | |
1105 | # mark '*' imports as used for each scope | |
1106 | binding.used = (self.scope, node) | |
1107 | from_list.append(binding.fullName) | |
1108 | ||
1109 | # report * usage, with a list of possible sources | |
1110 | from_list = ', '.join(sorted(from_list)) | |
1111 | self.report(messages.ImportStarUsage, node, name, from_list) | |
1112 | return | |
1113 | ||
1114 | if name == '__path__' and os.path.basename(self.filename) == '__init__.py': | |
1115 | # the special name __path__ is valid only in packages | |
1116 | return | |
1117 | ||
1118 | if name in DetectClassScopedMagic.names and isinstance(self.scope, ClassScope): | |
1119 | return | |
1120 | ||
1121 | # protected with a NameError handler? | |
1122 | if 'NameError' not in self.exceptHandlers[-1]: | |
1123 | self.report(messages.UndefinedName, node, name) | |
1124 | ||
1125 | def handleNodeStore(self, node): | |
1126 | name = getNodeName(node) | |
1127 | if not name: | |
1128 | return | |
1129 | # if the name hasn't already been defined in the current scope | |
1130 | if isinstance(self.scope, FunctionScope) and name not in self.scope: | |
1131 | # for each function or module scope above us | |
1132 | for scope in self.scopeStack[:-1]: | |
1133 | if not isinstance(scope, (FunctionScope, ModuleScope)): | |
1134 | continue | |
1135 | # if the name was defined in that scope, and the name has | |
1136 | # been accessed already in the current scope, and hasn't | |
1137 | # been declared global | |
1138 | used = name in scope and scope[name].used | |
1139 | if used and used[0] is self.scope and name not in self.scope.globals: | |
1140 | # then it's probably a mistake | |
1141 | self.report(messages.UndefinedLocal, | |
1142 | scope[name].used[1], name, scope[name].source) | |
1143 | break | |
1144 | ||
1145 | parent_stmt = self.getParent(node) | |
1146 | if isinstance(parent_stmt, ast.AnnAssign) and parent_stmt.value is None: | |
1147 | binding = Annotation(name, node) | |
1148 | elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( | |
1149 | parent_stmt != node._pyflakes_parent and | |
1150 | not self.isLiteralTupleUnpacking(parent_stmt)): | |
1151 | binding = Binding(name, node) | |
1152 | elif ( | |
1153 | name == '__all__' and | |
1154 | isinstance(self.scope, ModuleScope) and | |
1155 | isinstance( | |
1156 | node._pyflakes_parent, | |
1157 | (ast.Assign, ast.AugAssign, ast.AnnAssign) | |
1158 | ) | |
1159 | ): | |
1160 | binding = ExportBinding(name, node._pyflakes_parent, self.scope) | |
1161 | elif isinstance(parent_stmt, ast.NamedExpr): | |
1162 | binding = NamedExprAssignment(name, node) | |
1163 | else: | |
1164 | binding = Assignment(name, node) | |
1165 | self.addBinding(node, binding) | |
1166 | ||
1167 | def handleNodeDelete(self, node): | |
1168 | ||
1169 | def on_conditional_branch(): | |
1170 | """ | |
1171 | Return `True` if node is part of a conditional body. | |
1172 | """ | |
1173 | current = getattr(node, '_pyflakes_parent', None) | |
1174 | while current: | |
1175 | if isinstance(current, (ast.If, ast.While, ast.IfExp)): | |
1176 | return True | |
1177 | current = getattr(current, '_pyflakes_parent', None) | |
1178 | return False | |
1179 | ||
1180 | name = getNodeName(node) | |
1181 | if not name: | |
1182 | return | |
1183 | ||
1184 | if on_conditional_branch(): | |
1185 | # We cannot predict if this conditional branch is going to | |
1186 | # be executed. | |
1187 | return | |
1188 | ||
1189 | if isinstance(self.scope, FunctionScope) and name in self.scope.globals: | |
1190 | self.scope.globals.remove(name) | |
1191 | else: | |
1192 | try: | |
1193 | del self.scope[name] | |
1194 | except KeyError: | |
1195 | self.report(messages.UndefinedName, node, name) | |
1196 | ||
1197 | @contextlib.contextmanager | |
1198 | def _enter_annotation(self, ann_type=AnnotationState.BARE): | |
1199 | orig, self._in_annotation = self._in_annotation, ann_type | |
1200 | try: | |
1201 | yield | |
1202 | finally: | |
1203 | self._in_annotation = orig | |
1204 | ||
1205 | @property | |
1206 | def _in_postponed_annotation(self): | |
1207 | return ( | |
1208 | self._in_annotation == AnnotationState.STRING or | |
1209 | self.annotationsFutureEnabled | |
1210 | ) | |
1211 | ||
1212 | def handleChildren(self, tree, omit=None): | |
1213 | for node in iter_child_nodes(tree, omit=omit): | |
1214 | self.handleNode(node, tree) | |
1215 | ||
1216 | def isLiteralTupleUnpacking(self, node): | |
1217 | if isinstance(node, ast.Assign): | |
1218 | for child in node.targets + [node.value]: | |
1219 | if not hasattr(child, 'elts'): | |
1220 | return False | |
1221 | return True | |
1222 | ||
1223 | def isDocstring(self, node): | |
1224 | """ | |
1225 | Determine if the given node is a docstring, as long as it is at the | |
1226 | correct place in the node tree. | |
1227 | """ | |
1228 | return ( | |
1229 | isinstance(node, ast.Expr) and | |
1230 | isinstance(node.value, ast.Constant) and | |
1231 | isinstance(node.value.value, str) | |
1232 | ) | |
1233 | ||
1234 | def getDocstring(self, node): | |
1235 | if ( | |
1236 | isinstance(node, ast.Expr) and | |
1237 | isinstance(node.value, ast.Constant) and | |
1238 | isinstance(node.value.value, str) | |
1239 | ): | |
1240 | return node.value.value, node.lineno - 1 | |
1241 | else: | |
1242 | return None, None | |
1243 | ||
1244 | def handleNode(self, node, parent): | |
1245 | if node is None: | |
1246 | return | |
1247 | if self.offset and getattr(node, 'lineno', None) is not None: | |
1248 | node.lineno += self.offset[0] | |
1249 | node.col_offset += self.offset[1] | |
1250 | if ( | |
1251 | self.futuresAllowed and | |
1252 | self.nodeDepth == 0 and | |
1253 | not isinstance(node, ast.ImportFrom) and | |
1254 | not self.isDocstring(node) | |
1255 | ): | |
1256 | self.futuresAllowed = False | |
1257 | self.nodeDepth += 1 | |
1258 | node._pyflakes_depth = self.nodeDepth | |
1259 | node._pyflakes_parent = parent | |
1260 | try: | |
1261 | handler = self.getNodeHandler(node.__class__) | |
1262 | handler(node) | |
1263 | finally: | |
1264 | self.nodeDepth -= 1 | |
1265 | ||
1266 | _getDoctestExamples = doctest.DocTestParser().get_examples | |
1267 | ||
1268 | def handleDoctests(self, node): | |
1269 | try: | |
1270 | (docstring, node_lineno) = self.getDocstring(node.body[0]) | |
1271 | examples = docstring and self._getDoctestExamples(docstring) | |
1272 | except (ValueError, IndexError): | |
1273 | # e.g. line 6 of the docstring for <string> has inconsistent | |
1274 | # leading whitespace: ... | |
1275 | return | |
1276 | if not examples: | |
1277 | return | |
1278 | ||
1279 | # Place doctest in module scope | |
1280 | saved_stack = self.scopeStack | |
1281 | self.scopeStack = [self.scopeStack[0]] | |
1282 | node_offset = self.offset or (0, 0) | |
1283 | with self.in_scope(DoctestScope): | |
1284 | if '_' not in self.scopeStack[0]: | |
1285 | self.addBinding(None, Builtin('_')) | |
1286 | for example in examples: | |
1287 | try: | |
1288 | tree = ast.parse(example.source, "<doctest>") | |
1289 | except SyntaxError as e: | |
1290 | position = (node_lineno + example.lineno + e.lineno, | |
1291 | example.indent + 4 + (e.offset or 0)) | |
1292 | self.report(messages.DoctestSyntaxError, node, position) | |
1293 | else: | |
1294 | self.offset = (node_offset[0] + node_lineno + example.lineno, | |
1295 | node_offset[1] + example.indent + 4) | |
1296 | self.handleChildren(tree) | |
1297 | self.offset = node_offset | |
1298 | self.scopeStack = saved_stack | |
1299 | ||
1300 | @in_string_annotation | |
1301 | def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): | |
1302 | try: | |
1303 | tree = ast.parse(s) | |
1304 | except SyntaxError: | |
1305 | self.report(err, node, s) | |
1306 | return | |
1307 | ||
1308 | body = tree.body | |
1309 | if len(body) != 1 or not isinstance(body[0], ast.Expr): | |
1310 | self.report(err, node, s) | |
1311 | return | |
1312 | ||
1313 | parsed_annotation = tree.body[0].value | |
1314 | for descendant in ast.walk(parsed_annotation): | |
1315 | if ( | |
1316 | 'lineno' in descendant._attributes and | |
1317 | 'col_offset' in descendant._attributes | |
1318 | ): | |
1319 | descendant.lineno = ref_lineno | |
1320 | descendant.col_offset = ref_col_offset | |
1321 | ||
1322 | self.handleNode(parsed_annotation, node) | |
1323 | ||
1324 | def handle_annotation_always_deferred(self, annotation, parent): | |
1325 | fn = in_annotation(Checker.handleNode) | |
1326 | self.deferFunction(lambda: fn(self, annotation, parent)) | |
1327 | ||
1328 | @in_annotation | |
1329 | def handleAnnotation(self, annotation, node): | |
1330 | if ( | |
1331 | isinstance(annotation, ast.Constant) and | |
1332 | isinstance(annotation.value, str) | |
1333 | ): | |
1334 | # Defer handling forward annotation. | |
1335 | self.deferFunction(functools.partial( | |
1336 | self.handleStringAnnotation, | |
1337 | annotation.value, | |
1338 | node, | |
1339 | annotation.lineno, | |
1340 | annotation.col_offset, | |
1341 | messages.ForwardAnnotationSyntaxError, | |
1342 | )) | |
1343 | elif self.annotationsFutureEnabled: | |
1344 | self.handle_annotation_always_deferred(annotation, node) | |
1345 | else: | |
1346 | self.handleNode(annotation, node) | |
1347 | ||
1348 | def ignore(self, node): | |
1349 | pass | |
1350 | ||
1351 | # "stmt" type nodes | |
1352 | DELETE = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = ASYNCWITH = \ | |
1353 | EXPR = ASSIGN = handleChildren | |
1354 | ||
1355 | PASS = ignore | |
1356 | ||
1357 | # "expr" type nodes | |
1358 | BOOLOP = UNARYOP = SET = ATTRIBUTE = STARRED = NAMECONSTANT = \ | |
1359 | NAMEDEXPR = handleChildren | |
1360 | ||
1361 | def SUBSCRIPT(self, node): | |
1362 | if _is_name_or_attr(node.value, 'Literal'): | |
1363 | with self._enter_annotation(AnnotationState.NONE): | |
1364 | self.handleChildren(node) | |
1365 | elif _is_name_or_attr(node.value, 'Annotated'): | |
1366 | self.handleNode(node.value, node) | |
1367 | ||
1368 | # py39+ | |
1369 | if isinstance(node.slice, ast.Tuple): | |
1370 | slice_tuple = node.slice | |
1371 | # <py39 | |
1372 | elif ( | |
1373 | isinstance(node.slice, ast.Index) and | |
1374 | isinstance(node.slice.value, ast.Tuple) | |
1375 | ): | |
1376 | slice_tuple = node.slice.value | |
1377 | else: | |
1378 | slice_tuple = None | |
1379 | ||
1380 | # not a multi-arg `Annotated` | |
1381 | if slice_tuple is None or len(slice_tuple.elts) < 2: | |
1382 | self.handleNode(node.slice, node) | |
1383 | else: | |
1384 | # the first argument is the type | |
1385 | self.handleNode(slice_tuple.elts[0], node) | |
1386 | # the rest of the arguments are not | |
1387 | with self._enter_annotation(AnnotationState.NONE): | |
1388 | for arg in slice_tuple.elts[1:]: | |
1389 | self.handleNode(arg, node) | |
1390 | ||
1391 | self.handleNode(node.ctx, node) | |
1392 | else: | |
1393 | if _is_any_typing_member(node.value, self.scopeStack): | |
1394 | with self._enter_annotation(): | |
1395 | self.handleChildren(node) | |
1396 | else: | |
1397 | self.handleChildren(node) | |
1398 | ||
1399 | def _handle_string_dot_format(self, node): | |
1400 | try: | |
1401 | placeholders = tuple(parse_format_string(node.func.value.value)) | |
1402 | except ValueError as e: | |
1403 | self.report(messages.StringDotFormatInvalidFormat, node, e) | |
1404 | return | |
1405 | ||
1406 | auto = None | |
1407 | next_auto = 0 | |
1408 | ||
1409 | placeholder_positional = set() | |
1410 | placeholder_named = set() | |
1411 | ||
1412 | def _add_key(fmtkey): | |
1413 | """Returns True if there is an error which should early-exit""" | |
1414 | nonlocal auto, next_auto | |
1415 | ||
1416 | if fmtkey is None: # end of string or `{` / `}` escapes | |
1417 | return False | |
1418 | ||
1419 | # attributes / indices are allowed in `.format(...)` | |
1420 | fmtkey, _, _ = fmtkey.partition('.') | |
1421 | fmtkey, _, _ = fmtkey.partition('[') | |
1422 | ||
1423 | try: | |
1424 | fmtkey = int(fmtkey) | |
1425 | except ValueError: | |
1426 | pass | |
1427 | else: # fmtkey was an integer | |
1428 | if auto is True: | |
1429 | self.report(messages.StringDotFormatMixingAutomatic, node) | |
1430 | return True | |
1431 | else: | |
1432 | auto = False | |
1433 | ||
1434 | if fmtkey == '': | |
1435 | if auto is False: | |
1436 | self.report(messages.StringDotFormatMixingAutomatic, node) | |
1437 | return True | |
1438 | else: | |
1439 | auto = True | |
1440 | ||
1441 | fmtkey = next_auto | |
1442 | next_auto += 1 | |
1443 | ||
1444 | if isinstance(fmtkey, int): | |
1445 | placeholder_positional.add(fmtkey) | |
1446 | else: | |
1447 | placeholder_named.add(fmtkey) | |
1448 | ||
1449 | return False | |
1450 | ||
1451 | for _, fmtkey, spec, _ in placeholders: | |
1452 | if _add_key(fmtkey): | |
1453 | return | |
1454 | ||
1455 | # spec can also contain format specifiers | |
1456 | if spec is not None: | |
1457 | try: | |
1458 | spec_placeholders = tuple(parse_format_string(spec)) | |
1459 | except ValueError as e: | |
1460 | self.report(messages.StringDotFormatInvalidFormat, node, e) | |
1461 | return | |
1462 | ||
1463 | for _, spec_fmtkey, spec_spec, _ in spec_placeholders: | |
1464 | # can't recurse again | |
1465 | if spec_spec is not None and '{' in spec_spec: | |
1466 | self.report( | |
1467 | messages.StringDotFormatInvalidFormat, | |
1468 | node, | |
1469 | 'Max string recursion exceeded', | |
1470 | ) | |
1471 | return | |
1472 | if _add_key(spec_fmtkey): | |
1473 | return | |
1474 | ||
1475 | # bail early if there is *args or **kwargs | |
1476 | if ( | |
1477 | # *args | |
1478 | any(isinstance(arg, ast.Starred) for arg in node.args) or | |
1479 | # **kwargs | |
1480 | any(kwd.arg is None for kwd in node.keywords) | |
1481 | ): | |
1482 | return | |
1483 | ||
1484 | substitution_positional = set(range(len(node.args))) | |
1485 | substitution_named = {kwd.arg for kwd in node.keywords} | |
1486 | ||
1487 | extra_positional = substitution_positional - placeholder_positional | |
1488 | extra_named = substitution_named - placeholder_named | |
1489 | ||
1490 | missing_arguments = ( | |
1491 | (placeholder_positional | placeholder_named) - | |
1492 | (substitution_positional | substitution_named) | |
1493 | ) | |
1494 | ||
1495 | if extra_positional: | |
1496 | self.report( | |
1497 | messages.StringDotFormatExtraPositionalArguments, | |
1498 | node, | |
1499 | ', '.join(sorted(str(x) for x in extra_positional)), | |
1500 | ) | |
1501 | if extra_named: | |
1502 | self.report( | |
1503 | messages.StringDotFormatExtraNamedArguments, | |
1504 | node, | |
1505 | ', '.join(sorted(extra_named)), | |
1506 | ) | |
1507 | if missing_arguments: | |
1508 | self.report( | |
1509 | messages.StringDotFormatMissingArgument, | |
1510 | node, | |
1511 | ', '.join(sorted(str(x) for x in missing_arguments)), | |
1512 | ) | |
1513 | ||
1514 | def CALL(self, node): | |
1515 | if ( | |
1516 | isinstance(node.func, ast.Attribute) and | |
1517 | isinstance(node.func.value, ast.Constant) and | |
1518 | isinstance(node.func.value.value, str) and | |
1519 | node.func.attr == 'format' | |
1520 | ): | |
1521 | self._handle_string_dot_format(node) | |
1522 | ||
1523 | omit = [] | |
1524 | annotated = [] | |
1525 | not_annotated = [] | |
1526 | ||
1527 | if ( | |
1528 | _is_typing(node.func, 'cast', self.scopeStack) and | |
1529 | len(node.args) >= 1 | |
1530 | ): | |
1531 | with self._enter_annotation(): | |
1532 | self.handleNode(node.args[0], node) | |
1533 | ||
1534 | elif _is_typing(node.func, 'TypeVar', self.scopeStack): | |
1535 | ||
1536 | # TypeVar("T", "int", "str") | |
1537 | omit += ["args"] | |
1538 | annotated += [arg for arg in node.args[1:]] | |
1539 | ||
1540 | # TypeVar("T", bound="str") | |
1541 | omit += ["keywords"] | |
1542 | annotated += [k.value for k in node.keywords if k.arg == "bound"] | |
1543 | not_annotated += [ | |
1544 | (k, ["value"] if k.arg == "bound" else None) | |
1545 | for k in node.keywords | |
1546 | ] | |
1547 | ||
1548 | elif _is_typing(node.func, "TypedDict", self.scopeStack): | |
1549 | # TypedDict("a", {"a": int}) | |
1550 | if len(node.args) > 1 and isinstance(node.args[1], ast.Dict): | |
1551 | omit += ["args"] | |
1552 | annotated += node.args[1].values | |
1553 | not_annotated += [ | |
1554 | (arg, ["values"] if i == 1 else None) | |
1555 | for i, arg in enumerate(node.args) | |
1556 | ] | |
1557 | ||
1558 | # TypedDict("a", a=int) | |
1559 | omit += ["keywords"] | |
1560 | annotated += [k.value for k in node.keywords] | |
1561 | not_annotated += [(k, ["value"]) for k in node.keywords] | |
1562 | ||
1563 | elif _is_typing(node.func, "NamedTuple", self.scopeStack): | |
1564 | # NamedTuple("a", [("a", int)]) | |
1565 | if ( | |
1566 | len(node.args) > 1 and | |
1567 | isinstance(node.args[1], (ast.Tuple, ast.List)) and | |
1568 | all(isinstance(x, (ast.Tuple, ast.List)) and | |
1569 | len(x.elts) == 2 for x in node.args[1].elts) | |
1570 | ): | |
1571 | omit += ["args"] | |
1572 | annotated += [elt.elts[1] for elt in node.args[1].elts] | |
1573 | not_annotated += [(elt.elts[0], None) for elt in node.args[1].elts] | |
1574 | not_annotated += [ | |
1575 | (arg, ["elts"] if i == 1 else None) | |
1576 | for i, arg in enumerate(node.args) | |
1577 | ] | |
1578 | not_annotated += [(elt, "elts") for elt in node.args[1].elts] | |
1579 | ||
1580 | # NamedTuple("a", a=int) | |
1581 | omit += ["keywords"] | |
1582 | annotated += [k.value for k in node.keywords] | |
1583 | not_annotated += [(k, ["value"]) for k in node.keywords] | |
1584 | ||
1585 | if omit: | |
1586 | with self._enter_annotation(AnnotationState.NONE): | |
1587 | for na_node, na_omit in not_annotated: | |
1588 | self.handleChildren(na_node, omit=na_omit) | |
1589 | self.handleChildren(node, omit=omit) | |
1590 | ||
1591 | with self._enter_annotation(): | |
1592 | for annotated_node in annotated: | |
1593 | self.handleNode(annotated_node, node) | |
1594 | else: | |
1595 | self.handleChildren(node) | |
1596 | ||
1597 | def _handle_percent_format(self, node): | |
1598 | try: | |
1599 | placeholders = parse_percent_format(node.left.value) | |
1600 | except ValueError: | |
1601 | self.report( | |
1602 | messages.PercentFormatInvalidFormat, | |
1603 | node, | |
1604 | 'incomplete format', | |
1605 | ) | |
1606 | return | |
1607 | ||
1608 | named = set() | |
1609 | positional_count = 0 | |
1610 | positional = None | |
1611 | for _, placeholder in placeholders: | |
1612 | if placeholder is None: | |
1613 | continue | |
1614 | name, _, width, precision, conversion = placeholder | |
1615 | ||
1616 | if conversion == '%': | |
1617 | continue | |
1618 | ||
1619 | if conversion not in VALID_CONVERSIONS: | |
1620 | self.report( | |
1621 | messages.PercentFormatUnsupportedFormatCharacter, | |
1622 | node, | |
1623 | conversion, | |
1624 | ) | |
1625 | ||
1626 | if positional is None and conversion: | |
1627 | positional = name is None | |
1628 | ||
1629 | for part in (width, precision): | |
1630 | if part is not None and '*' in part: | |
1631 | if not positional: | |
1632 | self.report( | |
1633 | messages.PercentFormatStarRequiresSequence, | |
1634 | node, | |
1635 | ) | |
1636 | else: | |
1637 | positional_count += 1 | |
1638 | ||
1639 | if positional and name is not None: | |
1640 | self.report( | |
1641 | messages.PercentFormatMixedPositionalAndNamed, | |
1642 | node, | |
1643 | ) | |
1644 | return | |
1645 | elif not positional and name is None: | |
1646 | self.report( | |
1647 | messages.PercentFormatMixedPositionalAndNamed, | |
1648 | node, | |
1649 | ) | |
1650 | return | |
1651 | ||
1652 | if positional: | |
1653 | positional_count += 1 | |
1654 | else: | |
1655 | named.add(name) | |
1656 | ||
1657 | if ( | |
1658 | isinstance(node.right, (ast.List, ast.Tuple)) and | |
1659 | # does not have any *splats (py35+ feature) | |
1660 | not any( | |
1661 | isinstance(elt, ast.Starred) | |
1662 | for elt in node.right.elts | |
1663 | ) | |
1664 | ): | |
1665 | substitution_count = len(node.right.elts) | |
1666 | if positional and positional_count != substitution_count: | |
1667 | self.report( | |
1668 | messages.PercentFormatPositionalCountMismatch, | |
1669 | node, | |
1670 | positional_count, | |
1671 | substitution_count, | |
1672 | ) | |
1673 | elif not positional: | |
1674 | self.report(messages.PercentFormatExpectedMapping, node) | |
1675 | ||
1676 | if ( | |
1677 | isinstance(node.right, ast.Dict) and | |
1678 | all( | |
1679 | isinstance(k, ast.Constant) and isinstance(k.value, str) | |
1680 | for k in node.right.keys | |
1681 | ) | |
1682 | ): | |
1683 | if positional and positional_count > 1: | |
1684 | self.report(messages.PercentFormatExpectedSequence, node) | |
1685 | return | |
1686 | ||
1687 | substitution_keys = {k.value for k in node.right.keys} | |
1688 | extra_keys = substitution_keys - named | |
1689 | missing_keys = named - substitution_keys | |
1690 | if not positional and extra_keys: | |
1691 | self.report( | |
1692 | messages.PercentFormatExtraNamedArguments, | |
1693 | node, | |
1694 | ', '.join(sorted(extra_keys)), | |
1695 | ) | |
1696 | if not positional and missing_keys: | |
1697 | self.report( | |
1698 | messages.PercentFormatMissingArgument, | |
1699 | node, | |
1700 | ', '.join(sorted(missing_keys)), | |
1701 | ) | |
1702 | ||
1703 | def BINOP(self, node): | |
1704 | if ( | |
1705 | isinstance(node.op, ast.Mod) and | |
1706 | isinstance(node.left, ast.Constant) and | |
1707 | isinstance(node.left.value, str) | |
1708 | ): | |
1709 | self._handle_percent_format(node) | |
1710 | self.handleChildren(node) | |
1711 | ||
1712 | def CONSTANT(self, node): | |
1713 | if isinstance(node.value, str) and self._in_annotation: | |
1714 | fn = functools.partial( | |
1715 | self.handleStringAnnotation, | |
1716 | node.value, | |
1717 | node, | |
1718 | node.lineno, | |
1719 | node.col_offset, | |
1720 | messages.ForwardAnnotationSyntaxError, | |
1721 | ) | |
1722 | self.deferFunction(fn) | |
1723 | ||
1724 | # "slice" type nodes | |
1725 | SLICE = EXTSLICE = INDEX = handleChildren | |
1726 | ||
1727 | # expression contexts are node instances too, though being constants | |
1728 | LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore | |
1729 | ||
1730 | # same for operators | |
1731 | AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ | |
1732 | BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ | |
1733 | EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = \ | |
1734 | MATMULT = ignore | |
1735 | ||
1736 | def RAISE(self, node): | |
1737 | self.handleChildren(node) | |
1738 | ||
1739 | arg = node.exc | |
1740 | ||
1741 | if isinstance(arg, ast.Call): | |
1742 | if is_notimplemented_name_node(arg.func): | |
1743 | # Handle "raise NotImplemented(...)" | |
1744 | self.report(messages.RaiseNotImplemented, node) | |
1745 | elif is_notimplemented_name_node(arg): | |
1746 | # Handle "raise NotImplemented" | |
1747 | self.report(messages.RaiseNotImplemented, node) | |
1748 | ||
1749 | # additional node types | |
1750 | COMPREHENSION = KEYWORD = FORMATTEDVALUE = handleChildren | |
1751 | ||
1752 | _in_fstring = False | |
1753 | ||
1754 | def JOINEDSTR(self, node): | |
1755 | if ( | |
1756 | # the conversion / etc. flags are parsed as f-strings without | |
1757 | # placeholders | |
1758 | not self._in_fstring and | |
1759 | not any(isinstance(x, ast.FormattedValue) for x in node.values) | |
1760 | ): | |
1761 | self.report(messages.FStringMissingPlaceholders, node) | |
1762 | ||
1763 | self._in_fstring, orig = True, self._in_fstring | |
1764 | try: | |
1765 | self.handleChildren(node) | |
1766 | finally: | |
1767 | self._in_fstring = orig | |
1768 | ||
1769 | def DICT(self, node): | |
1770 | # Complain if there are duplicate keys with different values | |
1771 | # If they have the same value it's not going to cause potentially | |
1772 | # unexpected behaviour so we'll not complain. | |
1773 | keys = [ | |
1774 | convert_to_value(key) for key in node.keys | |
1775 | ] | |
1776 | ||
1777 | key_counts = counter(keys) | |
1778 | duplicate_keys = [ | |
1779 | key for key, count in key_counts.items() | |
1780 | if count > 1 | |
1781 | ] | |
1782 | ||
1783 | for key in duplicate_keys: | |
1784 | key_indices = [i for i, i_key in enumerate(keys) if i_key == key] | |
1785 | ||
1786 | values = counter( | |
1787 | convert_to_value(node.values[index]) | |
1788 | for index in key_indices | |
1789 | ) | |
1790 | if any(count == 1 for value, count in values.items()): | |
1791 | for key_index in key_indices: | |
1792 | key_node = node.keys[key_index] | |
1793 | if isinstance(key, VariableKey): | |
1794 | self.report(messages.MultiValueRepeatedKeyVariable, | |
1795 | key_node, | |
1796 | key.name) | |
1797 | else: | |
1798 | self.report( | |
1799 | messages.MultiValueRepeatedKeyLiteral, | |
1800 | key_node, | |
1801 | key, | |
1802 | ) | |
1803 | self.handleChildren(node) | |
1804 | ||
1805 | def IF(self, node): | |
1806 | if isinstance(node.test, ast.Tuple) and node.test.elts != []: | |
1807 | self.report(messages.IfTuple, node) | |
1808 | self.handleChildren(node) | |
1809 | ||
1810 | IFEXP = IF | |
1811 | ||
1812 | def ASSERT(self, node): | |
1813 | if isinstance(node.test, ast.Tuple) and node.test.elts != []: | |
1814 | self.report(messages.AssertTuple, node) | |
1815 | self.handleChildren(node) | |
1816 | ||
1817 | def GLOBAL(self, node): | |
1818 | """ | |
1819 | Keep track of globals declarations. | |
1820 | """ | |
1821 | global_scope_index = 1 if self._in_doctest() else 0 | |
1822 | global_scope = self.scopeStack[global_scope_index] | |
1823 | ||
1824 | # Ignore 'global' statement in global scope. | |
1825 | if self.scope is not global_scope: | |
1826 | ||
1827 | # One 'global' statement can bind multiple (comma-delimited) names. | |
1828 | for node_name in node.names: | |
1829 | node_value = Assignment(node_name, node) | |
1830 | ||
1831 | # Remove UndefinedName messages already reported for this name. | |
1832 | # TODO: if the global is not used in this scope, it does not | |
1833 | # become a globally defined name. See test_unused_global. | |
1834 | self.messages = [ | |
1835 | m for m in self.messages if not | |
1836 | isinstance(m, messages.UndefinedName) or | |
1837 | m.message_args[0] != node_name] | |
1838 | ||
1839 | # Bind name to global scope if it doesn't exist already. | |
1840 | global_scope.setdefault(node_name, node_value) | |
1841 | ||
1842 | # Bind name to non-global scopes, but as already "used". | |
1843 | node_value.used = (global_scope, node) | |
1844 | for scope in self.scopeStack[global_scope_index + 1:]: | |
1845 | scope[node_name] = node_value | |
1846 | ||
1847 | NONLOCAL = GLOBAL | |
1848 | ||
1849 | def GENERATOREXP(self, node): | |
1850 | with self.in_scope(GeneratorScope): | |
1851 | self.handleChildren(node) | |
1852 | ||
1853 | LISTCOMP = DICTCOMP = SETCOMP = GENERATOREXP | |
1854 | ||
1855 | def NAME(self, node): | |
1856 | """ | |
1857 | Handle occurrence of Name (which can be a load/store/delete access.) | |
1858 | """ | |
1859 | # Locate the name in locals / function / globals scopes. | |
1860 | if isinstance(node.ctx, ast.Load): | |
1861 | self.handleNodeLoad(node, self.getParent(node)) | |
1862 | if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and | |
1863 | isinstance(node._pyflakes_parent, ast.Call)): | |
1864 | # we are doing locals() call in current scope | |
1865 | self.scope.usesLocals = True | |
1866 | elif isinstance(node.ctx, ast.Store): | |
1867 | self.handleNodeStore(node) | |
1868 | elif isinstance(node.ctx, ast.Del): | |
1869 | self.handleNodeDelete(node) | |
1870 | else: | |
1871 | # Unknown context | |
1872 | raise RuntimeError(f"Got impossible expression context: {node.ctx!r}") | |
1873 | ||
1874 | def CONTINUE(self, node): | |
1875 | # Walk the tree up until we see a loop (OK), a function or class | |
1876 | # definition (not OK), for 'continue', a finally block (not OK), or | |
1877 | # the top module scope (not OK) | |
1878 | n = node | |
1879 | while hasattr(n, '_pyflakes_parent'): | |
1880 | n, n_child = n._pyflakes_parent, n | |
1881 | if isinstance(n, (ast.While, ast.For, ast.AsyncFor)): | |
1882 | # Doesn't apply unless it's in the loop itself | |
1883 | if n_child not in n.orelse: | |
1884 | return | |
1885 | if isinstance(n, (ast.FunctionDef, ast.ClassDef)): | |
1886 | break | |
1887 | if isinstance(node, ast.Continue): | |
1888 | self.report(messages.ContinueOutsideLoop, node) | |
1889 | else: # ast.Break | |
1890 | self.report(messages.BreakOutsideLoop, node) | |
1891 | ||
1892 | BREAK = CONTINUE | |
1893 | ||
1894 | def RETURN(self, node): | |
1895 | if isinstance(self.scope, (ClassScope, ModuleScope)): | |
1896 | self.report(messages.ReturnOutsideFunction, node) | |
1897 | return | |
1898 | ||
1899 | if ( | |
1900 | node.value and | |
1901 | hasattr(self.scope, 'returnValue') and | |
1902 | not self.scope.returnValue | |
1903 | ): | |
1904 | self.scope.returnValue = node.value | |
1905 | self.handleNode(node.value, node) | |
1906 | ||
1907 | def YIELD(self, node): | |
1908 | if isinstance(self.scope, (ClassScope, ModuleScope)): | |
1909 | self.report(messages.YieldOutsideFunction, node) | |
1910 | return | |
1911 | ||
1912 | self.handleNode(node.value, node) | |
1913 | ||
1914 | AWAIT = YIELDFROM = YIELD | |
1915 | ||
1916 | def FUNCTIONDEF(self, node): | |
1917 | for deco in node.decorator_list: | |
1918 | self.handleNode(deco, node) | |
1919 | ||
1920 | with self._type_param_scope(node): | |
1921 | self.LAMBDA(node) | |
1922 | ||
1923 | self.addBinding(node, FunctionDefinition(node.name, node)) | |
1924 | # doctest does not process doctest within a doctest, | |
1925 | # or in nested functions. | |
1926 | if (self.withDoctest and | |
1927 | not self._in_doctest() and | |
1928 | not isinstance(self.scope, FunctionScope)): | |
1929 | self.deferFunction(lambda: self.handleDoctests(node)) | |
1930 | ||
1931 | ASYNCFUNCTIONDEF = FUNCTIONDEF | |
1932 | ||
1933 | def LAMBDA(self, node): | |
1934 | args = [] | |
1935 | annotations = [] | |
1936 | ||
1937 | for arg in node.args.posonlyargs: | |
1938 | args.append(arg.arg) | |
1939 | annotations.append(arg.annotation) | |
1940 | for arg in node.args.args + node.args.kwonlyargs: | |
1941 | args.append(arg.arg) | |
1942 | annotations.append(arg.annotation) | |
1943 | defaults = node.args.defaults + node.args.kw_defaults | |
1944 | ||
1945 | has_annotations = not isinstance(node, ast.Lambda) | |
1946 | ||
1947 | for arg_name in ('vararg', 'kwarg'): | |
1948 | wildcard = getattr(node.args, arg_name) | |
1949 | if not wildcard: | |
1950 | continue | |
1951 | args.append(wildcard.arg) | |
1952 | if has_annotations: | |
1953 | annotations.append(wildcard.annotation) | |
1954 | ||
1955 | if has_annotations: | |
1956 | annotations.append(node.returns) | |
1957 | ||
1958 | if len(set(args)) < len(args): | |
1959 | for (idx, arg) in enumerate(args): | |
1960 | if arg in args[:idx]: | |
1961 | self.report(messages.DuplicateArgument, node, arg) | |
1962 | ||
1963 | for annotation in annotations: | |
1964 | self.handleAnnotation(annotation, node) | |
1965 | ||
1966 | for default in defaults: | |
1967 | self.handleNode(default, node) | |
1968 | ||
1969 | def runFunction(): | |
1970 | with self.in_scope(FunctionScope): | |
1971 | self.handleChildren( | |
1972 | node, | |
1973 | omit=('decorator_list', 'returns', 'type_params'), | |
1974 | ) | |
1975 | ||
1976 | self.deferFunction(runFunction) | |
1977 | ||
1978 | def ARGUMENTS(self, node): | |
1979 | self.handleChildren(node, omit=('defaults', 'kw_defaults')) | |
1980 | ||
1981 | def ARG(self, node): | |
1982 | self.addBinding(node, Argument(node.arg, self.getScopeNode(node))) | |
1983 | ||
1984 | def CLASSDEF(self, node): | |
1985 | """ | |
1986 | Check names used in a class definition, including its decorators, base | |
1987 | classes, and the body of its definition. Additionally, add its name to | |
1988 | the current scope. | |
1989 | """ | |
1990 | for deco in node.decorator_list: | |
1991 | self.handleNode(deco, node) | |
1992 | ||
1993 | with self._type_param_scope(node): | |
1994 | for baseNode in node.bases: | |
1995 | self.handleNode(baseNode, node) | |
1996 | for keywordNode in node.keywords: | |
1997 | self.handleNode(keywordNode, node) | |
1998 | with self.in_scope(ClassScope): | |
1999 | # doctest does not process doctest within a doctest | |
2000 | # classes within classes are processed. | |
2001 | if (self.withDoctest and | |
2002 | not self._in_doctest() and | |
2003 | not isinstance(self.scope, FunctionScope)): | |
2004 | self.deferFunction(lambda: self.handleDoctests(node)) | |
2005 | for stmt in node.body: | |
2006 | self.handleNode(stmt, node) | |
2007 | ||
2008 | self.addBinding(node, ClassDefinition(node.name, node)) | |
2009 | ||
2010 | def AUGASSIGN(self, node): | |
2011 | self.handleNodeLoad(node.target, node) | |
2012 | self.handleNode(node.value, node) | |
2013 | self.handleNode(node.target, node) | |
2014 | ||
2015 | def TUPLE(self, node): | |
2016 | if isinstance(node.ctx, ast.Store): | |
2017 | # Python 3 advanced tuple unpacking: a, *b, c = d. | |
2018 | # Only one starred expression is allowed, and no more than 1<<8 | |
2019 | # assignments are allowed before a stared expression. There is | |
2020 | # also a limit of 1<<24 expressions after the starred expression, | |
2021 | # which is impossible to test due to memory restrictions, but we | |
2022 | # add it here anyway | |
2023 | has_starred = False | |
2024 | star_loc = -1 | |
2025 | for i, n in enumerate(node.elts): | |
2026 | if isinstance(n, ast.Starred): | |
2027 | if has_starred: | |
2028 | self.report(messages.TwoStarredExpressions, node) | |
2029 | # The SyntaxError doesn't distinguish two from more | |
2030 | # than two. | |
2031 | break | |
2032 | has_starred = True | |
2033 | star_loc = i | |
2034 | if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24: | |
2035 | self.report(messages.TooManyExpressionsInStarredAssignment, node) | |
2036 | self.handleChildren(node) | |
2037 | ||
2038 | LIST = TUPLE | |
2039 | ||
2040 | def IMPORT(self, node): | |
2041 | for alias in node.names: | |
2042 | if '.' in alias.name and not alias.asname: | |
2043 | importation = SubmoduleImportation(alias.name, node) | |
2044 | else: | |
2045 | name = alias.asname or alias.name | |
2046 | importation = Importation(name, node, alias.name) | |
2047 | self.addBinding(node, importation) | |
2048 | ||
2049 | def IMPORTFROM(self, node): | |
2050 | if node.module == '__future__': | |
2051 | if not self.futuresAllowed: | |
2052 | self.report(messages.LateFutureImport, node) | |
2053 | else: | |
2054 | self.futuresAllowed = False | |
2055 | ||
2056 | module = ('.' * node.level) + (node.module or '') | |
2057 | ||
2058 | for alias in node.names: | |
2059 | name = alias.asname or alias.name | |
2060 | if node.module == '__future__': | |
2061 | importation = FutureImportation(name, node, self.scope) | |
2062 | if alias.name not in __future__.all_feature_names: | |
2063 | self.report(messages.FutureFeatureNotDefined, | |
2064 | node, alias.name) | |
2065 | if alias.name == 'annotations': | |
2066 | self.annotationsFutureEnabled = True | |
2067 | elif alias.name == '*': | |
2068 | if not isinstance(self.scope, ModuleScope): | |
2069 | self.report(messages.ImportStarNotPermitted, | |
2070 | node, module) | |
2071 | continue | |
2072 | ||
2073 | self.scope.importStarred = True | |
2074 | self.report(messages.ImportStarUsed, node, module) | |
2075 | importation = StarImportation(module, node) | |
2076 | else: | |
2077 | importation = ImportationFrom(name, node, | |
2078 | module, alias.name) | |
2079 | self.addBinding(node, importation) | |
2080 | ||
2081 | def TRY(self, node): | |
2082 | handler_names = [] | |
2083 | # List the exception handlers | |
2084 | for i, handler in enumerate(node.handlers): | |
2085 | if isinstance(handler.type, ast.Tuple): | |
2086 | for exc_type in handler.type.elts: | |
2087 | handler_names.append(getNodeName(exc_type)) | |
2088 | elif handler.type: | |
2089 | handler_names.append(getNodeName(handler.type)) | |
2090 | ||
2091 | if handler.type is None and i < len(node.handlers) - 1: | |
2092 | self.report(messages.DefaultExceptNotLast, handler) | |
2093 | # Memorize the except handlers and process the body | |
2094 | self.exceptHandlers.append(handler_names) | |
2095 | for child in node.body: | |
2096 | self.handleNode(child, node) | |
2097 | self.exceptHandlers.pop() | |
2098 | # Process the other nodes: "except:", "else:", "finally:" | |
2099 | self.handleChildren(node, omit='body') | |
2100 | ||
2101 | TRYSTAR = TRY | |
2102 | ||
2103 | def EXCEPTHANDLER(self, node): | |
2104 | if node.name is None: | |
2105 | self.handleChildren(node) | |
2106 | return | |
2107 | ||
2108 | # If the name already exists in the scope, modify state of existing | |
2109 | # binding. | |
2110 | if node.name in self.scope: | |
2111 | self.handleNodeStore(node) | |
2112 | ||
2113 | # 3.x: the name of the exception, which is not a Name node, but a | |
2114 | # simple string, creates a local that is only bound within the scope of | |
2115 | # the except: block. As such, temporarily remove the existing binding | |
2116 | # to more accurately determine if the name is used in the except: | |
2117 | # block. | |
2118 | ||
2119 | try: | |
2120 | prev_definition = self.scope.pop(node.name) | |
2121 | except KeyError: | |
2122 | prev_definition = None | |
2123 | ||
2124 | self.handleNodeStore(node) | |
2125 | self.handleChildren(node) | |
2126 | ||
2127 | # See discussion on https://github.com/PyCQA/pyflakes/pull/59 | |
2128 | ||
2129 | # We're removing the local name since it's being unbound after leaving | |
2130 | # the except: block and it's always unbound if the except: block is | |
2131 | # never entered. This will cause an "undefined name" error raised if | |
2132 | # the checked code tries to use the name afterwards. | |
2133 | # | |
2134 | # Unless it's been removed already. Then do nothing. | |
2135 | ||
2136 | try: | |
2137 | binding = self.scope.pop(node.name) | |
2138 | except KeyError: | |
2139 | pass | |
2140 | else: | |
2141 | if not binding.used: | |
2142 | self.report(messages.UnusedVariable, node, node.name) | |
2143 | ||
2144 | # Restore. | |
2145 | if prev_definition: | |
2146 | self.scope[node.name] = prev_definition | |
2147 | ||
2148 | def ANNASSIGN(self, node): | |
2149 | self.handleAnnotation(node.annotation, node) | |
2150 | # If the assignment has value, handle the *value* now. | |
2151 | if node.value: | |
2152 | # If the annotation is `TypeAlias`, handle the *value* as an annotation. | |
2153 | if _is_typing(node.annotation, 'TypeAlias', self.scopeStack): | |
2154 | self.handleAnnotation(node.value, node) | |
2155 | else: | |
2156 | self.handleNode(node.value, node) | |
2157 | self.handleNode(node.target, node) | |
2158 | ||
2159 | def COMPARE(self, node): | |
2160 | left = node.left | |
2161 | for op, right in zip(node.ops, node.comparators): | |
2162 | if ( | |
2163 | isinstance(op, (ast.Is, ast.IsNot)) and ( | |
2164 | _is_const_non_singleton(left) or | |
2165 | _is_const_non_singleton(right) | |
2166 | ) | |
2167 | ): | |
2168 | self.report(messages.IsLiteral, node) | |
2169 | left = right | |
2170 | ||
2171 | self.handleChildren(node) | |
2172 | ||
2173 | MATCH = MATCH_CASE = MATCHCLASS = MATCHOR = MATCHSEQUENCE = handleChildren | |
2174 | MATCHSINGLETON = MATCHVALUE = handleChildren | |
2175 | ||
2176 | def _match_target(self, node): | |
2177 | self.handleNodeStore(node) | |
2178 | self.handleChildren(node) | |
2179 | ||
2180 | MATCHAS = MATCHMAPPING = MATCHSTAR = _match_target | |
2181 | ||
2182 | @contextlib.contextmanager | |
2183 | def _type_param_scope(self, node): | |
2184 | with contextlib.ExitStack() as ctx: | |
2185 | if sys.version_info >= (3, 12): | |
2186 | ctx.enter_context(self.in_scope(TypeScope)) | |
2187 | for param in node.type_params: | |
2188 | self.handleNode(param, node) | |
2189 | yield | |
2190 | ||
2191 | def TYPEVAR(self, node): | |
2192 | self.handleNodeStore(node) | |
2193 | self.handle_annotation_always_deferred(node.bound, node) | |
2194 | ||
2195 | def TYPEALIAS(self, node): | |
2196 | self.handleNode(node.name, node) | |
2197 | with self._type_param_scope(node): | |
2198 | self.handle_annotation_always_deferred(node.value, node) |