Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/parso/python/errors.py: 96%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2import codecs
3import sys
4import warnings
5import re
6from contextlib import contextmanager
8from parso.normalizer import Normalizer, NormalizerConfig, Issue, Rule
9from parso.python.tokenize import _get_token_collection
11_BLOCK_STMTS = ('if_stmt', 'while_stmt', 'for_stmt', 'try_stmt', 'with_stmt')
12_STAR_EXPR_PARENTS = ('testlist_star_expr', 'testlist_comp', 'exprlist')
13# This is the maximal block size given by python.
14_MAX_BLOCK_SIZE = 20
15_MAX_INDENT_COUNT = 100
16ALLOWED_FUTURES = (
17 'nested_scopes', 'generators', 'division', 'absolute_import',
18 'with_statement', 'print_function', 'unicode_literals', 'generator_stop',
19)
20_COMP_FOR_TYPES = ('comp_for', 'sync_comp_for')
23def _get_rhs_name(node, version):
24 type_ = node.type
25 if type_ == "lambdef":
26 return "lambda"
27 elif type_ == "atom":
28 comprehension = _get_comprehension_type(node)
29 first, second = node.children[:2]
30 if comprehension is not None:
31 return comprehension
32 elif second.type == "dictorsetmaker":
33 if version < (3, 8):
34 return "literal"
35 else:
36 if second.children[1] == ":" or second.children[0] == "**":
37 if version < (3, 10):
38 return "dict display"
39 else:
40 return "dict literal"
41 else:
42 return "set display"
43 elif (
44 first == "("
45 and (second == ")"
46 or (len(node.children) == 3 and node.children[1].type == "testlist_comp"))
47 ):
48 return "tuple"
49 elif first == "(":
50 return _get_rhs_name(_remove_parens(node), version=version)
51 elif first == "[":
52 return "list"
53 elif first == "{" and second == "}":
54 if version < (3, 10):
55 return "dict display"
56 else:
57 return "dict literal"
58 elif first == "{" and len(node.children) > 2:
59 return "set display"
60 elif type_ == "keyword":
61 if "yield" in node.value:
62 return "yield expression"
63 if version < (3, 8):
64 return "keyword"
65 else:
66 return str(node.value)
67 elif type_ == "operator" and node.value == "...":
68 if version < (3, 10):
69 return "Ellipsis"
70 else:
71 return "ellipsis"
72 elif type_ == "comparison":
73 return "comparison"
74 elif type_ in ("string", "number", "strings"):
75 return "literal"
76 elif type_ == "yield_expr":
77 return "yield expression"
78 elif type_ == "test":
79 return "conditional expression"
80 elif type_ in ("atom_expr", "power"):
81 if node.children[0] == "await":
82 return "await expression"
83 elif node.children[-1].type == "trailer":
84 trailer = node.children[-1]
85 if trailer.children[0] == "(":
86 return "function call"
87 elif trailer.children[0] == "[":
88 return "subscript"
89 elif trailer.children[0] == ".":
90 return "attribute"
91 elif (
92 ("expr" in type_ and "star_expr" not in type_) # is a substring
93 or "_test" in type_
94 or type_ in ("term", "factor")
95 ):
96 if version < (3, 10):
97 return "operator"
98 else:
99 return "expression"
100 elif type_ == "star_expr":
101 return "starred"
102 elif type_ == "testlist_star_expr":
103 return "tuple"
104 elif type_ == "fstring":
105 return "f-string expression"
106 return type_ # shouldn't reach here
109def _iter_stmts(scope):
110 """
111 Iterates over all statements and splits up simple_stmt.
112 """
113 for child in scope.children:
114 if child.type == 'simple_stmt':
115 for child2 in child.children:
116 if child2.type == 'newline' or child2 == ';':
117 continue
118 yield child2
119 else:
120 yield child
123def _get_comprehension_type(atom):
124 first, second = atom.children[:2]
125 if second.type == 'testlist_comp' and second.children[1].type in _COMP_FOR_TYPES:
126 if first == '[':
127 return 'list comprehension'
128 else:
129 return 'generator expression'
130 elif second.type == 'dictorsetmaker' and second.children[-1].type in _COMP_FOR_TYPES:
131 if second.children[1] == ':':
132 return 'dict comprehension'
133 else:
134 return 'set comprehension'
135 return None
138def _is_future_import(import_from):
139 # It looks like a __future__ import that is relative is still a future
140 # import. That feels kind of odd, but whatever.
141 # if import_from.level != 0:
142 # return False
143 from_names = import_from.get_from_names()
144 return [n.value for n in from_names] == ['__future__']
147def _remove_parens(atom):
148 """
149 Returns the inner part of an expression like `(foo)`. Also removes nested
150 parens.
151 """
152 try:
153 children = atom.children
154 except AttributeError:
155 pass
156 else:
157 if len(children) == 3 and children[0] == '(':
158 return _remove_parens(atom.children[1])
159 return atom
162def _skip_parens_bottom_up(node):
163 """
164 Returns an ancestor node of an expression, skipping all levels of parens
165 bottom-up.
166 """
167 while node.parent is not None:
168 node = node.parent
169 if node.type != 'atom' or node.children[0] != '(':
170 return node
171 return None
174def _iter_params(parent_node):
175 return (n for n in parent_node.children if n.type == 'param' or n.type == 'operator')
178def _is_future_import_first(import_from):
179 """
180 Checks if the import is the first statement of a file.
181 """
182 found_docstring = False
183 for stmt in _iter_stmts(import_from.get_root_node()):
184 if stmt.type == 'string' and not found_docstring:
185 continue
186 found_docstring = True
188 if stmt == import_from:
189 return True
190 if stmt.type == 'import_from' and _is_future_import(stmt):
191 continue
192 return False
195def _iter_definition_exprs_from_lists(exprlist):
196 def check_expr(child):
197 if child.type == 'atom':
198 if child.children[0] == '(':
199 testlist_comp = child.children[1]
200 if testlist_comp.type == 'testlist_comp':
201 yield from _iter_definition_exprs_from_lists(testlist_comp)
202 return
203 else:
204 # It's a paren that doesn't do anything, like 1 + (1)
205 yield from check_expr(testlist_comp)
206 return
207 elif child.children[0] == '[':
208 yield testlist_comp
209 return
210 yield child
212 if exprlist.type in _STAR_EXPR_PARENTS:
213 for child in exprlist.children[::2]:
214 yield from check_expr(child)
215 else:
216 yield from check_expr(exprlist)
219def _get_expr_stmt_definition_exprs(expr_stmt):
220 exprs = []
221 for list_ in expr_stmt.children[:-2:2]:
222 if list_.type in ('testlist_star_expr', 'testlist'):
223 exprs += _iter_definition_exprs_from_lists(list_)
224 else:
225 exprs.append(list_)
226 return exprs
229def _get_for_stmt_definition_exprs(for_stmt):
230 exprlist = for_stmt.children[1]
231 return list(_iter_definition_exprs_from_lists(exprlist))
234def _is_argument_comprehension(argument):
235 return argument.children[1].type in _COMP_FOR_TYPES
238def _any_fstring_error(version, node):
239 if version < (3, 9) or node is None:
240 return False
241 if node.type == "error_node":
242 return any(child.type == "fstring_start" for child in node.children)
243 elif node.type == "fstring":
244 return True
245 else:
246 return node.search_ancestor("fstring")
249class _Context:
250 def __init__(self, node, add_syntax_error, parent_context=None):
251 self.node = node
252 self.blocks = []
253 self.parent_context = parent_context
254 self._used_name_dict = {}
255 self._global_names = []
256 self._local_params_names = []
257 self._nonlocal_names = []
258 self._nonlocal_names_in_subscopes = []
259 self._add_syntax_error = add_syntax_error
261 def is_async_funcdef(self):
262 # Stupidly enough async funcdefs can have two different forms,
263 # depending if a decorator is used or not.
264 return self.is_function() \
265 and self.node.parent.type in ('async_funcdef', 'async_stmt')
267 def is_function(self):
268 return self.node.type == 'funcdef'
270 def add_name(self, name):
271 parent_type = name.parent.type
272 if parent_type == 'trailer':
273 # We are only interested in first level names.
274 return
276 if parent_type == 'global_stmt':
277 self._global_names.append(name)
278 elif parent_type == 'nonlocal_stmt':
279 self._nonlocal_names.append(name)
280 elif parent_type == 'funcdef':
281 self._local_params_names.extend(
282 [param.name.value for param in name.parent.get_params()]
283 )
284 else:
285 self._used_name_dict.setdefault(name.value, []).append(name)
287 def finalize(self):
288 """
289 Returns a list of nonlocal names that need to be part of that scope.
290 """
291 self._analyze_names(self._global_names, 'global')
292 self._analyze_names(self._nonlocal_names, 'nonlocal')
294 global_name_strs = {n.value: n for n in self._global_names}
295 for nonlocal_name in self._nonlocal_names:
296 try:
297 global_name = global_name_strs[nonlocal_name.value]
298 except KeyError:
299 continue
301 message = "name '%s' is nonlocal and global" % global_name.value
302 if global_name.start_pos < nonlocal_name.start_pos:
303 error_name = global_name
304 else:
305 error_name = nonlocal_name
306 self._add_syntax_error(error_name, message)
308 nonlocals_not_handled = []
309 for nonlocal_name in self._nonlocal_names_in_subscopes:
310 search = nonlocal_name.value
311 if search in self._local_params_names:
312 continue
313 if search in global_name_strs or self.parent_context is None:
314 message = "no binding for nonlocal '%s' found" % nonlocal_name.value
315 self._add_syntax_error(nonlocal_name, message)
316 elif not self.is_function() or \
317 nonlocal_name.value not in self._used_name_dict:
318 nonlocals_not_handled.append(nonlocal_name)
319 return self._nonlocal_names + nonlocals_not_handled
321 def _analyze_names(self, globals_or_nonlocals, type_):
322 def raise_(message):
323 self._add_syntax_error(base_name, message % (base_name.value, type_))
325 params = []
326 if self.node.type == 'funcdef':
327 params = self.node.get_params()
329 for base_name in globals_or_nonlocals:
330 found_global_or_nonlocal = False
331 # Somehow Python does it the reversed way.
332 for name in reversed(self._used_name_dict.get(base_name.value, [])):
333 if name.start_pos > base_name.start_pos:
334 # All following names don't have to be checked.
335 found_global_or_nonlocal = True
337 parent = name.parent
338 if parent.type == 'param' and parent.name == name:
339 # Skip those here, these definitions belong to the next
340 # scope.
341 continue
343 if name.is_definition():
344 if parent.type == 'expr_stmt' \
345 and parent.children[1].type == 'annassign':
346 if found_global_or_nonlocal:
347 # If it's after the global the error seems to be
348 # placed there.
349 base_name = name
350 raise_("annotated name '%s' can't be %s")
351 break
352 else:
353 message = "name '%s' is assigned to before %s declaration"
354 else:
355 message = "name '%s' is used prior to %s declaration"
357 if not found_global_or_nonlocal:
358 raise_(message)
359 # Only add an error for the first occurence.
360 break
362 for param in params:
363 if param.name.value == base_name.value:
364 raise_("name '%s' is parameter and %s"),
366 @contextmanager
367 def add_block(self, node):
368 self.blocks.append(node)
369 yield
370 self.blocks.pop()
372 def add_context(self, node):
373 return _Context(node, self._add_syntax_error, parent_context=self)
375 def close_child_context(self, child_context):
376 self._nonlocal_names_in_subscopes += child_context.finalize()
379class ErrorFinder(Normalizer):
380 """
381 Searches for errors in the syntax tree.
382 """
383 def __init__(self, *args, **kwargs):
384 super().__init__(*args, **kwargs)
385 self._error_dict = {}
386 self.version = self.grammar.version_info
388 def initialize(self, node):
389 def create_context(node):
390 if node is None:
391 return None
393 parent_context = create_context(node.parent)
394 if node.type in ('classdef', 'funcdef', 'file_input'):
395 return _Context(node, self._add_syntax_error, parent_context)
396 return parent_context
398 self.context = create_context(node) or _Context(node, self._add_syntax_error)
399 self._indentation_count = 0
401 def visit(self, node):
402 if node.type == 'error_node':
403 with self.visit_node(node):
404 # Don't need to investigate the inners of an error node. We
405 # might find errors in there that should be ignored, because
406 # the error node itself already shows that there's an issue.
407 return ''
408 return super().visit(node)
410 @contextmanager
411 def visit_node(self, node):
412 self._check_type_rules(node)
414 if node.type in _BLOCK_STMTS:
415 with self.context.add_block(node):
416 if len(self.context.blocks) == _MAX_BLOCK_SIZE:
417 self._add_syntax_error(node, "too many statically nested blocks")
418 yield
419 return
420 elif node.type == 'suite':
421 self._indentation_count += 1
422 if self._indentation_count == _MAX_INDENT_COUNT:
423 self._add_indentation_error(node.children[1], "too many levels of indentation")
425 yield
427 if node.type == 'suite':
428 self._indentation_count -= 1
429 elif node.type in ('classdef', 'funcdef'):
430 context = self.context
431 self.context = context.parent_context
432 self.context.close_child_context(context)
434 def visit_leaf(self, leaf):
435 if leaf.type == 'error_leaf':
436 if leaf.token_type in ('INDENT', 'ERROR_DEDENT'):
437 # Indents/Dedents itself never have a prefix. They are just
438 # "pseudo" tokens that get removed by the syntax tree later.
439 # Therefore in case of an error we also have to check for this.
440 spacing = list(leaf.get_next_leaf()._split_prefix())[-1]
441 if leaf.token_type == 'INDENT':
442 message = 'unexpected indent'
443 else:
444 message = 'unindent does not match any outer indentation level'
445 self._add_indentation_error(spacing, message)
446 else:
447 if leaf.value.startswith('\\'):
448 message = 'unexpected character after line continuation character'
449 else:
450 match = re.match('\\w{,2}("{1,3}|\'{1,3})', leaf.value)
451 if match is None:
452 message = 'invalid syntax'
453 if (
454 self.version >= (3, 9)
455 and leaf.value in _get_token_collection(
456 self.version
457 ).always_break_tokens
458 ):
459 message = "f-string: " + message
460 else:
461 if len(match.group(1)) == 1:
462 message = 'EOL while scanning string literal'
463 else:
464 message = 'EOF while scanning triple-quoted string literal'
465 self._add_syntax_error(leaf, message)
466 return ''
467 elif leaf.value == ':':
468 parent = leaf.parent
469 if parent.type in ('classdef', 'funcdef'):
470 self.context = self.context.add_context(parent)
472 # The rest is rule based.
473 return super().visit_leaf(leaf)
475 def _add_indentation_error(self, spacing, message):
476 self.add_issue(spacing, 903, "IndentationError: " + message)
478 def _add_syntax_error(self, node, message):
479 self.add_issue(node, 901, "SyntaxError: " + message)
481 def add_issue(self, node, code, message):
482 # Overwrite the default behavior.
483 # Check if the issues are on the same line.
484 line = node.start_pos[0]
485 args = (code, message, node)
486 self._error_dict.setdefault(line, args)
488 def finalize(self):
489 self.context.finalize()
491 for code, message, node in self._error_dict.values():
492 self.issues.append(Issue(node, code, message))
495class IndentationRule(Rule):
496 code = 903
498 def _get_message(self, message, node):
499 message = super()._get_message(message, node)
500 return "IndentationError: " + message
503@ErrorFinder.register_rule(type='error_node')
504class _ExpectIndentedBlock(IndentationRule):
505 message = 'expected an indented block'
507 def get_node(self, node):
508 leaf = node.get_next_leaf()
509 return list(leaf._split_prefix())[-1]
511 def is_issue(self, node):
512 # This is the beginning of a suite that is not indented.
513 return node.children[-1].type == 'newline'
516class ErrorFinderConfig(NormalizerConfig):
517 normalizer_class = ErrorFinder
520class SyntaxRule(Rule):
521 code = 901
523 def _get_message(self, message, node):
524 message = super()._get_message(message, node)
525 if (
526 "f-string" not in message
527 and _any_fstring_error(self._normalizer.version, node)
528 ):
529 message = "f-string: " + message
530 return "SyntaxError: " + message
533@ErrorFinder.register_rule(type='error_node')
534class _InvalidSyntaxRule(SyntaxRule):
535 message = "invalid syntax"
536 fstring_message = "f-string: invalid syntax"
538 def get_node(self, node):
539 return node.get_next_leaf()
541 def is_issue(self, node):
542 error = node.get_next_leaf().type != 'error_leaf'
543 if (
544 error
545 and _any_fstring_error(self._normalizer.version, node)
546 ):
547 self.add_issue(node, message=self.fstring_message)
548 else:
549 # Error leafs will be added later as an error.
550 return error
553@ErrorFinder.register_rule(value='await')
554class _AwaitOutsideAsync(SyntaxRule):
555 message = "'await' outside async function"
557 def is_issue(self, leaf):
558 return not self._normalizer.context.is_async_funcdef()
560 def get_error_node(self, node):
561 # Return the whole await statement.
562 return node.parent
565@ErrorFinder.register_rule(value='break')
566class _BreakOutsideLoop(SyntaxRule):
567 message = "'break' outside loop"
569 def is_issue(self, leaf):
570 in_loop = False
571 for block in self._normalizer.context.blocks:
572 if block.type in ('for_stmt', 'while_stmt'):
573 in_loop = True
574 return not in_loop
577@ErrorFinder.register_rule(value='continue')
578class _ContinueChecks(SyntaxRule):
579 message = "'continue' not properly in loop"
580 message_in_finally = "'continue' not supported inside 'finally' clause"
582 def is_issue(self, leaf):
583 in_loop = False
584 for block in self._normalizer.context.blocks:
585 if block.type in ('for_stmt', 'while_stmt'):
586 in_loop = True
587 if block.type == 'try_stmt':
588 last_block = block.children[-3]
589 if (
590 last_block == "finally"
591 and leaf.start_pos > last_block.start_pos
592 and self._normalizer.version < (3, 8)
593 ):
594 self.add_issue(leaf, message=self.message_in_finally)
595 return False # Error already added
596 if not in_loop:
597 return True
600@ErrorFinder.register_rule(value='from')
601class _YieldFromCheck(SyntaxRule):
602 message = "'yield from' inside async function"
604 def get_node(self, leaf):
605 return leaf.parent.parent # This is the actual yield statement.
607 def is_issue(self, leaf):
608 return leaf.parent.type == 'yield_arg' \
609 and self._normalizer.context.is_async_funcdef()
612@ErrorFinder.register_rule(type='name')
613class _NameChecks(SyntaxRule):
614 message = 'cannot assign to __debug__'
615 message_none = 'cannot assign to None'
617 def is_issue(self, leaf):
618 self._normalizer.context.add_name(leaf)
620 if leaf.value == '__debug__' and leaf.is_definition():
621 return True
624@ErrorFinder.register_rule(type='string')
625class _StringChecks(SyntaxRule):
626 if sys.version_info < (3, 10):
627 message = "bytes can only contain ASCII literal characters."
628 else:
629 message = "bytes can only contain ASCII literal characters"
631 def is_issue(self, leaf):
632 string_prefix = leaf.string_prefix.lower()
633 if 'b' in string_prefix \
634 and any(c for c in leaf.value if ord(c) > 127):
635 # b'ä'
636 return True
638 if 'r' not in string_prefix:
639 # Raw strings don't need to be checked if they have proper
640 # escaping.
642 payload = leaf._get_payload()
643 if 'b' in string_prefix:
644 payload = payload.encode('utf-8')
645 func = codecs.escape_decode
646 else:
647 func = codecs.unicode_escape_decode
649 try:
650 with warnings.catch_warnings():
651 # The warnings from parsing strings are not relevant.
652 warnings.filterwarnings('ignore')
653 func(payload)
654 except UnicodeDecodeError as e:
655 self.add_issue(leaf, message='(unicode error) ' + str(e))
656 except ValueError as e:
657 self.add_issue(leaf, message='(value error) ' + str(e))
660@ErrorFinder.register_rule(value='*')
661class _StarCheck(SyntaxRule):
662 if sys.version_info[:2] < (3, 15):
663 message = "named arguments must follow bare *"
664 else:
665 message = "named parameters must follow bare *"
667 def is_issue(self, leaf):
668 params = leaf.parent
669 if params.type == 'parameters' and params:
670 after = params.children[params.children.index(leaf) + 1:]
671 after = [child for child in after
672 if child not in (',', ')') and not child.star_count]
673 return len(after) == 0
676@ErrorFinder.register_rule(value='**')
677class _StarStarCheck(SyntaxRule):
678 # e.g. {**{} for a in [1]}
679 # TODO this should probably get a better end_pos including
680 # the next sibling of leaf.
681 message = "dict unpacking cannot be used in dict comprehension"
683 def is_issue(self, leaf):
684 if leaf.parent.type == 'dictorsetmaker':
685 comp_for = leaf.get_next_sibling().get_next_sibling()
686 return comp_for is not None and comp_for.type in _COMP_FOR_TYPES
689@ErrorFinder.register_rule(value='yield')
690@ErrorFinder.register_rule(value='return')
691class _ReturnAndYieldChecks(SyntaxRule):
692 message = "'return' with value in async generator"
693 message_async_yield = "'yield' inside async function"
695 def get_node(self, leaf):
696 return leaf.parent
698 def is_issue(self, leaf):
699 if self._normalizer.context.node.type != 'funcdef':
700 self.add_issue(self.get_node(leaf), message="'%s' outside function" % leaf.value)
701 elif self._normalizer.context.is_async_funcdef() \
702 and any(self._normalizer.context.node.iter_yield_exprs()):
703 if leaf.value == 'return' and leaf.parent.type == 'return_stmt':
704 return True
707@ErrorFinder.register_rule(type='strings')
708class _BytesAndStringMix(SyntaxRule):
709 # e.g. 's' b''
710 message = "cannot mix bytes and nonbytes literals"
712 def _is_bytes_literal(self, string):
713 if string.type == 'fstring':
714 return False
715 return 'b' in string.string_prefix.lower()
717 def is_issue(self, node):
718 first = node.children[0]
719 first_is_bytes = self._is_bytes_literal(first)
720 for string in node.children[1:]:
721 if first_is_bytes != self._is_bytes_literal(string):
722 return True
725@ErrorFinder.register_rule(type='import_as_names')
726class _TrailingImportComma(SyntaxRule):
727 # e.g. from foo import a,
728 message = "trailing comma not allowed without surrounding parentheses"
730 def is_issue(self, node):
731 if node.children[-1] == ',' and node.parent.children[-1] != ')':
732 return True
735@ErrorFinder.register_rule(type='import_from')
736class _ImportStarInFunction(SyntaxRule):
737 message = "import * only allowed at module level"
739 def is_issue(self, node):
740 return node.is_star_import() and self._normalizer.context.parent_context is not None
743@ErrorFinder.register_rule(type='import_from')
744class _FutureImportRule(SyntaxRule):
745 message = "from __future__ imports must occur at the beginning of the file"
747 def is_issue(self, node):
748 if _is_future_import(node):
749 if not _is_future_import_first(node):
750 return True
752 for from_name, future_name in node.get_paths():
753 name = future_name.value
754 allowed_futures = list(ALLOWED_FUTURES)
755 if self._normalizer.version >= (3, 7):
756 allowed_futures.append('annotations')
757 if name == 'braces':
758 self.add_issue(node, message="not a chance")
759 elif name == 'barry_as_FLUFL':
760 m = "Seriously I'm not implementing this :) ~ Dave"
761 self.add_issue(node, message=m)
762 elif name not in allowed_futures:
763 message = "future feature %s is not defined" % name
764 self.add_issue(node, message=message)
767@ErrorFinder.register_rule(type='star_expr')
768class _StarExprRule(SyntaxRule):
769 message_iterable_unpacking = "iterable unpacking cannot be used in comprehension"
771 def is_issue(self, node):
772 def check_delete_starred(node):
773 while node.parent is not None:
774 node = node.parent
775 if node.type == 'del_stmt':
776 return True
777 if node.type not in (*_STAR_EXPR_PARENTS, 'atom'):
778 return False
779 return False
781 if self._normalizer.version >= (3, 9):
782 ancestor = node.parent
783 else:
784 ancestor = _skip_parens_bottom_up(node)
785 # starred expression not in tuple/list/set
786 if ancestor.type not in (*_STAR_EXPR_PARENTS, 'dictorsetmaker') \
787 and not (ancestor.type == 'atom' and ancestor.children[0] != '('):
788 self.add_issue(node, message="can't use starred expression here")
789 return
791 if check_delete_starred(node):
792 if self._normalizer.version >= (3, 9):
793 self.add_issue(node, message="cannot delete starred")
794 else:
795 self.add_issue(node, message="can't use starred expression here")
796 return
798 if node.parent.type == 'testlist_comp':
799 # [*[] for a in [1]]
800 if node.parent.children[1].type in _COMP_FOR_TYPES:
801 self.add_issue(node, message=self.message_iterable_unpacking)
804@ErrorFinder.register_rule(types=_STAR_EXPR_PARENTS)
805class _StarExprParentRule(SyntaxRule):
806 def is_issue(self, node):
807 def is_definition(node, ancestor):
808 if ancestor is None:
809 return False
811 type_ = ancestor.type
812 if type_ == 'trailer':
813 return False
815 if type_ == 'expr_stmt':
816 return node.start_pos < ancestor.children[-1].start_pos
818 return is_definition(node, ancestor.parent)
820 if is_definition(node, node.parent):
821 args = [c for c in node.children if c != ',']
822 starred = [c for c in args if c.type == 'star_expr']
823 if len(starred) > 1:
824 if self._normalizer.version < (3, 9):
825 message = "two starred expressions in assignment"
826 else:
827 message = "multiple starred expressions in assignment"
828 self.add_issue(starred[1], message=message)
829 elif starred:
830 count = args.index(starred[0])
831 if count >= 256:
832 message = "too many expressions in star-unpacking assignment"
833 self.add_issue(starred[0], message=message)
836@ErrorFinder.register_rule(type='annassign')
837class _AnnotatorRule(SyntaxRule):
838 # True: int
839 # {}: float
840 message = "illegal target for annotation"
842 def get_node(self, node):
843 return node.parent
845 def is_issue(self, node):
846 type_ = None
847 lhs = node.parent.children[0]
848 lhs = _remove_parens(lhs)
849 try:
850 children = lhs.children
851 except AttributeError:
852 pass
853 else:
854 if ',' in children or lhs.type == 'atom' and children[0] == '(':
855 type_ = 'tuple'
856 elif lhs.type == 'atom' and children[0] == '[':
857 type_ = 'list'
858 trailer = children[-1]
860 if type_ is None:
861 if not (lhs.type == 'name'
862 # subscript/attributes are allowed
863 or lhs.type in ('atom_expr', 'power')
864 and trailer.type == 'trailer'
865 and trailer.children[0] != '('):
866 return True
867 else:
868 # x, y: str
869 message = "only single target (not %s) can be annotated"
870 self.add_issue(lhs.parent, message=message % type_)
873@ErrorFinder.register_rule(type='argument')
874class _ArgumentRule(SyntaxRule):
875 def is_issue(self, node):
876 first = node.children[0]
877 if self._normalizer.version < (3, 8):
878 # a((b)=c) is valid in <3.8
879 first = _remove_parens(first)
880 if node.children[1] == '=' and first.type != 'name':
881 if first.type == 'lambdef':
882 # f(lambda: 1=1)
883 if self._normalizer.version < (3, 8):
884 message = "lambda cannot contain assignment"
885 else:
886 message = 'expression cannot contain assignment, perhaps you meant "=="?'
887 else:
888 # f(+x=1)
889 if self._normalizer.version < (3, 8):
890 message = "keyword can't be an expression"
891 else:
892 message = 'expression cannot contain assignment, perhaps you meant "=="?'
893 self.add_issue(first, message=message)
895 if _is_argument_comprehension(node) and node.parent.type == 'classdef':
896 self.add_issue(node, message='invalid syntax')
899@ErrorFinder.register_rule(type='nonlocal_stmt')
900class _NonlocalModuleLevelRule(SyntaxRule):
901 message = "nonlocal declaration not allowed at module level"
903 def is_issue(self, node):
904 return self._normalizer.context.parent_context is None
907@ErrorFinder.register_rule(type='arglist')
908class _ArglistRule(SyntaxRule):
909 @property
910 def message(self):
911 if self._normalizer.version < (3, 7):
912 return "Generator expression must be parenthesized if not sole argument"
913 else:
914 return "Generator expression must be parenthesized"
916 def is_issue(self, node):
917 arg_set = set()
918 kw_only = False
919 kw_unpacking_only = False
920 for argument in node.children:
921 if argument == ',':
922 continue
924 if argument.type == 'argument':
925 first = argument.children[0]
926 if _is_argument_comprehension(argument) and len(node.children) >= 2:
927 # a(a, b for b in c)
928 return True
930 if first in ('*', '**'):
931 if first == '*':
932 if kw_unpacking_only:
933 # foo(**kwargs, *args)
934 message = "iterable argument unpacking " \
935 "follows keyword argument unpacking"
936 self.add_issue(argument, message=message)
937 else:
938 kw_unpacking_only = True
939 elif argument.children[1] == ':=':
940 # f(a := 1) is a positional argument, not a keyword one.
941 pass
942 else: # Is a keyword argument.
943 kw_only = True
944 if first.type == 'name':
945 if first.value in arg_set:
946 # f(x=1, x=2)
947 message = "keyword argument repeated"
948 if self._normalizer.version >= (3, 9):
949 message += ": {}".format(first.value)
950 self.add_issue(first, message=message)
951 else:
952 arg_set.add(first.value)
953 else:
954 if kw_unpacking_only:
955 # f(**x, y)
956 message = "positional argument follows keyword argument unpacking"
957 self.add_issue(argument, message=message)
958 elif kw_only:
959 # f(x=2, y)
960 message = "positional argument follows keyword argument"
961 self.add_issue(argument, message=message)
964@ErrorFinder.register_rule(type='parameters')
965@ErrorFinder.register_rule(type='lambdef')
966class _ParameterRule(SyntaxRule):
967 # def f(x=3, y): pass
968 message = "non-default argument follows default argument"
970 def is_issue(self, node):
971 param_names = set()
972 default_only = False
973 star_seen = False
974 for p in _iter_params(node):
975 if p.type == 'operator':
976 if p.value == '*':
977 star_seen = True
978 default_only = False
979 continue
981 if p.name.value in param_names:
982 if sys.version_info[:2] < (3, 15):
983 message = "duplicate argument '%s' in function definition"
984 else:
985 message = "duplicate parameter '%s' in function definition"
986 self.add_issue(p.name, message=message % p.name.value)
987 param_names.add(p.name.value)
989 if not star_seen:
990 if p.default is None and not p.star_count:
991 if default_only:
992 return True
993 elif p.star_count:
994 star_seen = True
995 default_only = False
996 else:
997 default_only = True
1000@ErrorFinder.register_rule(type='try_stmt')
1001class _TryStmtRule(SyntaxRule):
1002 message = "default 'except:' must be last"
1004 def is_issue(self, try_stmt):
1005 default_except = None
1006 for except_clause in try_stmt.children[3::3]:
1007 if except_clause in ('else', 'finally'):
1008 break
1009 if except_clause == 'except':
1010 default_except = except_clause
1011 elif default_except is not None:
1012 self.add_issue(default_except, message=self.message)
1015@ErrorFinder.register_rule(type='fstring')
1016class _FStringRule(SyntaxRule):
1017 _fstring_grammar = None
1018 message_expr = "f-string expression part cannot include a backslash"
1019 message_nested = "f-string: expressions nested too deeply"
1020 message_conversion = "f-string: invalid conversion character: expected 's', 'r', or 'a'"
1022 def _check_format_spec(self, format_spec, depth):
1023 self._check_fstring_contents(format_spec.children[1:], depth)
1025 def _check_fstring_expr(self, fstring_expr, depth):
1026 if depth >= 2:
1027 self.add_issue(fstring_expr, message=self.message_nested)
1029 expr = fstring_expr.children[1]
1030 if '\\' in expr.get_code():
1031 self.add_issue(expr, message=self.message_expr)
1033 children_2 = fstring_expr.children[2]
1034 if children_2.type == 'operator' and children_2.value == '=':
1035 conversion = fstring_expr.children[3]
1036 else:
1037 conversion = children_2
1038 if conversion.type == 'fstring_conversion':
1039 name = conversion.children[1]
1040 if name.value not in ('s', 'r', 'a'):
1041 self.add_issue(name, message=self.message_conversion)
1043 format_spec = fstring_expr.children[-2]
1044 if format_spec.type == 'fstring_format_spec':
1045 self._check_format_spec(format_spec, depth + 1)
1047 def is_issue(self, fstring):
1048 self._check_fstring_contents(fstring.children[1:-1])
1050 def _check_fstring_contents(self, children, depth=0):
1051 for fstring_content in children:
1052 if fstring_content.type == 'fstring_expr':
1053 self._check_fstring_expr(fstring_content, depth)
1056class _CheckAssignmentRule(SyntaxRule):
1057 def _check_assignment(self, node, is_deletion=False, is_namedexpr=False, is_aug_assign=False):
1058 error = None
1059 type_ = node.type
1060 if type_ == 'lambdef':
1061 error = 'lambda'
1062 elif type_ == 'atom':
1063 first, second = node.children[:2]
1064 error = _get_comprehension_type(node)
1065 if error is None:
1066 if second.type == 'dictorsetmaker':
1067 if self._normalizer.version < (3, 8):
1068 error = 'literal'
1069 else:
1070 if second.children[1] == ':':
1071 if self._normalizer.version < (3, 10):
1072 error = 'dict display'
1073 else:
1074 error = 'dict literal'
1075 else:
1076 error = 'set display'
1077 elif first == "{" and second == "}":
1078 if self._normalizer.version < (3, 8):
1079 error = 'literal'
1080 else:
1081 if self._normalizer.version < (3, 10):
1082 error = "dict display"
1083 else:
1084 error = "dict literal"
1085 elif first == "{" and len(node.children) > 2:
1086 if self._normalizer.version < (3, 8):
1087 error = 'literal'
1088 else:
1089 error = "set display"
1090 elif first in ('(', '['):
1091 if second.type == 'yield_expr':
1092 error = 'yield expression'
1093 elif second.type == 'testlist_comp':
1094 # ([a, b] := [1, 2])
1095 # ((a, b) := [1, 2])
1096 if is_namedexpr:
1097 if first == '(':
1098 error = 'tuple'
1099 elif first == '[':
1100 error = 'list'
1102 # This is not a comprehension, they were handled
1103 # further above.
1104 for child in second.children[::2]:
1105 self._check_assignment(child, is_deletion, is_namedexpr, is_aug_assign)
1106 else: # Everything handled, must be useless brackets.
1107 self._check_assignment(second, is_deletion, is_namedexpr, is_aug_assign)
1108 elif type_ == 'keyword':
1109 if node.value == "yield":
1110 error = "yield expression"
1111 elif self._normalizer.version < (3, 8):
1112 error = 'keyword'
1113 else:
1114 error = str(node.value)
1115 elif type_ == 'operator':
1116 if node.value == '...':
1117 if self._normalizer.version < (3, 10):
1118 error = 'Ellipsis'
1119 else:
1120 error = 'ellipsis'
1121 elif type_ == 'comparison':
1122 error = 'comparison'
1123 elif type_ in ('string', 'number', 'strings'):
1124 error = 'literal'
1125 elif type_ == 'yield_expr':
1126 # This one seems to be a slightly different warning in Python.
1127 message = 'assignment to yield expression not possible'
1128 self.add_issue(node, message=message)
1129 elif type_ == 'test':
1130 error = 'conditional expression'
1131 elif type_ in ('atom_expr', 'power'):
1132 if node.children[0] == 'await':
1133 error = 'await expression'
1134 elif node.children[-2] == '**':
1135 if self._normalizer.version < (3, 10):
1136 error = 'operator'
1137 else:
1138 error = 'expression'
1139 else:
1140 # Has a trailer
1141 trailer = node.children[-1]
1142 assert trailer.type == 'trailer'
1143 if trailer.children[0] == '(':
1144 error = 'function call'
1145 elif is_namedexpr and trailer.children[0] == '[':
1146 error = 'subscript'
1147 elif is_namedexpr and trailer.children[0] == '.':
1148 error = 'attribute'
1149 elif type_ == "fstring":
1150 if self._normalizer.version < (3, 8):
1151 error = 'literal'
1152 else:
1153 error = "f-string expression"
1154 elif type_ in ('testlist_star_expr', 'exprlist', 'testlist'):
1155 for child in node.children[::2]:
1156 self._check_assignment(child, is_deletion, is_namedexpr, is_aug_assign)
1157 elif ('expr' in type_ and type_ != 'star_expr' # is a substring
1158 or '_test' in type_
1159 or type_ in ('term', 'factor')):
1160 if self._normalizer.version < (3, 10):
1161 error = 'operator'
1162 else:
1163 error = 'expression'
1164 elif type_ == "star_expr":
1165 if is_deletion:
1166 if self._normalizer.version >= (3, 9):
1167 error = "starred"
1168 else:
1169 self.add_issue(node, message="can't use starred expression here")
1170 else:
1171 if self._normalizer.version >= (3, 9):
1172 ancestor = node.parent
1173 else:
1174 ancestor = _skip_parens_bottom_up(node)
1175 if ancestor.type not in _STAR_EXPR_PARENTS and not is_aug_assign \
1176 and not (ancestor.type == 'atom' and ancestor.children[0] == '['):
1177 message = "starred assignment target must be in a list or tuple"
1178 self.add_issue(node, message=message)
1180 self._check_assignment(node.children[1])
1182 if error is not None:
1183 if is_namedexpr:
1184 message = 'cannot use assignment expressions with %s' % error
1185 else:
1186 cannot = "can't" if self._normalizer.version < (3, 8) else "cannot"
1187 message = ' '.join([cannot, "delete" if is_deletion else "assign to", error])
1188 self.add_issue(node, message=message)
1191@ErrorFinder.register_rule(type='sync_comp_for')
1192class _CompForRule(_CheckAssignmentRule):
1193 message = "asynchronous comprehension outside of an asynchronous function"
1195 def is_issue(self, node):
1196 expr_list = node.children[1]
1197 if expr_list.type != 'expr_list': # Already handled.
1198 self._check_assignment(expr_list)
1200 return node.parent.children[0] == 'async' \
1201 and not self._normalizer.context.is_async_funcdef()
1204@ErrorFinder.register_rule(type='expr_stmt')
1205class _ExprStmtRule(_CheckAssignmentRule):
1206 message = "illegal expression for augmented assignment"
1207 extended_message = "'{target}' is an " + message
1209 def is_issue(self, node):
1210 augassign = node.children[1]
1211 is_aug_assign = augassign != '=' and augassign.type != 'annassign'
1213 if self._normalizer.version <= (3, 8) or not is_aug_assign:
1214 for before_equal in node.children[:-2:2]:
1215 self._check_assignment(before_equal, is_aug_assign=is_aug_assign)
1217 if is_aug_assign:
1218 target = _remove_parens(node.children[0])
1219 # a, a[b], a.b
1221 if target.type == "name" or (
1222 target.type in ("atom_expr", "power")
1223 and target.children[1].type == "trailer"
1224 and target.children[-1].children[0] != "("
1225 ):
1226 return False
1228 if self._normalizer.version <= (3, 8):
1229 return True
1230 else:
1231 self.add_issue(
1232 node,
1233 message=self.extended_message.format(
1234 target=_get_rhs_name(node.children[0], self._normalizer.version)
1235 ),
1236 )
1239@ErrorFinder.register_rule(type='with_item')
1240class _WithItemRule(_CheckAssignmentRule):
1241 def is_issue(self, with_item):
1242 self._check_assignment(with_item.children[2])
1245@ErrorFinder.register_rule(type='del_stmt')
1246class _DelStmtRule(_CheckAssignmentRule):
1247 def is_issue(self, del_stmt):
1248 child = del_stmt.children[1]
1250 if child.type != 'expr_list': # Already handled.
1251 self._check_assignment(child, is_deletion=True)
1254@ErrorFinder.register_rule(type='expr_list')
1255class _ExprListRule(_CheckAssignmentRule):
1256 def is_issue(self, expr_list):
1257 for expr in expr_list.children[::2]:
1258 self._check_assignment(expr)
1261@ErrorFinder.register_rule(type='for_stmt')
1262class _ForStmtRule(_CheckAssignmentRule):
1263 def is_issue(self, for_stmt):
1264 # Some of the nodes here are already used, so no else if
1265 expr_list = for_stmt.children[1]
1266 if expr_list.type != 'expr_list': # Already handled.
1267 self._check_assignment(expr_list)
1270@ErrorFinder.register_rule(type='namedexpr_test')
1271class _NamedExprRule(_CheckAssignmentRule):
1272 # namedexpr_test: test [':=' test]
1274 def is_issue(self, namedexpr_test):
1275 # assigned name
1276 first = namedexpr_test.children[0]
1278 def search_namedexpr_in_comp_for(node):
1279 while True:
1280 parent = node.parent
1281 if parent is None:
1282 return parent
1283 if parent.type == 'sync_comp_for' and parent.children[3] == node:
1284 return parent
1285 node = parent
1287 if search_namedexpr_in_comp_for(namedexpr_test):
1288 # [i+1 for i in (i := range(5))]
1289 # [i+1 for i in (j := range(5))]
1290 # [i+1 for i in (lambda: (j := range(5)))()]
1291 message = 'assignment expression cannot be used in a comprehension iterable expression'
1292 self.add_issue(namedexpr_test, message=message)
1294 # defined names
1295 exprlist = list()
1297 def process_comp_for(comp_for):
1298 if comp_for.type == 'sync_comp_for':
1299 comp = comp_for
1300 elif comp_for.type == 'comp_for':
1301 comp = comp_for.children[1]
1302 exprlist.extend(_get_for_stmt_definition_exprs(comp))
1304 def search_all_comp_ancestors(node):
1305 has_ancestors = False
1306 while True:
1307 node = node.search_ancestor('testlist_comp', 'dictorsetmaker')
1308 if node is None:
1309 break
1310 for child in node.children:
1311 if child.type in _COMP_FOR_TYPES:
1312 process_comp_for(child)
1313 has_ancestors = True
1314 break
1315 return has_ancestors
1317 # check assignment expressions in comprehensions
1318 search_all = search_all_comp_ancestors(namedexpr_test)
1319 if search_all:
1320 if self._normalizer.context.node.type == 'classdef':
1321 message = 'assignment expression within a comprehension ' \
1322 'cannot be used in a class body'
1323 self.add_issue(namedexpr_test, message=message)
1325 namelist = [expr.value for expr in exprlist if expr.type == 'name']
1326 if first.type == 'name' and first.value in namelist:
1327 # [i := 0 for i, j in range(5)]
1328 # [[(i := i) for j in range(5)] for i in range(5)]
1329 # [i for i, j in range(5) if True or (i := 1)]
1330 # [False and (i := 0) for i, j in range(5)]
1331 message = 'assignment expression cannot rebind ' \
1332 'comprehension iteration variable %r' % first.value
1333 self.add_issue(namedexpr_test, message=message)
1335 self._check_assignment(first, is_namedexpr=True)