Remove nodes_to_execute in favor of a function in parser_utils.

This commit is contained in:
Dave Halter
2017-04-08 12:59:49 +02:00
parent 8542047e5c
commit 65a6c61dc6
6 changed files with 81 additions and 182 deletions

View File

@@ -16,6 +16,7 @@ import sys
from jedi.parser.python import load_grammar
from jedi.parser.python import tree
from jedi.parser.python import parse
from jedi.parser_utils import get_executable_nodes
from jedi import debug
from jedi import settings
from jedi import common
@@ -331,7 +332,7 @@ class Script(object):
module_node = self._get_module_node()
self._evaluator.analysis_modules = [module_node]
try:
for node in module_node.nodes_to_execute():
for node in get_executable_nodes(module_node):
context = self._get_module().create_context(node)
if node.type in ('funcdef', 'classdef'):
# TODO This is stupid, should be private

View File

@@ -450,7 +450,8 @@ class Evaluator(object):
raise NotImplementedError
if def_.type == 'expr_stmt' and name in def_.get_defined_names():
return self.eval_statement(context, def_, name)
elif def_.type == 'for_stmt':
elif def_.type == 'for_stmt' and \
name.start_pos < def_.children[1].end_pos:
container_types = self.eval_element(context, def_.children[3])
cn = ContextualizedNode(context, def_.children[3])
for_types = iterable.py__iter__types(self, container_types, cn)

View File

