diff -r f7cb83647621 -r 72721823d453 eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py --- a/eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py Sun May 31 17:26:46 2020 +0200 +++ b/eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py Sat Jul 04 11:45:34 2020 +0200 @@ -15,6 +15,7 @@ import ast import bisect import collections +import contextlib import doctest import functools import os @@ -85,6 +86,51 @@ LOOP_TYPES = (ast.While, ast.For) FUNCTION_TYPES = (ast.FunctionDef,) + +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))) + ) +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'} + ) + + +def _is_tuple_constant(node): # type: (ast.AST) -> bool + return ( + isinstance(node, ast.Tuple) and + all(_is_constant(elt) for elt in node.elts) + ) + + +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 + _is_singleton(node) or + _is_tuple_constant(node) + ) + + +def _is_const_non_singleton(node): # type: (ast.AST) -> bool + return _is_constant(node) and not _is_singleton(node) + + # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104 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 @@ -625,40 +671,82 @@ return node.name -def is_typing_overload(value, scope_stack): - def name_is_typing_overload(name): # type: (str) -> bool +TYPING_MODULES = frozenset(('typing', 'typing_extensions')) + + +def _is_typing_helper(node, is_name_match_fn, scope_stack): + """ + Internal helper to determine whether or not something is a member of a + typing module. This is used as part of working out whether we are within a + type annotation context. + + Note: you probably don't want to use this function directly. Instead see the + utils below which wrap it (`_is_typing` and `_is_any_typing_member`). + """ + + def _bare_name_is_attr(name): for scope in reversed(scope_stack): if name in scope: return ( isinstance(scope[name], ImportationFrom) and - scope[name].fullName in ( - 'typing.overload', 'typing_extensions.overload', - ) + scope[name].module in TYPING_MODULES and + is_name_match_fn(scope[name].real_name) ) return False - def is_typing_overload_decorator(node): - return ( - ( - isinstance(node, ast.Name) and name_is_typing_overload(node.id) - ) or ( - isinstance(node, ast.Attribute) and - isinstance(node.value, ast.Name) and - node.value.id == 'typing' and - node.attr == 'overload' - ) + return ( + ( + isinstance(node, ast.Name) and + _bare_name_is_attr(node.id) + ) or ( + isinstance(node, ast.Attribute) and + isinstance(node.value, ast.Name) and + node.value.id in TYPING_MODULES and + is_name_match_fn(node.attr) ) + ) + +def _is_typing(node, typing_attr, scope_stack): + """ + Determine whether `node` represents the member of a typing module specified + by `typing_attr`. + + This is used as part of working out whether we are within a type annotation + context. + """ + return _is_typing_helper(node, lambda x: x == typing_attr, scope_stack) + + +def _is_any_typing_member(node, scope_stack): + """ + Determine whether `node` represents any member of a typing module. + + This is used as part of working out whether we are within a type annotation + context. + """ + return _is_typing_helper(node, lambda x: True, scope_stack) + + +def is_typing_overload(value, scope_stack): return ( isinstance(value.source, FUNCTION_TYPES) and any( - is_typing_overload_decorator(dec) + _is_typing(dec, 'overload', scope_stack) for dec in value.source.decorator_list ) ) +def in_annotation(func): + @functools.wraps(func) + def in_annotation_func(self, *args, **kwargs): + with self._enter_annotation(): + return func(self, *args, **kwargs) + return in_annotation_func + + def make_tokens(code): # PY3: tokenize.tokenize requires readline of bytes if not isinstance(code, bytes): @@ -740,11 +828,14 @@ ast.DictComp: GeneratorScope, } if PY35_PLUS: - _ast_node_scope[ast.AsyncFunctionDef] = FunctionScope, + _ast_node_scope[ast.AsyncFunctionDef] = FunctionScope nodeDepth = 0 offset = None traceTree = False + _in_annotation = False + _in_typing_literal = False + _in_deferred = False builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') @@ -776,6 +867,7 @@ for builtin in self.builtIns: self.addBinding(None, Builtin(builtin)) self.handleChildren(tree) + self._in_deferred = True self.runDeferred(self._deferredFunctions) # Set _deferredFunctions to None so that deferFunction will fail # noisily if called after we've run through the deferred functions. @@ -1016,12 +1108,30 @@ self.scope[value.name] = value + def _unknown_handler(self, node): + # this environment variable configures whether to error on unknown + # ast types. + # + # this is silent by default but the error is enabled for the pyflakes + # testsuite. + # + # this allows new syntax to be added to python without *requiring* + # changes from the pyflakes side. but will still produce an error + # 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))) + else: + self.handleChildren(node) + def getNodeHandler(self, node_class): try: return self._nodeHandlers[node_class] except KeyError: nodeType = getNodeType(node_class) - self._nodeHandlers[node_class] = handler = getattr(self, nodeType) + self._nodeHandlers[node_class] = handler = getattr( + self, nodeType, self._unknown_handler, + ) return handler def handleNodeLoad(self, node): @@ -1125,7 +1235,7 @@ binding = Binding(name, node) elif name == '__all__' and isinstance(self.scope, ModuleScope): binding = ExportBinding(name, node._pyflakes_parent, self.scope) - elif isinstance(getattr(node, 'ctx', None), ast.Param): + elif PY2 and isinstance(getattr(node, 'ctx', None), ast.Param): binding = Argument(name, self.getScopeNode(node)) else: binding = Assignment(name, node) @@ -1161,6 +1271,14 @@ except KeyError: self.report(messages.UndefinedName, node, name) + @contextlib.contextmanager + def _enter_annotation(self): + orig, self._in_annotation = self._in_annotation, True + try: + yield + finally: + self._in_annotation = orig + def _handle_type_comments(self, node): for (lineno, col_offset), comment in self._type_comments.get(node, ()): comment = comment.split(':', 1)[1].strip() @@ -1288,6 +1406,7 @@ self.popScope() self.scopeStack = saved_stack + @in_annotation def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): try: tree = ast.parse(s) @@ -1311,6 +1430,7 @@ self.handleNode(parsed_annotation, node) + @in_annotation def handleAnnotation(self, annotation, node): if isinstance(annotation, ast.Str): # Defer handling forward annotation. @@ -1323,7 +1443,8 @@ messages.ForwardAnnotationSyntaxError, )) elif self.annotationsFutureEnabled: - self.deferFunction(lambda: self.handleNode(annotation, node)) + fn = in_annotation(Checker.handleNode) + self.deferFunction(lambda: fn(self, annotation, node)) else: self.handleNode(annotation, node) @@ -1331,17 +1452,39 @@ pass # "stmt" type nodes - DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \ + DELETE = PRINT = FOR = ASYNCFOR = WHILE = WITH = WITHITEM = \ ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \ EXPR = ASSIGN = handleChildren PASS = ignore # "expr" type nodes - BOOLOP = UNARYOP = IFEXP = SET = \ - REPR = ATTRIBUTE = SUBSCRIPT = \ + BOOLOP = UNARYOP = SET = \ + REPR = ATTRIBUTE = \ STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren + def SUBSCRIPT(self, node): + if ( + ( + isinstance(node.value, ast.Name) and + node.value.id == 'Literal' + ) or ( + isinstance(node.value, ast.Attribute) and + node.value.attr == 'Literal' + ) + ): + orig, self._in_typing_literal = self._in_typing_literal, True + try: + self.handleChildren(node) + finally: + self._in_typing_literal = orig + else: + if _is_any_typing_member(node.value, self.scopeStack): + with self._enter_annotation(): + self.handleChildren(node) + else: + self.handleChildren(node) + def _handle_string_dot_format(self, node): try: placeholders = tuple(parse_format_string(node.func.value.s)) @@ -1469,6 +1612,15 @@ node.func.attr == 'format' ): self._handle_string_dot_format(node) + + if ( + _is_typing(node.func, 'cast', self.scopeStack) and + len(node.args) >= 1 and + isinstance(node.args[0], ast.Str) + ): + with self._enter_annotation(): + self.handleNode(node.args[0], node) + self.handleChildren(node) def _handle_percent_format(self, node): @@ -1582,7 +1734,27 @@ self._handle_percent_format(node) self.handleChildren(node) - NUM = STR = BYTES = ELLIPSIS = CONSTANT = ignore + def STR(self, node): + if self._in_annotation and not self._in_typing_literal: + fn = functools.partial( + self.handleStringAnnotation, + node.s, + node, + node.lineno, + node.col_offset, + messages.ForwardAnnotationSyntaxError, + ) + if self._in_deferred: + fn() + 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 # "slice" type nodes SLICE = EXTSLICE = INDEX = handleChildren @@ -1665,6 +1837,13 @@ ) self.handleChildren(node) + def IF(self, node): + if isinstance(node.test, ast.Tuple) and node.test.elts != []: + self.report(messages.IfTuple, node) + self.handleChildren(node) + + IFEXP = IF + def ASSERT(self, node): if isinstance(node.test, ast.Tuple) and node.test.elts != []: self.report(messages.AssertTuple, node) @@ -1716,13 +1895,15 @@ Handle occurrence of Name (which can be a load/store/delete access.) """ # Locate the name in locals / function / globals scopes. - if isinstance(node.ctx, (ast.Load, ast.AugLoad)): + if isinstance(node.ctx, ast.Load): self.handleNodeLoad(node) if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and isinstance(node._pyflakes_parent, ast.Call)): # we are doing locals() call in current scope self.scope.usesLocals = True - elif isinstance(node.ctx, (ast.Store, ast.AugStore, ast.Param)): + 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) @@ -2061,18 +2242,15 @@ self.handleNode(node.value, node) def COMPARE(self, node): - literals = (ast.Str, ast.Num) - if not PY2: - literals += (ast.Bytes,) - left = node.left for op, right in zip(node.ops, node.comparators): - if (isinstance(op, (ast.Is, ast.IsNot)) and - (isinstance(left, literals) or isinstance(right, literals))): + if ( + isinstance(op, (ast.Is, ast.IsNot)) and ( + _is_const_non_singleton(left) or + _is_const_non_singleton(right) + ) + ): self.report(messages.IsLiteral, node) left = right self.handleChildren(node) - -# -# eflag: noqa = M702