Skip to content

Commit 7f751be

Browse files
asmeurerbitglue
authored andcommitted
Check for non-ast SyntaxErrors
This includes return and yield outside of a function and break and continue outside of a loop. Fixes lp 1293654. The problem is that these SyntaxErrors are not encoded in the ast grammar, so they are not detected when just compiling to ast. You must compile down to bytecode to catch them. The advantage here is that we can still check for other kinds of errors in this case, because the ast is still valid.
1 parent 93aa3c4 commit 7f751be

File tree

3 files changed

+772
-10
lines changed

3 files changed

+772
-10
lines changed

pyflakes/checker.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -658,11 +658,11 @@ def ignore(self, node):
658658
ASYNCWITH = ASYNCWITHITEM = RAISE = TRYFINALLY = ASSERT = EXEC = \
659659
EXPR = ASSIGN = handleChildren
660660

661-
CONTINUE = BREAK = PASS = ignore
661+
PASS = ignore
662662

663663
# "expr" type nodes
664664
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = \
665-
COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \
665+
COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = \
666666
STARRED = NAMECONSTANT = handleChildren
667667

668668
NUM = STR = BYTES = ELLIPSIS = ignore
@@ -748,8 +748,33 @@ def NAME(self, node):
748748
# arguments, but these aren't dispatched through here
749749
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
750750

751+
def CONTINUE(self, node):
752+
# Walk the tree up until we see a loop (OK), a function or class
753+
# definition (not OK), for 'continue', a finally block (not OK), or
754+
# the top module scope (not OK)
755+
n = node
756+
while hasattr(n, 'parent'):
757+
n, n_child = n.parent, n
758+
if isinstance(n, (ast.While, ast.For)):
759+
# Doesn't apply unless it's in the loop itself
760+
if n_child not in n.orelse:
761+
return
762+
if isinstance(n, (ast.FunctionDef, ast.ClassDef)):
763+
break
764+
# Handle Try/TryFinally difference in Python < and >= 3.3
765+
if hasattr(n, 'finalbody') and isinstance(node, ast.Continue):
766+
if n_child in n.finalbody:
767+
self.report(messages.ContinueInFinally, node)
768+
return
769+
if isinstance(node, ast.Continue):
770+
self.report(messages.ContinueOutsideLoop, node)
771+
else: # ast.Break
772+
self.report(messages.BreakOutsideLoop, node)
773+
774+
BREAK = CONTINUE
775+
751776
def RETURN(self, node):
752-
if isinstance(self.scope, ClassScope):
777+
if isinstance(self.scope, (ClassScope, ModuleScope)):
753778
self.report(messages.ReturnOutsideFunction, node)
754779
return
755780

@@ -762,6 +787,10 @@ def RETURN(self, node):
762787
self.handleNode(node.value, node)
763788

764789
def YIELD(self, node):
790+
if isinstance(self.scope, (ClassScope, ModuleScope)):
791+
self.report(messages.YieldOutsideFunction, node)
792+
return
793+
765794
self.scope.isGenerator = True
766795
self.handleNode(node.value, node)
767796

@@ -886,6 +915,31 @@ def AUGASSIGN(self, node):
886915
self.handleNode(node.value, node)
887916
self.handleNode(node.target, node)
888917

918+
def TUPLE(self, node):
919+
if not PY2 and isinstance(node.ctx, ast.Store):
920+
# Python 3 advanced tuple unpacking: a, *b, c = d.
921+
# Only one starred expression is allowed, and no more than 1<<8
922+
# assignments are allowed before a stared expression. There is
923+
# also a limit of 1<<24 expressions after the starred expression,
924+
# which is impossible to test due to memory restrictions, but we
925+
# add it here anyway
926+
has_starred = False
927+
star_loc = -1
928+
for i, n in enumerate(node.elts):
929+
if isinstance(n, ast.Starred):
930+
if has_starred:
931+
self.report(messages.TwoStarredExpressions, node)
932+
# The SyntaxError doesn't distinguish two from more
933+
# than two.
934+
break
935+
has_starred = True
936+
star_loc = i
937+
if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24:
938+
self.report(messages.TooManyExpressionsInStarredAssignment, node)
939+
self.handleChildren(node)
940+
941+
LIST = TUPLE
942+
889943
def IMPORT(self, node):
890944
for alias in node.names:
891945
name = alias.asname or alias.name
@@ -914,12 +968,15 @@ def IMPORTFROM(self, node):
914968
def TRY(self, node):
915969
handler_names = []
916970
# List the exception handlers
917-
for handler in node.handlers:
971+
for i, handler in enumerate(node.handlers):
918972
if isinstance(handler.type, ast.Tuple):
919973
for exc_type in handler.type.elts:
920974
handler_names.append(getNodeName(exc_type))
921975
elif handler.type:
922976
handler_names.append(getNodeName(handler.type))
977+
978+
if handler.type is None and i < len(node.handlers) - 1:
979+
self.report(messages.DefaultExceptNotLast, handler)
923980
# Memorize the except handlers and process the body
924981
self.exceptHandlers.append(handler_names)
925982
for child in node.body:

pyflakes/messages.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ def __init__(self, filename, loc, name):
101101

102102

103103
class LateFutureImport(Message):
104-
message = 'future import(s) %r after other statements'
104+
message = 'from __future__ imports must occur at the beginning of the file'
105105

106106
def __init__(self, filename, loc, names):
107107
Message.__init__(self, filename, loc)
108-
self.message_args = (names,)
108+
self.message_args = ()
109109

110110

111111
class UnusedVariable(Message):
@@ -132,3 +132,54 @@ class ReturnOutsideFunction(Message):
132132
Indicates a return statement outside of a function/method.
133133
"""
134134
message = '\'return\' outside function'
135+
136+
137+
class YieldOutsideFunction(Message):
138+
"""
139+
Indicates a yield or yield from statement outside of a function/method.
140+
"""
141+
message = '\'yield\' outside function'
142+
143+
144+
# For whatever reason, Python gives different error messages for these two. We
145+
# match the Python error message exactly.
146+
class ContinueOutsideLoop(Message):
147+
"""
148+
Indicates a continue statement outside of a while or for loop.
149+
"""
150+
message = '\'continue\' not properly in loop'
151+
152+
153+
class BreakOutsideLoop(Message):
154+
"""
155+
Indicates a break statement outside of a while or for loop.
156+
"""
157+
message = '\'break\' outside loop'
158+
159+
160+
class ContinueInFinally(Message):
161+
"""
162+
Indicates a continue statement in a finally block in a while or for loop.
163+
"""
164+
message = '\'continue\' not supported inside \'finally\' clause'
165+
166+
167+
class DefaultExceptNotLast(Message):
168+
"""
169+
Indicates an except: block as not the last exception handler.
170+
"""
171+
message = 'default \'except:\' must be last'
172+
173+
174+
class TwoStarredExpressions(Message):
175+
"""
176+
Two or more starred expressions in an assignment (a, *b, *c = d).
177+
"""
178+
message = 'two starred expressions in assignment'
179+
180+
181+
class TooManyExpressionsInStarredAssignment(Message):
182+
"""
183+
Too many expressions in an assignment with star-unpacking
184+
"""
185+
message = 'too many expressions in star-unpacking assignment'

0 commit comments

Comments
 (0)