@@ -25,16 +25,11 @@ Any subclasses of :class:`Scope`, including :class:`Module` has an attribute
[<ImportName: import os@1,0>]
See also :attr:`Scope.subscopes` and :attr:`Scope.statements`.
For static analysis purposes there exists a method called
``nodes_to_execute`` on all nodes and leaves. It's documented in the static
anaylsis documentation.
"""
from inspect import cleandoc
from itertools import chain
import textwrap
import abc
from jedi._compatibility import (Python3Method, is_py3, utf8_repr,
literal_eval, unicode)
@@ -143,10 +138,6 @@ class PythonMixin():
# Default is not being a scope. Just inherit from Scope.
return False
@abc.abstractmethod
def nodes_to_execute(self, last_added=False):
raise NotImplementedError()
@Python3Method
def name_for_position(self, position):
for c in self.children:
@@ -256,10 +247,6 @@ class Name(_LeafWithoutNewlines):
'comp_for', 'with_stmt') \
and self in stmt.get_defined_names()
def nodes_to_execute(self, last_added=False):
if last_added is False:
yield self
class Literal(PythonLeaf):
__slots__ = ()
@@ -431,13 +418,6 @@ class Module(Scope):
return True
return False
def nodes_to_execute(self, last_added=False):
# Yield itself, class needs to be executed for decorator checks.
result = []
for child in self.children:
result += child.nodes_to_execute()
return result
@property
def used_names(self):
if self._used_names is None:
@@ -464,13 +444,6 @@ class Decorator(PythonBaseNode):
type = 'decorator'
__slots__ = ()
def nodes_to_execute(self, last_added=False):
if self.children[-2] == ')':
node = self.children[-3]
if node != '(':
return node.nodes_to_execute()
return []
class ClassOrFunc(Scope):
__slots__ = ()
@@ -528,34 +501,6 @@ class Class(ClassOrFunc):
sub.get_call_signature(func_name=self.name), docstr)
return docstr
def nodes_to_execute(self, last_added=False):
# Yield itself, class needs to be executed for decorator checks.
yield self
# Super arguments.
arglist = self.get_super_arglist()
try:
children = arglist.children
except AttributeError:
if arglist is not None:
for node_to_execute in arglist.nodes_to_execute():
yield node_to_execute
else:
for argument in children:
if argument.type == 'argument':
# metaclass= or list comprehension or */**
raise NotImplementedError('Metaclasses not implemented')
else:
for node_to_execute in argument.nodes_to_execute():
yield node_to_execute
# care for the class suite:
for node in self.children[self.children.index(':'):]:
# This could be easier without the fast parser. But we need to find
# the position of the colon, because everything after it can be a
# part of the class, not just its suite.
for node_to_execute in node.nodes_to_execute():
yield node_to_execute
def _create_params(parent, argslist_list):
"""
@@ -676,21 +621,6 @@ class Function(ClassOrFunc):
docstr = self.raw_doc
return '%s\n\n%s' % (self.get_call_signature(), docstr)
def nodes_to_execute(self, last_added=False):
# Yield itself, functions needs to be executed for decorator checks.
yield self
for param in self.params:
if param.default is not None:
yield param.default
# care for the function suite:
for node in self.children[4:]:
# This could be easier without the fast parser. The fast parser
# allows that the 4th position is empty or that there's even a
# fifth element (another function/class). So just scan everything
# after colon.
for node_to_execute in node.nodes_to_execute():
yield node_to_execute
class Lambda(Function):
"""
@@ -734,14 +664,6 @@ class Lambda(Function):
def yields(self):
return []
def nodes_to_execute(self, last_added=False):
for param in self.params:
if param.default is not None:
yield param.default
# Care for the lambda test (last child):
for node_to_execute in self.children[-1].nodes_to_execute():
yield node_to_execute
def __repr__(self):
return "<%s@%s>" % (self.__class__.__name__, self.start_pos)
@@ -752,11 +674,6 @@ class Flow(PythonBaseNode):
'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
)
def nodes_to_execute(self, last_added=False):
for child in self.children:
for node_to_execute in child.nodes_to_execute():
yield node_to_execute
def get_branch_keyword(self, node):
start_pos = node.start_pos
if not (self.start_pos < start_pos <= self.end_pos):
@@ -857,16 +774,6 @@ class TryStmt(Flow):
elif node == 'except':
yield None
def nodes_to_execute(self, last_added=False):
result = []
for child in self.children[2::3]:
result += child.nodes_to_execute()
for child in self.children[0::3]:
if child.type == 'except_clause':
# Add the test node and ignore the `as NAME` definition.
result += child.children[1].nodes_to_execute()
return result
class WithStmt(Flow):
type = 'with_stmt'
@@ -887,16 +794,6 @@ class WithStmt(Flow):
if node.type == 'with_item':
return node.children[0]
def nodes_to_execute(self, last_added=False):
result = []
for child in self.children[1::2]:
if child.type == 'with_item':
# Just ignore the `as EXPR` part - at least for now, because
# most times it's just a name.
child = child.children[0]
result += child.nodes_to_execute()
return result
class Import(PythonBaseNode):
__slots__ = ()
@@ -919,14 +816,6 @@ class Import(PythonBaseNode):
def is_star_import(self):
return self.children[-1] == '*'
def nodes_to_execute(self, last_added=False):
"""
`nodes_to_execute` works a bit different for imports, because the names
itself cannot directly get resolved (except on itself).
"""
# TODO couldn't we return the names? Would be nicer.
return [self]
class ImportFrom(Import):
type = 'import_from'
@@ -1070,12 +959,6 @@ class KeywordStatement(PythonBaseNode):
def keyword(self):
return self.children[0].value
def nodes_to_execute(self, last_added=False):
result = []
for child in self.children:
result += child.nodes_to_execute()
return result
class AssertStmt(KeywordStatement):
__slots__ = ()
@@ -1093,13 +976,6 @@ class GlobalStmt(KeywordStatement):
def get_global_names(self):
return self.children[1::2]
def nodes_to_execute(self, last_added=False):
"""
The global keyword allows to define any name. Even if it doesn't
exist.
"""
return []
class ReturnStmt(KeywordStatement):
__slots__ = ()
@@ -1112,12 +988,6 @@ class YieldExpr(PythonBaseNode):
def type(self):
return 'yield_expr'
def nodes_to_execute(self, last_added=False):
if len(self.children) > 1:
return self.children[1].nodes_to_execute()
else:
return []
def _defined_names(current):
"""
@@ -1167,14 +1037,6 @@ class ExprStmt(PythonBaseNode, DocstringMixin):
except IndexError:
return None
def nodes_to_execute(self, last_added=False):
# I think evaluating the statement (and possibly returned arrays),
# should be enough for static analysis.
result = [self]
for child in self.children:
result += child.nodes_to_execute(last_added=True)
return result
class Param(PythonBaseNode):
"""
@@ -1273,16 +1135,3 @@ class CompFor(PythonBaseNode):
def get_defined_names(self):
return _defined_names(self.children[1])
def nodes_to_execute(self, last_added=False):
last = self.children[-1]
if last.type == 'comp_if':
for node in last.children[-1].nodes_to_execute():
yield node
last = self.children[-2]
elif last.type == 'comp_for':
for node in last.nodes_to_execute():
yield node
last = self.children[-2]
for node in last.nodes_to_execute():
yield node

View File

@@ -132,9 +132,6 @@ class Leaf(_NodeOrLeaf):
else:
return self.value
def nodes_to_execute(self, last_added=False):
return []
@property
def end_pos(self):
"""
@@ -276,13 +273,6 @@ class Node(BaseNode):
"""Concrete implementation for interior nodes."""
__slots__ = ('type',)
_IGNORE_EXECUTE_NODES = set([
'suite', 'subscriptlist', 'subscript', 'simple_stmt', 'sliceop',
'testlist_comp', 'dictorsetmaker', 'trailer', 'decorators',
'decorated', 'arglist', 'argument', 'exprlist', 'testlist',
'testlist_safe', 'testlist1'
])
def __init__(self, type, children):
"""
Initializer.
@@ -295,19 +285,6 @@ class Node(BaseNode):
super(Node, self).__init__(children)
self.type = type
def nodes_to_execute(self, last_added=False):
"""
For static analysis.
"""
result = []
if self.type not in Node._IGNORE_EXECUTE_NODES and not last_added:
result.append(self)
last_added = True
for child in self.children:
result += child.nodes_to_execute(last_added)
return result
def __repr__(self):
return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children)
@@ -319,9 +296,6 @@ class ErrorNode(BaseNode):
__slots__ = ()
type = 'error_node'
def nodes_to_execute(self, last_added=False):
return []
class ErrorLeaf(Leaf):
"""
@@ -337,5 +311,3 @@ class ErrorLeaf(Leaf):
def __repr__(self):
return "<%s: %s:%s, %s)>" % \
(type(self).__name__, self.original_type, repr(self.value), self.start_pos)

