1
0
forked from VimPlug/jedi

Remove the module path from the parser tree.

Some static analysis tests are still failing.
This commit is contained in:
Dave Halter
2017-03-27 18:13:32 +02:00
parent b60ec024fa
commit 8a35a04439
14 changed files with 102 additions and 101 deletions

View File

@@ -142,7 +142,11 @@ class Script(object):
@cache.memoize_method @cache.memoize_method
def _get_module(self): def _get_module(self):
module = er.ModuleContext(self._evaluator, self._get_module_node()) module = er.ModuleContext(
self._evaluator,
self._get_module_node(),
self.path
)
imports.add_module(self._evaluator, module.name.string_name, module) imports.add_module(self._evaluator, module.name.string_name, module)
return module return module
@@ -398,7 +402,8 @@ class Interpreter(Script):
return interpreter.MixedModuleContext( return interpreter.MixedModuleContext(
self._evaluator, self._evaluator,
parser_module, parser_module,
self.namespaces self.namespaces,
path=self.path
) )

View File

@@ -300,7 +300,7 @@ def cache_call_signatures(evaluator, context, bracket_leaf, code_lines, user_pos
whole = '\n'.join(other_lines + [before_cursor]) whole = '\n'.join(other_lines + [before_cursor])
before_bracket = re.match(r'.*\(', whole, re.DOTALL) before_bracket = re.match(r'.*\(', whole, re.DOTALL)
module_path = bracket_leaf.get_root_node().path module_path = context.get_root_context().py__file__()
if module_path is None: if module_path is None:
yield None # Don't cache! yield None # Don't cache!
else: else:

View File

@@ -12,12 +12,12 @@ class MixedModuleContext(Context):
resets_positions = True resets_positions = True
type = 'mixed_module' type = 'mixed_module'
def __init__(self, evaluator, tree_module, namespaces): def __init__(self, evaluator, tree_module, namespaces, path):
self.evaluator = evaluator self.evaluator = evaluator
self._namespaces = namespaces self._namespaces = namespaces
self._namespace_objects = [type('jedi_namespace', (), n) for n in namespaces] self._namespace_objects = [type('jedi_namespace', (), n) for n in namespaces]
self._module_context = ModuleContext(evaluator, tree_module) self._module_context = ModuleContext(evaluator, tree_module, path=path)
self.tree_node = tree_module self.tree_node = tree_module
def get_node(self): def get_node(self):
@@ -33,7 +33,7 @@ class MixedModuleContext(Context):
self.evaluator, self.evaluator,
parent_context=self, parent_context=self,
compiled_object=compiled_object, compiled_object=compiled_object,
tree_name=self.tree_node.name tree_context=self._module_context
) )
for filter in mixed_object.get_filters(*args, **kwargs): for filter in mixed_object.get_filters(*args, **kwargs):
yield filter yield filter

View File

