Skip to content

Commit

Permalink
pyflakes: python3.8+ (#752)
Browse files Browse the repository at this point in the history
  • Loading branch information
asottile committed Nov 27, 2022
1 parent 37f203e commit 98d4fa3
Show file tree
Hide file tree
Showing 10 changed files with 31 additions and 144 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Expand Up @@ -12,12 +12,12 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-beta - 3.11.999", "pypy-3.9"]
python-version: ["3.8", "3.9", "3.10", "3.11", "pypy-3.9"]
os: [ubuntu-latest]
# Include minimum py3 + maximum py3 + pypy3 on Windows
include:
- { os: "windows-latest" , python-version: "3.6" }
- { os: "windows-latest" , python-version: "3.10" }
- { os: "windows-latest" , python-version: "3.8" }
- { os: "windows-latest" , python-version: "3.11" }
- { os: "windows-latest" , python-version: "pypy-3.9" }

steps:
Expand Down
59 changes: 16 additions & 43 deletions pyflakes/checker.py
Expand Up @@ -18,7 +18,6 @@

from pyflakes import messages

PY38_PLUS = sys.version_info >= (3, 8)
PYPY = hasattr(sys, 'pypy_version_info')

builtin_vars = dir(builtins)
Expand All @@ -35,15 +34,12 @@ def getAlternatives(n):

FOR_TYPES = (ast.For, ast.AsyncFor)

if PY38_PLUS:
def _is_singleton(node): # type: (ast.AST) -> bool
return (
isinstance(node, ast.Constant) and
isinstance(node.value, (bool, type(Ellipsis), type(None)))
)
else:
def _is_singleton(node): # type: (ast.AST) -> bool
return isinstance(node, (ast.NameConstant, ast.Ellipsis))

def _is_singleton(node): # type: (ast.AST) -> bool
return (
isinstance(node, ast.Constant) and
isinstance(node.value, (bool, type(Ellipsis), type(None)))
)


def _is_tuple_constant(node): # type: (ast.AST) -> bool
Expand All @@ -53,16 +49,8 @@ def _is_tuple_constant(node): # type: (ast.AST) -> bool
)


if PY38_PLUS:
def _is_constant(node):
return isinstance(node, ast.Constant) or _is_tuple_constant(node)
else:
def _is_constant(node):
return (
isinstance(node, (ast.Str, ast.Num, ast.Bytes)) or
_is_singleton(node) or
_is_tuple_constant(node)
)
def _is_constant(node):
return isinstance(node, ast.Constant) or _is_tuple_constant(node)


def _is_const_non_singleton(node): # type: (ast.AST) -> bool
Expand Down Expand Up @@ -1176,7 +1164,7 @@ def handleNodeStore(self, node):
)
):
binding = ExportBinding(name, node._pyflakes_parent, self.scope)
elif PY38_PLUS and isinstance(parent_stmt, ast.NamedExpr):
elif isinstance(parent_stmt, ast.NamedExpr):
binding = NamedExprAssignment(name, node)
else:
binding = Assignment(name, node)
Expand Down Expand Up @@ -1252,13 +1240,7 @@ def getDocstring(self, node):
if not isinstance(node, ast.Str):
return (None, None)

if PYPY or PY38_PLUS:
doctest_lineno = node.lineno - 1
else:
# Computed incorrectly if the docstring has backslash
doctest_lineno = node.lineno - node.s.count('\n') - 1

return (node.s, doctest_lineno)
return (node.s, node.lineno - 1)

def handleNode(self, node, parent):
if node is None:
Expand Down Expand Up @@ -1729,12 +1711,9 @@ def STR(self, node):
else:
self.deferFunction(fn)

if PY38_PLUS:
def CONSTANT(self, node):
if isinstance(node.value, str):
return self.STR(node)
else:
NUM = BYTES = ELLIPSIS = CONSTANT = ignore
def CONSTANT(self, node):
if isinstance(node.value, str):
return self.STR(node)

# "slice" type nodes
SLICE = EXTSLICE = INDEX = handleChildren
Expand Down Expand Up @@ -1900,11 +1879,6 @@ def CONTINUE(self, node):
return
if isinstance(n, (ast.FunctionDef, ast.ClassDef)):
break
# Handle Try/TryFinally difference in Python < and >= 3.3
if hasattr(n, 'finalbody') and isinstance(node, ast.Continue):
if n_child in n.finalbody and not PY38_PLUS:
self.report(messages.ContinueInFinally, node)
return
if isinstance(node, ast.Continue):
self.report(messages.ContinueOutsideLoop, node)
else: # ast.Break
Expand Down Expand Up @@ -1952,10 +1926,9 @@ def LAMBDA(self, node):
args = []
annotations = []

