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

View File

@@ -450,7 +450,8 @@ class Evaluator(object):
raise NotImplementedError raise NotImplementedError
if def_.type == 'expr_stmt' and name in def_.get_defined_names(): if def_.type == 'expr_stmt' and name in def_.get_defined_names():
return self.eval_statement(context, def_, name) 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]) container_types = self.eval_element(context, def_.children[3])
cn = ContextualizedNode(context, def_.children[3]) cn = ContextualizedNode(context, def_.children[3])
for_types = iterable.py__iter__types(self, container_types, cn) 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>] [<ImportName: import os@1,0>]
See also :attr:`Scope.subscopes` and :attr:`Scope.statements`. 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 inspect import cleandoc
from itertools import chain from itertools import chain
import textwrap import textwrap
import abc
from jedi._compatibility import (Python3Method, is_py3, utf8_repr, from jedi._compatibility import (Python3Method, is_py3, utf8_repr,
literal_eval, unicode) literal_eval, unicode)
@@ -143,10 +138,6 @@ class PythonMixin():
# Default is not being a scope. Just inherit from Scope. # Default is not being a scope. Just inherit from Scope.
return False return False
@abc.abstractmethod
def nodes_to_execute(self, last_added=False):
raise NotImplementedError()
@Python3Method @Python3Method
def name_for_position(self, position): def name_for_position(self, position):
for c in self.children: for c in self.children:
@@ -256,10 +247,6 @@ class Name(_LeafWithoutNewlines):
'comp_for', 'with_stmt') \ 'comp_for', 'with_stmt') \
and self in stmt.get_defined_names() 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): class Literal(PythonLeaf):
__slots__ = () __slots__ = ()
@@ -431,13 +418,6 @@ class Module(Scope):
return True return True
return False 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 @property
def used_names(self): def used_names(self):
if self._used_names is None: if self._used_names is None:
@@ -464,13 +444,6 @@ class Decorator(PythonBaseNode):
type = 'decorator' type = 'decorator'
__slots__ = () __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): class ClassOrFunc(Scope):
__slots__ = () __slots__ = ()
@@ -528,34 +501,6 @@ class Class(ClassOrFunc):
sub.get_call_signature(func_name=self.name), docstr) sub.get_call_signature(func_name=self.name), docstr)
return 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): def _create_params(parent, argslist_list):
""" """
@@ -676,21 +621,6 @@ class Function(ClassOrFunc):
docstr = self.raw_doc docstr = self.raw_doc
return '%s\n\n%s' % (self.get_call_signature(), docstr) 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): class Lambda(Function):
""" """
@@ -734,14 +664,6 @@ class Lambda(Function):
def yields(self): def yields(self):
return [] 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): def __repr__(self):
return "<%s@%s>" % (self.__class__.__name__, self.start_pos) 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' '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): def get_branch_keyword(self, node):
start_pos = node.start_pos start_pos = node.start_pos
if not (self.start_pos < start_pos <= self.end_pos): if not (self.start_pos < start_pos <= self.end_pos):
@@ -857,16 +774,6 @@ class TryStmt(Flow):
elif node == 'except': elif node == 'except':
yield None 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): class WithStmt(Flow):
type = 'with_stmt' type = 'with_stmt'
@@ -887,16 +794,6 @@ class WithStmt(Flow):
if node.type == 'with_item': if node.type == 'with_item':
return node.children[0] 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): class Import(PythonBaseNode):
__slots__ = () __slots__ = ()
@@ -919,14 +816,6 @@ class Import(PythonBaseNode):
def is_star_import(self): def is_star_import(self):
return self.children[-1] == '*' 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): class ImportFrom(Import):
type = 'import_from' type = 'import_from'
@@ -1070,12 +959,6 @@ class KeywordStatement(PythonBaseNode):
def keyword(self): def keyword(self):
return self.children[0].value 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): class AssertStmt(KeywordStatement):
__slots__ = () __slots__ = ()
@@ -1093,13 +976,6 @@ class GlobalStmt(KeywordStatement):
def get_global_names(self): def get_global_names(self):
return self.children[1::2] 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): class ReturnStmt(KeywordStatement):
__slots__ = () __slots__ = ()
@@ -1112,12 +988,6 @@ class YieldExpr(PythonBaseNode):
def type(self): def type(self):
return 'yield_expr' 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): def _defined_names(current):
""" """
@@ -1167,14 +1037,6 @@ class ExprStmt(PythonBaseNode, DocstringMixin):
except IndexError: except IndexError:
return None 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): class Param(PythonBaseNode):
""" """
@@ -1273,16 +1135,3 @@ class CompFor(PythonBaseNode):
def get_defined_names(self): def get_defined_names(self):
return _defined_names(self.children[1]) 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: else:
return self.value return self.value
def nodes_to_execute(self, last_added=False):
return []
@property @property
def end_pos(self): def end_pos(self):
""" """
@@ -276,13 +273,6 @@ class Node(BaseNode):
"""Concrete implementation for interior nodes.""" """Concrete implementation for interior nodes."""
__slots__ = ('type',) __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): def __init__(self, type, children):
""" """
Initializer. Initializer.
@@ -295,19 +285,6 @@ class Node(BaseNode):
super(Node, self).__init__(children) super(Node, self).__init__(children)
self.type = type 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): def __repr__(self):
return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children) return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children)
@@ -319,9 +296,6 @@ class ErrorNode(BaseNode):
__slots__ = () __slots__ = ()
type = 'error_node' type = 'error_node'
def nodes_to_execute(self, last_added=False):
return []
class ErrorLeaf(Leaf): class ErrorLeaf(Leaf):
""" """
@@ -337,5 +311,3 @@ class ErrorLeaf(Leaf):
def __repr__(self): def __repr__(self):
return "<%s: %s:%s, %s)>" % \ return "<%s: %s:%s, %s)>" % \
(type(self).__name__, self.original_type, repr(self.value), self.start_pos) (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 # Some variables within are not defined
# ---------- # ----------
abcdef = None abcdef = []
#! 12 name-error #! 12 name-error
[1 for a in NOT_DEFINFED for b in abcdef if 1] [1 for a in NOT_DEFINFED for b in abcdef if 1]