summaryrefslogtreecommitdiff
path: root/pyflakes/checker.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyflakes/checker.py')
-rw-r--r--pyflakes/checker.py295
1 files changed, 94 insertions, 201 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 6dd4d89..42e5649 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -5,6 +5,7 @@ Implement the central Checker class.
Also, it models the Bindings and Scopes.
"""
import __future__
+import builtins
import ast
import bisect
import collections
@@ -19,66 +20,22 @@ import tokenize
from pyflakes import messages
-PY2 = sys.version_info < (3, 0)
-PY35_PLUS = sys.version_info >= (3, 5) # Python 3.5 and above
-PY36_PLUS = sys.version_info >= (3, 6) # Python 3.6 and above
PY38_PLUS = sys.version_info >= (3, 8)
PYPY = hasattr(sys, 'pypy_version_info')
-builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins'))
+builtin_vars = dir(builtins)
parse_format_string = string.Formatter().parse
-if PY2:
- tokenize_tokenize = tokenize.generate_tokens
-else:
- tokenize_tokenize = tokenize.tokenize
-
-if PY2:
- def getNodeType(node_class):
- # workaround str.upper() which is locale-dependent
- return str(unicode(node_class.__name__).upper())
- def get_raise_argument(node):
- return node.type
+def getAlternatives(n):
+ if isinstance(n, ast.If):
+ return [n.body]
+ if isinstance(n, ast.Try):
+ return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
-else:
- def getNodeType(node_class):
- return node_class.__name__.upper()
-
- def get_raise_argument(node):
- return node.exc
-
- # Silence `pyflakes` from reporting `undefined name 'unicode'` in Python 3.
- unicode = str
-
-# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally)
-if PY2:
- def getAlternatives(n):
- if isinstance(n, (ast.If, ast.TryFinally)):
- return [n.body]
- if isinstance(n, ast.TryExcept):
- return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
-else:
- def getAlternatives(n):
- if isinstance(n, ast.If):
- return [n.body]
- if isinstance(n, ast.Try):
- return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
-
-if PY35_PLUS:
- FOR_TYPES = (ast.For, ast.AsyncFor)
- LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor)
- FUNCTION_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef)
-else:
- FOR_TYPES = (ast.For,)
- LOOP_TYPES = (ast.While, ast.For)
- FUNCTION_TYPES = (ast.FunctionDef,)
-if PY36_PLUS:
- ANNASSIGN_TYPES = (ast.AnnAssign,)
-else:
- ANNASSIGN_TYPES = ()
+FOR_TYPES = (ast.For, ast.AsyncFor)
if PY38_PLUS:
def _is_singleton(node): # type: (ast.AST) -> bool
@@ -86,15 +43,9 @@ if PY38_PLUS:
isinstance(node, ast.Constant) and
isinstance(node.value, (bool, type(Ellipsis), type(None)))
)
-elif not PY2:
- def _is_singleton(node): # type: (ast.AST) -> bool
- return isinstance(node, (ast.NameConstant, ast.Ellipsis))
else:
def _is_singleton(node): # type: (ast.AST) -> bool
- return (
- isinstance(node, ast.Name) and
- node.id in {'True', 'False', 'Ellipsis', 'None'}
- )
+ return isinstance(node, (ast.NameConstant, ast.Ellipsis))
def _is_tuple_constant(node): # type: (ast.AST) -> bool
@@ -108,13 +59,9 @@ if PY38_PLUS:
def _is_constant(node):
return isinstance(node, ast.Constant) or _is_tuple_constant(node)
else:
- _const_tps = (ast.Str, ast.Num)
- if not PY2:
- _const_tps += (ast.Bytes,)
-
def _is_constant(node):
return (
- isinstance(node, _const_tps) or
+ isinstance(node, (ast.Str, ast.Num, ast.Bytes)) or
_is_singleton(node) or
_is_tuple_constant(node)
)
@@ -136,7 +83,7 @@ TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*')
# https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413
ASCII_NON_ALNUM = ''.join([chr(i) for i in range(128) if not chr(i).isalnum()])
TYPE_IGNORE_RE = re.compile(
- TYPE_COMMENT_RE.pattern + r'ignore([{}]|$)'.format(ASCII_NON_ALNUM))
+ TYPE_COMMENT_RE.pattern + fr'ignore([{ASCII_NON_ALNUM}]|$)')
# https://github.com/python/typed_ast/blob/1.4.0/ast27/Grammar/Grammar#L147
TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$')
@@ -293,8 +240,7 @@ def convert_to_value(item):
result.name,
result,
)
- elif (not PY2) and isinstance(item, ast.NameConstant):
- # None, True, False are nameconstants in python3, but names in 2
+ elif isinstance(item, ast.NameConstant):
return item.value
else:
return UnhandledKeyType()
@@ -304,7 +250,7 @@ def is_notimplemented_name_node(node):
return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented'
-class Binding(object):
+class Binding:
"""
Represents the binding of a value to a name.
@@ -325,10 +271,12 @@ class Binding(object):
return self.name
def __repr__(self):
- return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
- self.name,
- self.source.lineno,
- id(self))
+ return '<{} object {!r} from line {!r} at 0x{:x}>'.format(
+ self.__class__.__name__,
+ self.name,
+ self.source.lineno,
+ id(self),
+ )
def redefines(self, other):
return isinstance(other, Definition) and self.name == other.name
@@ -344,21 +292,23 @@ class Builtin(Definition):
"""A definition created for all Python builtins."""
def __init__(self, name):
- super(Builtin, self).__init__(name, None)
+ super().__init__(name, None)
def __repr__(self):
- return '<%s object %r at 0x%x>' % (self.__class__.__name__,
- self.name,
- id(self))
+ return '<{} object {!r} at 0x{:x}>'.format(
+ self.__class__.__name__,
+ self.name,
+ id(self)
+ )
-class UnhandledKeyType(object):
+class UnhandledKeyType:
"""
A dictionary key of a type that we cannot or do not check for duplicates.
"""
-class VariableKey(object):
+class VariableKey:
"""
A dictionary key which is a variable.
@@ -389,7 +339,7 @@ class Importation(Definition):
def __init__(self, name, source, full_name=None):
self.fullName = full_name or name
self.redefined = []
- super(Importation, self).__init__(name, source)
+ super().__init__(name, source)
def redefines(self, other):
if isinstance(other, SubmoduleImportation):
@@ -405,7 +355,7 @@ class Importation(Definition):
def source_statement(self):
"""Generate a source statement equivalent to the import."""
if self._has_alias():
- return 'import %s as %s' % (self.fullName, self.name)
+ return f'import {self.fullName} as {self.name}'
else:
return 'import %s' % self.fullName
@@ -438,13 +388,13 @@ class SubmoduleImportation(Importation):
# A dot should only appear in the name when it is a submodule import
assert '.' in name and (not source or isinstance(source, ast.Import))
package_name = name.split('.')[0]
- super(SubmoduleImportation, self).__init__(package_name, source)
+ super().__init__(package_name, source)
self.fullName = name
def redefines(self, other):
if isinstance(other, Importation):
return self.fullName == other.fullName
- return super(SubmoduleImportation, self).redefines(other)
+ return super().redefines(other)
def __str__(self):
return self.fullName
@@ -465,7 +415,7 @@ class ImportationFrom(Importation):
else:
full_name = module + '.' + self.real_name
- super(ImportationFrom, self).__init__(name, source, full_name)
+ super().__init__(name, source, full_name)
def __str__(self):
"""Return import full name with alias."""
@@ -477,18 +427,16 @@ class ImportationFrom(Importation):
@property
def source_statement(self):
if self.real_name != self.name:
- return 'from %s import %s as %s' % (self.module,
- self.real_name,
- self.name)
+ return f'from {self.module} import {self.real_name} as {self.name}'
else:
- return 'from %s import %s' % (self.module, self.name)
+ return f'from {self.module} import {self.name}'
class StarImportation(Importation):
"""A binding created by a 'from x import *' statement."""
def __init__(self, name, source):
- super(StarImportation, self).__init__('*', source)
+ super().__init__('*', source)
# Each star importation needs a unique name, and
# may not be the module name otherwise it will be deemed imported
self.name = name + '.*'
@@ -514,7 +462,7 @@ class FutureImportation(ImportationFrom):
"""
def __init__(self, name, source, scope):
- super(FutureImportation, self).__init__(name, source, '__future__')
+ super().__init__(name, source, '__future__')
self.used = (scope, source)
@@ -608,7 +556,7 @@ class ExportBinding(Binding):
# If not list concatenation
else:
break
- super(ExportBinding, self).__init__(name, source)
+ super().__init__(name, source)
class Scope(dict):
@@ -616,7 +564,7 @@ class Scope(dict):
def __repr__(self):
scope_cls = self.__class__.__name__
- return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self))
+ return f'<{scope_cls} at 0x{id(self):x} {dict.__repr__(self)}>'
class ClassScope(Scope):
@@ -634,7 +582,7 @@ class FunctionScope(Scope):
'__traceback_supplement__'}
def __init__(self):
- super(FunctionScope, self).__init__()
+ super().__init__()
# Simplify: manage the special locals as globals
self.globals = self.alwaysUsed.copy()
self.returnValue = None # First non-empty return
@@ -667,7 +615,7 @@ class DoctestScope(ModuleScope):
"""Scope for a doctest."""
-class DummyNode(object):
+class DummyNode:
"""Used in place of an `ast.AST` to set error message positions"""
def __init__(self, lineno, col_offset):
self.lineno = lineno
@@ -680,10 +628,7 @@ class DetectClassScopedMagic:
# Globally defined names which are not attributes of the builtins module, or
# are only present on some platforms.
-_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
-# module scope annotation will store in `__annotations__`, see also PEP 526.
-if PY36_PLUS:
- _MAGIC_GLOBALS.append('__annotations__')
+_MAGIC_GLOBALS = ['__file__', '__builtins__', '__annotations__', 'WindowsError']
def getNodeName(node):
@@ -766,7 +711,7 @@ def _is_any_typing_member(node, scope_stack):
def is_typing_overload(value, scope_stack):
return (
- isinstance(value.source, FUNCTION_TYPES) and
+ isinstance(value.source, (ast.FunctionDef, ast.AsyncFunctionDef)) and
any(
_is_typing(dec, 'overload', scope_stack)
for dec in value.source.decorator_list
@@ -802,7 +747,7 @@ def make_tokens(code):
code = code.encode('UTF-8')
lines = iter(code.splitlines(True))
# next(lines, b'') is to prevent an error in pypy3
- return tuple(tokenize_tokenize(lambda: next(lines, b'')))
+ return tuple(tokenize.tokenize(lambda: next(lines, b'')))
class _TypeableVisitor(ast.NodeVisitor):
@@ -853,7 +798,7 @@ def _collect_type_comments(tree, tokens):
return type_comments
-class Checker(object):
+class Checker:
"""
I check the cleanliness and sanity of Python code.
@@ -870,14 +815,13 @@ class Checker(object):
ast.Module: ModuleScope,
ast.ClassDef: ClassScope,
ast.FunctionDef: FunctionScope,
+ ast.AsyncFunctionDef: FunctionScope,
ast.Lambda: FunctionScope,
ast.ListComp: GeneratorScope,
ast.SetComp: GeneratorScope,
ast.GeneratorExp: GeneratorScope,
ast.DictComp: GeneratorScope,
}
- if PY35_PLUS:
- _ast_node_scope[ast.AsyncFunctionDef] = FunctionScope
nodeDepth = 0
offset = None
@@ -1138,16 +1082,13 @@ class Checker(object):
node, value.name, existing.source)
elif scope is self.scope:
- if (isinstance(parent_stmt, ast.comprehension) and
- not isinstance(self.getParent(existing.source),
- (FOR_TYPES, ast.comprehension))):
- self.report(messages.RedefinedInListComp,
+ if (
+ (not existing.used and value.redefines(existing)) and
+ (value.name != '_' or isinstance(existing, Importation)) and
+ not is_typing_overload(existing, self.scopeStack)
+ ):
+ self.report(messages.RedefinedWhileUnused,
node, value.name, existing.source)
- elif not existing.used and value.redefines(existing):
- if value.name != '_' or isinstance(existing, Importation):
- if not is_typing_overload(existing, self.scopeStack):
- self.report(messages.RedefinedWhileUnused,
- node, value.name, existing.source)
elif isinstance(existing, Importation) and value.redefines(existing):
existing.redefined.append(node)
@@ -1180,7 +1121,7 @@ class Checker(object):
# in the pyflakes testsuite (so more specific handling can be added if
# needed).
if os.environ.get('PYFLAKES_ERROR_UNKNOWN'):
- raise NotImplementedError('Unexpected type: {}'.format(type(node)))
+ raise NotImplementedError(f'Unexpected type: {type(node)}')
else:
self.handleChildren(node)
@@ -1188,7 +1129,7 @@ class Checker(object):
try:
return self._nodeHandlers[node_class]
except KeyError:
- nodeType = getNodeType(node_class)
+ nodeType = node_class.__name__.upper()
self._nodeHandlers[node_class] = handler = getattr(
self, nodeType, self._unknown_handler,
)
@@ -1205,7 +1146,7 @@ class Checker(object):
# try enclosing function scopes and global scope
for scope in self.scopeStack[-1::-1]:
if isinstance(scope, ClassScope):
- if not PY2 and name == '__class__':
+ if name == '__class__':
return
elif in_generators is False:
# only generators used in a class scope can access the
@@ -1292,7 +1233,7 @@ class Checker(object):
break
parent_stmt = self.getParent(node)
- if isinstance(parent_stmt, ANNASSIGN_TYPES) and parent_stmt.value is None:
+ if isinstance(parent_stmt, ast.AnnAssign) and parent_stmt.value is None:
binding = Annotation(name, node)
elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or (
parent_stmt != node._pyflakes_parent and
@@ -1307,8 +1248,6 @@ class Checker(object):
)
):
binding = ExportBinding(name, node._pyflakes_parent, self.scope)
- elif PY2 and isinstance(getattr(node, 'ctx', None), ast.Param):
- binding = Argument(name, self.getScopeNode(node))
elif PY38_PLUS and isinstance(parent_stmt, ast.NamedExpr):
binding = NamedExprAssignment(name, node)
else:
@@ -1373,8 +1312,6 @@ class Checker(object):
parts = (comment,)
for part in parts:
- if PY2:
- part = part.replace('...', 'Ellipsis')
self.deferFunction(functools.partial(
self.handleStringAnnotation,
part, DummyNode(lineno, col_offset), lineno, col_offset,
@@ -1517,16 +1454,14 @@ class Checker(object):
pass
# "stmt" type nodes
- DELETE = PRINT = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = \
- ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \
+ DELETE = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = ASYNCWITH = \
EXPR = ASSIGN = handleChildren
PASS = ignore
# "expr" type nodes
- BOOLOP = UNARYOP = SET = \
- REPR = ATTRIBUTE = \
- STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren
+ BOOLOP = UNARYOP = SET = ATTRIBUTE = STARRED = NAMECONSTANT = \
+ NAMEDEXPR = handleChildren
def SUBSCRIPT(self, node):
if _is_name_or_attr(node.value, 'Literal'):
@@ -1573,15 +1508,16 @@ class Checker(object):
self.report(messages.StringDotFormatInvalidFormat, node, e)
return
- class state: # py2-compatible `nonlocal`
- auto = None
- next_auto = 0
+ auto = None
+ next_auto = 0
placeholder_positional = set()
placeholder_named = set()
def _add_key(fmtkey):
"""Returns True if there is an error which should early-exit"""
+ nonlocal auto, next_auto
+
if fmtkey is None: # end of string or `{` / `}` escapes
return False
@@ -1594,21 +1530,21 @@ class Checker(object):
except ValueError:
pass
else: # fmtkey was an integer
- if state.auto is True:
+ if auto is True:
self.report(messages.StringDotFormatMixingAutomatic, node)
return True
else:
- state.auto = False
+ auto = False
if fmtkey == '':
- if state.auto is False:
+ if auto is False:
self.report(messages.StringDotFormatMixingAutomatic, node)
return True
else:
- state.auto = True
+ auto = True
- fmtkey = state.next_auto
- state.next_auto += 1
+ fmtkey = next_auto
+ next_auto += 1
if isinstance(fmtkey, int):
placeholder_positional.add(fmtkey)
@@ -1643,15 +1579,9 @@ class Checker(object):
# bail early if there is *args or **kwargs
if (
- # python 2.x *args / **kwargs
- getattr(node, 'starargs', None) or
- getattr(node, 'kwargs', None) or
- # python 3.x *args
- any(
- isinstance(arg, getattr(ast, 'Starred', ()))
- for arg in node.args
- ) or
- # python 3.x **kwargs
+ # *args
+ any(isinstance(arg, ast.Starred) for arg in node.args) or
+ # **kwargs
any(kwd.arg is None for kwd in node.keywords)
):
return
@@ -1832,7 +1762,7 @@ class Checker(object):
isinstance(node.right, (ast.List, ast.Tuple)) and
# does not have any *splats (py35+ feature)
not any(
- isinstance(elt, getattr(ast, 'Starred', ()))
+ isinstance(elt, ast.Starred)
for elt in node.right.elts
)
):
@@ -1916,7 +1846,7 @@ class Checker(object):
def RAISE(self, node):
self.handleChildren(node)
- arg = get_raise_argument(node)
+ arg = node.exc
if isinstance(arg, ast.Call):
if is_notimplemented_name_node(arg.func):
@@ -2031,9 +1961,7 @@ class Checker(object):
self.handleChildren(node)
self.popScope()
- LISTCOMP = handleChildren if PY2 else GENERATOREXP
-
- DICTCOMP = SETCOMP = GENERATOREXP
+ LISTCOMP = DICTCOMP = SETCOMP = GENERATOREXP
def NAME(self, node):
"""
@@ -2048,13 +1976,11 @@ class Checker(object):
self.scope.usesLocals = True
elif isinstance(node.ctx, ast.Store):
self.handleNodeStore(node)
- elif PY2 and isinstance(node.ctx, ast.Param):
- self.handleNodeStore(node)
elif isinstance(node.ctx, ast.Del):
self.handleNodeDelete(node)
else:
# Unknown context
- raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
+ raise RuntimeError(f"Got impossible expression context: {node.ctx!r}")
def CONTINUE(self, node):
# Walk the tree up until we see a loop (OK), a function or class
@@ -2063,7 +1989,7 @@ class Checker(object):
n = node
while hasattr(n, '_pyflakes_parent'):
n, n_child = n._pyflakes_parent, n
- if isinstance(n, LOOP_TYPES):
+ if isinstance(n, (ast.While, ast.For, ast.AsyncFor)):
# Doesn't apply unless it's in the loop itself
if n_child not in n.orelse:
return
@@ -2122,41 +2048,26 @@ class Checker(object):
args = []
annotations = []
- if PY2:
- def addArgs(arglist):
- for arg in arglist:
- if isinstance(arg, ast.Tuple):
- addArgs(arg.elts)
- else:
- args.append(arg.id)
- addArgs(node.args.args)
- defaults = node.args.defaults
- else:
- if PY38_PLUS:
- for arg in node.args.posonlyargs:
- args.append(arg.arg)
- annotations.append(arg.annotation)
- for arg in node.args.args + node.args.kwonlyargs:
+ if PY38_PLUS:
+ for arg in node.args.posonlyargs:
args.append(arg.arg)
annotations.append(arg.annotation)
- defaults = node.args.defaults + node.args.kw_defaults
+ for arg in node.args.args + node.args.kwonlyargs:
+ args.append(arg.arg)
+ annotations.append(arg.annotation)
+ defaults = node.args.defaults + node.args.kw_defaults
- # Only for Python3 FunctionDefs
- is_py3_func = hasattr(node, 'returns')
+ has_annotations = not isinstance(node, ast.Lambda)
for arg_name in ('vararg', 'kwarg'):
wildcard = getattr(node.args, arg_name)
if not wildcard:
continue
- args.append(wildcard if PY2 else wildcard.arg)
- if is_py3_func:
- if PY2: # Python 2.7
- argannotation = arg_name + 'annotation'
- annotations.append(getattr(node.args, argannotation))
- else: # Python >= 3.4
- annotations.append(wildcard.annotation)
-
- if is_py3_func:
+ args.append(wildcard.arg)
+ if has_annotations:
+ annotations.append(wildcard.annotation)
+
+ if has_annotations:
annotations.append(node.returns)
if len(set(args)) < len(args):
@@ -2184,28 +2095,12 @@ class Checker(object):
self.report(messages.UnusedVariable, binding.source, name)
self.deferAssignment(checkUnusedAssignments)
- if PY2:
- def checkReturnWithArgumentInsideGenerator():
- """
- Check to see if there is any return statement with
- arguments but the function is a generator.
- """
- if self.scope.isGenerator and self.scope.returnValue:
- self.report(messages.ReturnWithArgsInsideGenerator,
- self.scope.returnValue)
- self.deferAssignment(checkReturnWithArgumentInsideGenerator)
self.popScope()
self.deferFunction(runFunction)
def ARGUMENTS(self, node):
self.handleChildren(node, omit=('defaults', 'kw_defaults'))
- if PY2:
- scope_node = self.getScopeNode(node)
- if node.vararg:
- self.addBinding(node, Argument(node.vararg, scope_node))
- if node.kwarg:
- self.addBinding(node, Argument(node.kwarg, scope_node))
def ARG(self, node):
self.addBinding(node, Argument(node.arg, self.getScopeNode(node)))
@@ -2220,9 +2115,8 @@ class Checker(object):
self.handleNode(deco, node)
for baseNode in node.bases:
self.handleNode(baseNode, node)
- if not PY2:
- for keywordNode in node.keywords:
- self.handleNode(keywordNode, node)
+ for keywordNode in node.keywords:
+ self.handleNode(keywordNode, node)
self.pushScope(ClassScope)
# doctest does not process doctest within a doctest
# classes within classes are processed.
@@ -2241,7 +2135,7 @@ class Checker(object):
self.handleNode(node.target, node)
def TUPLE(self, node):
- if not PY2 and isinstance(node.ctx, ast.Store):
+ if isinstance(node.ctx, ast.Store):
# Python 3 advanced tuple unpacking: a, *b, c = d.
# Only one starred expression is allowed, and no more than 1<<8
# assignments are allowed before a stared expression. There is
@@ -2293,8 +2187,7 @@ class Checker(object):
if alias.name == 'annotations':
self.annotationsFutureEnabled = True
elif alias.name == '*':
- # Only Python 2, local import * is a SyntaxWarning
- if not PY2 and not isinstance(self.scope, ModuleScope):
+ if not isinstance(self.scope, ModuleScope):
self.report(messages.ImportStarNotPermitted,
node, module)
continue
@@ -2327,10 +2220,10 @@ class Checker(object):
# Process the other nodes: "except:", "else:", "finally:"
self.handleChildren(node, omit='body')
- TRYEXCEPT = TRYSTAR = TRY
+ TRYSTAR = TRY
def EXCEPTHANDLER(self, node):
- if PY2 or node.name is None:
+ if node.name is None:
self.handleChildren(node)
return