if PY38_PLUS:
for arg in node.args.posonlyargs:
args.append(arg.arg)
annotations.append(arg.annotation)
for arg in node.args.posonlyargs:
args.append(arg.arg)
annotations.append(arg.annotation)
for arg in node.args.args + node.args.kwonlyargs:
args.append(arg.arg)
annotations.append(arg.annotation)
Expand Down
7 changes: 0 additions & 7 deletions pyflakes/messages.py
Expand Up @@ -198,13 +198,6 @@ class BreakOutsideLoop(Message):
message = '\'break\' outside loop'


class ContinueInFinally(Message):
"""
Indicates a continue statement in a finally block in a while or for loop.
"""
message = '\'continue\' not supported inside \'finally\' clause'


class DefaultExceptNotLast(Message):
"""
Indicates an except: block as not the last exception handler.
Expand Down
2 changes: 0 additions & 2 deletions pyflakes/reporter.py
Expand Up @@ -60,8 +60,6 @@ def syntaxError(self, filename, msg, lineno, offset, text):
lineno = max(lineno, 1)

if offset is not None:
if sys.version_info < (3, 8) and text is not None:
offset = offset - (len(text) - len(line)) + 1
# some versions of python emit an offset of -1 for certain encoding errors
offset = max(offset, 1)
self._stderr.write('%s:%d:%d: %s\n' %
Expand Down
29 changes: 7 additions & 22 deletions pyflakes/test/test_api.py
Expand Up @@ -233,9 +233,7 @@ def test_syntaxError(self):
"""
err = io.StringIO()
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3,
8 if sys.version_info >= (3, 8) else 7,
'bad line of source')
reporter.syntaxError('foo.py', 'a problem', 3, 8, 'bad line of source')
self.assertEqual(
("foo.py:3:8: a problem\n"
"bad line of source\n"
Expand Down Expand Up @@ -281,11 +279,10 @@ def test_multiLineSyntaxError(self):
reporter = Reporter(None, err)
reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7,
'\n'.join(lines))
column = 25 if sys.version_info >= (3, 8) else 7
self.assertEqual(
("foo.py:3:%d: a problem\n" % column +
("foo.py:3:25: a problem\n" +
lines[-1] + "\n" +
" " * (column - 1) + "^\n"),
" " * 24 + "^\n"),
err.getvalue())

def test_unexpectedError(self):
Expand Down Expand Up @@ -417,10 +414,8 @@ def evaluate(source):

if PYPY or sys.version_info >= (3, 10):
column = 12
elif sys.version_info >= (3, 8):
column = 8
else:
column = 11
column = 8
self.assertHasErrors(
sourcePath,
["""\
Expand Down Expand Up @@ -487,10 +482,8 @@ def foo(bar=baz, bax):
column = 18
elif sys.version_info >= (3, 9):
column = 21
elif sys.version_info >= (3, 8):
column = 9
else:
column = 8
column = 9
last_line = ' ' * (column - 1) + '^\n'
columnstr = '%d:' % column
self.assertHasErrors(
Expand All @@ -512,7 +505,7 @@ def test_nonKeywordAfterKeywordSyntaxError(self):
with self.makeTempFile(source) as sourcePath:
if sys.version_info >= (3, 9):
column = 17
elif not PYPY and sys.version_info >= (3, 8):
elif not PYPY:
column = 14
else:
column = 13
Expand Down Expand Up @@ -679,18 +672,10 @@ def test_stdinReportsErrors(self):
"max(1 for i in range(10), key=lambda x: x+1)",
" ^",
]
elif sys.version_info >= (3, 8):
else:
expected_error = [
"<stdin>:1:5: Generator expression must be parenthesized",
]
elif sys.version_info >= (3, 7):
expected_error = [
"<stdin>:1:4: Generator expression must be parenthesized",
]
elif sys.version_info >= (3, 6):
expected_error = [
"<stdin>:1:4: Generator expression must be parenthesized if not sole argument", # noqa: E501
]

self.assertEqual(errlines, expected_error)

Expand Down
13 changes: 3 additions & 10 deletions pyflakes/test/test_doctests.py
@@ -1,4 +1,3 @@
import sys
import textwrap

from pyflakes import messages as m
Expand Down Expand Up @@ -323,7 +322,7 @@ def doctest_stuff():
m.DoctestSyntaxError).messages
exc = exceptions[0]
self.assertEqual(exc.lineno, 4)
if not PYPY and sys.version_info >= (3, 8):
if not PYPY:
self.assertEqual(exc.col, 18)
else:
self.assertEqual(exc.col, 26)
Expand All @@ -339,10 +338,7 @@ def doctest_stuff():
self.assertEqual(exc.col, 16)
exc = exceptions[2]
self.assertEqual(exc.lineno, 6)
if PYPY or sys.version_info >= (3, 8):
self.assertEqual(exc.col, 13)
else:
self.assertEqual(exc.col, 18)
self.assertEqual(exc.col, 13)

def test_indentationErrorInDoctest(self):
exc = self.flakes('''
Expand All @@ -353,10 +349,7 @@ def doctest_stuff():
"""
''', m.DoctestSyntaxError).messages[0]
self.assertEqual(exc.lineno, 5)
if PYPY or sys.version_info >= (3, 8):
self.assertEqual(exc.col, 13)
else:
self.assertEqual(exc.col, 16)
self.assertEqual(exc.col, 13)

def test_offsetWithMultiLineArgs(self):
(exc1, exc2) = self.flakes(
Expand Down
46 changes: 0 additions & 46 deletions pyflakes/test/test_other.py
Expand Up @@ -445,36 +445,6 @@ def test_continueInsideLoop(self):
continue
''')

@skipIf(version_info > (3, 8), "Python <= 3.8 only")
def test_continueInFinally(self):
# 'continue' inside 'finally' is a special syntax error
# that is removed in 3.8
self.flakes('''
while True:
try:
pass
finally:
continue
''', m.ContinueInFinally)

self.flakes('''
while True:
try:
pass
finally:
if 1:
if 2:
continue
''', m.ContinueInFinally)

# Even when not in a loop, this is the error Python gives
self.flakes('''
try:
pass
finally:
continue
''', m.ContinueInFinally)

def test_breakOutsideLoop(self):
self.flakes('''
break
Expand Down Expand Up @@ -1716,7 +1686,6 @@ def test_f_string(self):
print(f'\x7b4*baz\N{RIGHT CURLY BRACKET}')
''')

@skipIf(version_info < (3, 8), 'new in Python 3.8')
def test_assign_expr(self):
"""Test PEP 572 assignment expressions are treated as usage / write."""
self.flakes('''
Expand All @@ -1725,15 +1694,13 @@ def test_assign_expr(self):
print(x)
''')

@skipIf(version_info < (3, 8), 'new in Python 3.8')
def test_assign_expr_generator_scope(self):
"""Test assignment expressions in generator expressions."""
self.flakes('''
if (any((y := x[0]) for x in [[True]])):
print(y)
''')

@skipIf(version_info < (3, 8), 'new in Python 3.8')
def test_assign_expr_nested(self):
"""Test assignment expressions in nested expressions."""
self.flakes('''
Expand Down Expand Up @@ -1972,19 +1939,6 @@ async def read_data(db):
return output
''', m.BreakOutsideLoop)

@skipIf(version_info > (3, 8), "Python <= 3.8 only")
def test_continueInAsyncForFinally(self):
self.flakes('''
async def read_data(db):
output = []
async for row in db.cursor():
try:
output.append(row)
finally:
continue
return output
''', m.ContinueInFinally)

def test_asyncWith(self):
self.flakes('''
async def commit(session, data):
Expand Down
3 changes: 0 additions & 3 deletions pyflakes/test/test_type_annotations.py
Expand Up @@ -379,7 +379,6 @@ class c: pass
async def func(c: c) -> None: pass
''')

@skipIf(version_info < (3, 7), 'new in Python 3.7')
def test_postponed_annotations(self):
self.flakes('''
from __future__ import annotations
Expand Down Expand Up @@ -434,7 +433,6 @@ def t(self) -> Y:
return Y
""", m.UndefinedName)

@skipIf(version_info < (3, 8), 'new in Python 3.8')
def test_positional_only_argument_annotations(self):
self.flakes("""
from x import C
Expand Down Expand Up @@ -584,7 +582,6 @@ def f() -> "Optional['Queue[str]']":
return None
""")

@skipIf(version_info < (3, 7), 'new in Python 3.7')
def test_partial_string_annotations_with_future_annotations(self):
self.flakes("""
from __future__ import annotations
Expand Down
7 changes: 1 addition & 6 deletions setup.py
Expand Up @@ -42,19 +42,14 @@ def get_long_description():
author_email="code-quality@python.org",
url="https://github.com/PyCQA/pyflakes",
packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"],
python_requires='>=3.6',
python_requires='>=3.8',
classifiers=[
"Development Status :: 6 - Mature",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
Expand Down

0 comments on commit 98d4fa3

Please sign in to comment.