76
jedi/parser_utils.py Normal file
View File

@@ -0,0 +1,76 @@
from jedi.parser.python.tree import PythonLeaf
_IGNORE_EXECUTE_NODES = set([
'suite', 'subscriptlist', 'subscript', 'simple_stmt', 'sliceop',
'testlist_comp', 'dictorsetmaker', 'trailer', 'decorators',
'decorated', 'arglist', 'argument', 'exprlist', 'testlist',
'testlist_safe', 'testlist1', 'global_stmt', 'file_input', 'for_stmt',
'while_stmt', 'if_stmt', 'try_stmt', 'with_stmt', 'comp_for', 'comp_if',
'param', 'except_clause', 'dotted_name', 'keyword_stmt', 'return_stmt',
'del_stmt', 'pass_stmt', 'nonlocal_stmt', 'assert_stmt', 'break_stmt',
'continue_stmt', 'raise_stmt', 'yield_stmt'
])
return_ = 'import_name', 'import_from'
# last added: Flow, KeywordStatement
def get_executable_nodes(node, last_added=False):
"""
For static analysis.
"""
result = []
typ = node.type
if typ == 'classdef':
# Yield itself, class needs to be executed for decorator checks.
result.append(node)
# Super arguments.
arglist = node.get_super_arglist()
try:
children = arglist.children
except AttributeError:
if arglist is not None:
result += get_executable_nodes(arglist)
else:
for argument in children:
if argument.type == 'argument':
# metaclass= or list comprehension or */**
raise NotImplementedError('Metaclasses not implemented, yet.')
else:
result += get_executable_nodes(argument)
# Care for the class suite:
suite = node.children[-1]
result += get_executable_nodes(suite)
elif typ == 'yield_expr':
if len(node.children) > 1:
# TODO delete?
result += get_executable_nodes(node.children[1])
elif typ == 'name':
next_leaf = node.get_next_leaf()
if last_added is False and node.parent.type != 'param' and next_leaf != '=':
result.append(node)
elif typ == 'expr_stmt':
# I think evaluating the statement (and possibly returned arrays),
# should be enough for static analysis.
result.append(node)
for child in node.children:
result += get_executable_nodes(child, last_added=True)
elif isinstance(node, PythonLeaf):
pass
elif typ == 'decorator':
# decorator
if node.children[-2] == ')':
node = children[-3]
if node != '(':
result += get_executable_nodes(node)
else:
if node.type not in _IGNORE_EXECUTE_NODES and not last_added:
result.append(node)
#last_added = True
for child in node.children:
result += get_executable_nodes(child, last_added)
return result

View File

@@ -17,7 +17,7 @@ tuple(a + 3 for a in [''])
# Some variables within are not defined
# ----------
abcdef = None
abcdef = []
#! 12 name-error
[1 for a in NOT_DEFINFED for b in abcdef if 1]