@@ -5,8 +5,6 @@ from jedi import debug
from jedi.parser.python import tree from jedi.parser.python import tree
from jedi.evaluate.compiled import CompiledObject from jedi.evaluate.compiled import CompiledObject
from jedi.common import unite
CODES = { CODES = {
'attribute-error': (1, AttributeError, 'Potential AttributeError.'), 'attribute-error': (1, AttributeError, 'Potential AttributeError.'),
@@ -82,7 +80,9 @@ def add(node_context, error_name, node, message=None, typ=Error, payload=None):
if _check_for_exception_catch(node_context, node, exception, payload): if _check_for_exception_catch(node_context, node, exception, payload):
return return
module_path = node.get_root_node().path # TODO this path is probably not right
module_context = node_context.get_root_context()
module_path = module_context.py__file__()
instance = typ(error_name, module_path, node.start_pos, message) instance = typ(error_name, module_path, node.start_pos, message)
debug.warning(str(instance), format=False) debug.warning(str(instance), format=False)
node_context.evaluator.analysis.append(instance) node_context.evaluator.analysis.append(instance)

View File

@@ -29,25 +29,12 @@ class MixedObject(object):
fewer special cases, because we in Python you don't have the same freedoms fewer special cases, because we in Python you don't have the same freedoms
to modify the runtime. to modify the runtime.
""" """
def __init__(self, evaluator, parent_context, compiled_object, tree_name): def __init__(self, evaluator, parent_context, compiled_object, tree_context):
self.evaluator = evaluator self.evaluator = evaluator
self.parent_context = parent_context
self.compiled_object = compiled_object self.compiled_object = compiled_object
self._context = tree_context
self.obj = compiled_object.obj self.obj = compiled_object.obj
self._tree_name = tree_name
name_module = tree_name.get_root_node()
if parent_context.tree_node.get_root_node() != name_module:
from jedi.evaluate.representation import ModuleContext
module_context = ModuleContext(evaluator, name_module)
name = compiled_object.get_root_context().py__name__()
imports.add_module(evaluator, name, module_context)
else:
module_context = parent_context.get_root_context()
self._context = module_context.create_context(
tree_name.parent,
node_is_context=True,
node_is_object=True
)
# We have to overwrite everything that has to do with trailers, name # We have to overwrite everything that has to do with trailers, name
# lookups and filters to make it possible to route name lookups towards # lookups and filters to make it possible to route name lookups towards
@@ -132,10 +119,10 @@ def find_syntax_node_name(evaluator, python_object):
path = inspect.getsourcefile(python_object) path = inspect.getsourcefile(python_object)
except TypeError: except TypeError:
# The type might not be known (e.g. class_with_dict.__weakref__) # The type might not be known (e.g. class_with_dict.__weakref__)
return None return None, None
if path is None or not os.path.exists(path): if path is None or not os.path.exists(path):
# The path might not exist or be e.g. <stdin>. # The path might not exist or be e.g. <stdin>.
return None return None, None
module = _load_module(evaluator, path, python_object) module = _load_module(evaluator, path, python_object)
@@ -143,17 +130,17 @@ def find_syntax_node_name(evaluator, python_object):
# We don't need to check names for modules, because there's not really # We don't need to check names for modules, because there's not really
# a way to write a module in a module in Python (and also __name__ can # a way to write a module in a module in Python (and also __name__ can
# be something like ``email.utils``). # be something like ``email.utils``).
return module.name return module, path
name_str = python_object.__name__ name_str = python_object.__name__
if name_str == '<lambda>': if name_str == '<lambda>':
return None # It's too hard to find lambdas. return None, None # It's too hard to find lambdas.
# Doesn't always work (e.g. os.stat_result) # Doesn't always work (e.g. os.stat_result)
try: try:
names = module.used_names[name_str] names = module.used_names[name_str]
except KeyError: except KeyError:
return None return None, None
names = [n for n in names if n.is_definition()] names = [n for n in names if n.is_definition()]
try: try:
@@ -170,22 +157,44 @@ def find_syntax_node_name(evaluator, python_object):
# There's a chance that the object is not available anymore, because # There's a chance that the object is not available anymore, because
# the code has changed in the background. # the code has changed in the background.
if line_names: if line_names:
return line_names[-1] return line_names[-1].parent, path
# It's really hard to actually get the right definition, here as a last # It's really hard to actually get the right definition, here as a last
# resort we just return the last one. This chance might lead to odd # resort we just return the last one. This chance might lead to odd
# completions at some points but will lead to mostly correct type # completions at some points but will lead to mostly correct type
# inference, because people tend to define a public name in a module only # inference, because people tend to define a public name in a module only
# once. # once.
return names[-1] return names[-1].parent, path
@compiled.compiled_objects_cache('mixed_cache') @compiled.compiled_objects_cache('mixed_cache')
def create(evaluator, obj, parent_context=None, *args): def create(evaluator, obj, parent_context=None, *args):
tree_name = find_syntax_node_name(evaluator, obj) tree_node, path = find_syntax_node_name(evaluator, obj)
compiled_object = compiled.create( compiled_object = compiled.create(
evaluator, obj, parent_context=parent_context.compiled_object) evaluator, obj, parent_context=parent_context.compiled_object)
if tree_name is None: if tree_node is None:
return compiled_object return compiled_object
return MixedObject(evaluator, parent_context, compiled_object, tree_name)
module_node = tree_node.get_root_node()
if parent_context.tree_node.get_root_node() == module_node:
module_context = parent_context.get_root_context()
else:
from jedi.evaluate.representation import ModuleContext
module_context = ModuleContext(evaluator, module_node, path=path)
name = compiled_object.get_root_context().py__name__()
imports.add_module(evaluator, name, module_context)
tree_context = module_context.create_context(
tree_node,
node_is_context=True,
node_is_object=True
)
return MixedObject(
evaluator,
parent_context,
compiled_object,
tree_context=tree_context
)

View File

@@ -459,7 +459,7 @@ def _load_module(evaluator, path=None, code=None, sys_path=None, parent_module=N
module_node = parse(code=code, path=path, cache=True, diff_cache=True) module_node = parse(code=code, path=path, cache=True, diff_cache=True)
from jedi.evaluate.representation import ModuleContext from jedi.evaluate.representation import ModuleContext
return ModuleContext(evaluator, module_node) return ModuleContext(evaluator, module_node, path=path)
else: else:
return compiled.load_module(evaluator, path) return compiled.load_module(evaluator, path)
@@ -489,7 +489,7 @@ def get_modules_containing_name(evaluator, modules, name):
return None return None
else: else:
module_node = parser_cache_item.parser.get_root_node() module_node = parser_cache_item.parser.get_root_node()
return er.ModuleContext(evaluator, module_node) return er.ModuleContext(evaluator, module_node, path=path)
def check_fs(path): def check_fs(path):
with open(path, 'rb') as f: with open(path, 'rb') as f:

View File

@@ -150,7 +150,11 @@ def py__getitem__(context, typ, node):
return context.eval_node(nodes[0]) return context.eval_node(nodes[0])
from jedi.evaluate.representation import ModuleContext from jedi.evaluate.representation import ModuleContext
typing = ModuleContext(context.evaluator, _get_typing_replacement_module()) typing = ModuleContext(
context.evaluator,
module_node=_get_typing_replacement_module(),
path=None
)
factories = typing.py__getattribute__("factory") factories = typing.py__getattribute__("factory")
assert len(factories) == 1 assert len(factories) == 1
factory = list(factories)[0] factory = list(factories)[0]

View File

@@ -57,7 +57,8 @@ from jedi.evaluate import imports
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \ from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
GlobalNameFilter, DictFilter, ContextName, AbstractNameDefinition, \ GlobalNameFilter, DictFilter, ContextName, AbstractNameDefinition, \
ParamName, AnonymousInstanceParamName, TreeNameDefinition ParamName, AnonymousInstanceParamName, TreeNameDefinition, \
ContextNameMixin
from jedi.evaluate.dynamic import search_params from jedi.evaluate.dynamic import search_params
from jedi.evaluate import context from jedi.evaluate import context
@@ -406,13 +407,26 @@ class ModuleAttributeName(AbstractNameDefinition):
) )
class ModuleName(ContextNameMixin, AbstractNameDefinition):
start_pos = 1, 0
def __init__(self, context, name):
self._context = context
self._name = name
@property
def string_name(self):
return self._name
class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)): class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
api_type = 'module' api_type = 'module'
parent_context = None parent_context = None
def __init__(self, evaluator, module_node): def __init__(self, evaluator, module_node, path):
super(ModuleContext, self).__init__(evaluator, parent_context=None) super(ModuleContext, self).__init__(evaluator, parent_context=None)
self.tree_node = module_node self.tree_node = module_node
self._path = path
def get_filters(self, search_global, until_position=None, origin_scope=None): def get_filters(self, search_global, until_position=None, origin_scope=None):
yield ParserTreeFilter( yield ParserTreeFilter(
@@ -449,10 +463,21 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
# All the additional module attributes are strings. # All the additional module attributes are strings.
return dict((n, ModuleAttributeName(self, n)) for n in names) return dict((n, ModuleAttributeName(self, n)) for n in names)
@property
def _string_name(self):
""" This is used for the goto functions. """
if self._path is None:
return '' # no path -> empty name
else:
sep = (re.escape(os.path.sep),) * 2
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self._path)
# Remove PEP 3149 names
return re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
@property @property
@memoize_default() @memoize_default()
def name(self): def name(self):
return ContextName(self, self.tree_node.name) return ModuleName(self, self._string_name)
def _get_init_directory(self): def _get_init_directory(self):
""" """
@@ -478,10 +503,10 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
""" """
In contrast to Python's __file__ can be None. In contrast to Python's __file__ can be None.
""" """
if self.tree_node.path is None: if self._path is None:
return None return None
return os.path.abspath(self.tree_node.path) return os.path.abspath(self._path)
def py__package__(self): def py__package__(self):
if self._get_init_directory() is None: if self._get_init_directory() is None:
@@ -539,7 +564,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
Lists modules in the directory of this module (if this module is a Lists modules in the directory of this module (if this module is a
package). package).
""" """
path = self.tree_node.path path = self._path
names = {} names = {}
if path is not None and path.endswith(os.path.sep + '__init__.py'): if path is not None and path.endswith(os.path.sep + '__init__.py'):
mods = pkgutil.iter_modules([os.path.dirname(path)]) mods = pkgutil.iter_modules([os.path.dirname(path)])
@@ -559,6 +584,11 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
def py__class__(self): def py__class__(self):
return compiled.get_special_object(self.evaluator, 'MODULE_CLASS') return compiled.get_special_object(self.evaluator, 'MODULE_CLASS')
def __repr__(self):
return "<%s: %s@%s-%s>" % (
self.__class__.__name__, self._string_name,
self.tree_node.start_pos[0], self.tree_node.end_pos[0])
class ImplicitNSName(AbstractNameDefinition): class ImplicitNSName(AbstractNameDefinition):
""" """

View File

@@ -222,7 +222,7 @@ def _get_paths_from_buildout_script(evaluator, buildout_script_path):
return return
from jedi.evaluate.representation import ModuleContext from jedi.evaluate.representation import ModuleContext
for path in _check_module(ModuleContext(evaluator, module_node)): for path in _check_module(ModuleContext(evaluator, module_node, buildout_script_path)):
yield path yield path

View File

@@ -144,8 +144,6 @@ class ParserWithRecovery(Parser):
:param grammar: The grammar object of pgen2. Loaded by load_grammar. :param grammar: The grammar object of pgen2. Loaded by load_grammar.
:param source: The codebase for the parser. Must be unicode. :param source: The codebase for the parser. Must be unicode.
:param module_path: The path of the module in the file system, may be None.
:type module_path: str
""" """
def __init__(self, grammar, source, module_path=None): def __init__(self, grammar, source, module_path=None):
super(ParserWithRecovery, self).__init__( super(ParserWithRecovery, self).__init__(
@@ -155,7 +153,6 @@ class ParserWithRecovery(Parser):
self.syntax_errors = [] self.syntax_errors = []
self._omit_dedent_list = [] self._omit_dedent_list = []
self._indent_counter = 0 self._indent_counter = 0
self._module_path = module_path
# TODO do print absolute import detection here. # TODO do print absolute import detection here.
# try: # try:
@@ -169,7 +166,6 @@ class ParserWithRecovery(Parser):
def parse(self, tokens): def parse(self, tokens):
root_node = super(ParserWithRecovery, self).parse(self._tokenize(tokens)) root_node = super(ParserWithRecovery, self).parse(self._tokenize(tokens))
root_node.path = self._module_path
return root_node return root_node
def error_recovery(self, grammar, stack, arcs, typ, value, start_pos, prefix, def error_recovery(self, grammar, stack, arcs, typ, value, start_pos, prefix,

View File

@@ -32,8 +32,7 @@ For static analysis purposes there exists a method called
``nodes_to_execute`` on all nodes and leaves. It's documented in the static ``nodes_to_execute`` on all nodes and leaves. It's documented in the static
anaylsis documentation. anaylsis documentation.
""" """
import os
import re
from inspect import cleandoc from inspect import cleandoc
from itertools import chain from itertools import chain
import textwrap import textwrap
@@ -277,7 +276,7 @@ class Name(_LeafWithoutNewlines):
return False return False
stmt = self.get_definition() stmt = self.get_definition()
if stmt.type in ('funcdef', 'classdef', 'file_input', 'param'): if stmt.type in ('funcdef', 'classdef', 'param'):
return self == stmt.name return self == stmt.name
elif stmt.type == 'for_stmt': elif stmt.type == 'for_stmt':
return self.start_pos < stmt.children[2].start_pos return self.start_pos < stmt.children[2].start_pos
@@ -412,13 +411,10 @@ class Scope(PythonBaseNode, DocstringMixin):
return True return True
def __repr__(self): def __repr__(self):
try:
name = self.path
except AttributeError:
try: try:
name = self.name name = self.name
except AttributeError: except AttributeError:
name = self.command name = ''
return "<%s: %s@%s-%s>" % (type(self).__name__, name, return "<%s: %s@%s-%s>" % (type(self).__name__, name,
self.start_pos[0], self.end_pos[0]) self.start_pos[0], self.end_pos[0])
@@ -442,38 +438,13 @@ class Module(Scope):
Depending on the underlying parser this may be a full module or just a part Depending on the underlying parser this may be a full module or just a part
of a module. of a module.
""" """
__slots__ = ('path', '_used_names', '_name') __slots__ = ('_used_names', '_name')
type = 'file_input' type = 'file_input'
def __init__(self, children): def __init__(self, children):
"""
Initialize :class:`Module`.
:type path: str
:arg path: File path to this module.
.. todo:: Document `top_module`.
"""
super(Module, self).__init__(children) super(Module, self).__init__(children)
self.path = None # Set later.
self._used_names = None self._used_names = None
@property
def name(self):
""" This is used for the goto functions. """
if self.path is None:
string = '' # no path -> empty name
else:
sep = (re.escape(os.path.sep),) * 2
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self.path)
# Remove PEP 3149 names
string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
# Positions are not real, but a module starts at (1, 0)
p = (1, 0)
name = Name(string, p)
name.parent = self
return name
@property @property
def has_explicit_absolute_import(self): def has_explicit_absolute_import(self):
""" """

View File

@@ -338,7 +338,7 @@ class TestGotoAssignments(TestCase):
n = nms[0].goto_assignments()[0] n = nms[0].goto_assignments()[0]
assert n.name == 'json' assert n.name == 'json'
assert n.type == 'module' assert n.type == 'module'
assert n._name.tree_name.get_definition().type == 'file_input' assert n._name._context.tree_node.type == 'file_input'
assert nms[1].name == 'foo' assert nms[1].name == 'foo'
assert nms[1].type == 'module' assert nms[1].type == 'module'
@@ -347,7 +347,7 @@ class TestGotoAssignments(TestCase):
assert len(ass) == 1 assert len(ass) == 1
assert ass[0].name == 'json' assert ass[0].name == 'json'
assert ass[0].type == 'module' assert ass[0].type == 'module'
assert ass[0]._name.tree_name.get_definition().type == 'file_input' assert ass[0]._name._context.tree_node.type == 'file_input'
def test_added_equals_to_params(): def test_added_equals_to_params():

View File

@@ -15,7 +15,7 @@ from ..helpers import cwd_at
def check_module_test(code): def check_module_test(code):
grammar = load_grammar() grammar = load_grammar()
module_context = ModuleContext(Evaluator(grammar), parse(code)) module_context = ModuleContext(Evaluator(grammar), parse(code), path=None)
return _check_module(module_context) return _check_module(module_context)
@@ -68,7 +68,7 @@ def test_sys_path_with_modifications():
path = os.path.abspath(os.path.join(os.curdir, 'module_name.py')) path = os.path.abspath(os.path.join(os.curdir, 'module_name.py'))
grammar = load_grammar() grammar = load_grammar()
module_node = parse(code, path=path) module_node = parse(code, path=path)
module_context = ModuleContext(Evaluator(grammar), module_node) module_context = ModuleContext(Evaluator(grammar), module_node, path=path)
paths = sys_path_with_modifications(module_context.evaluator, module_context) paths = sys_path_with_modifications(module_context.evaluator, module_context)
assert '/tmp/.buildout/eggs/important_package.egg' in paths assert '/tmp/.buildout/eggs/important_package.egg' in paths

View File

@@ -88,20 +88,6 @@ class TestImports():
assert imp.end_pos == (1, len('import math')) assert imp.end_pos == (1, len('import math'))
def test_module():
module = parse('asdf', path='example.py')
name = module.name
assert str(name) == 'example'
assert name.start_pos == (1, 0)
assert name.end_pos == (1, 7)
module = parse('asdf')
name = module.name
assert str(name) == ''
assert name.start_pos == (1, 0)
assert name.end_pos == (1, 0)
def test_end_pos(): def test_end_pos():
s = dedent(''' s = dedent('''
x = ['a', 'b', 'c'] x = ['a', 'b', 'c']