Cleanup the finder.

This commit is contained in:
Dave Halter
2016-12-17 16:59:21 +01:00
parent 437f915f35
commit 589e1906e4
2 changed files with 3 additions and 288 deletions

View File

@@ -21,9 +21,7 @@ from jedi.common import unite
from jedi import settings
from jedi.evaluate import representation as er
from jedi.evaluate.instance import AbstractInstanceContext
from jedi.evaluate import dynamic
from jedi.evaluate import compiled
from jedi.evaluate import docstrings
from jedi.evaluate import pep0484
from jedi.evaluate import iterable
from jedi.evaluate import imports
@@ -31,64 +29,7 @@ from jedi.evaluate import analysis
from jedi.evaluate import flow_analysis
from jedi.evaluate import param
from jedi.evaluate import helpers
from jedi.evaluate.filters import get_global_filters, TreeNameDefinition
def filter_after_position(names, position, origin=None):
"""
Removes all names after a certain position. If position is None, just
returns the names list.
"""
if position is None:
return names
names_new = []
for n in names:
# Filter positions and also allow list comprehensions and lambdas.
if n.start_pos[0] is not None and n.start_pos < position:
names_new.append(n)
elif isinstance(n.get_definition(), (tree.CompFor, tree.Lambda)):
if origin is not None and origin.get_definition() != n.get_definition():
# This is extremely hacky. A transition that we have to use
# until we get rid of names_dicts.
continue
names_new.append(n)
return names_new
def is_comprehension_name(name, origin):
definition = name.get_definition()
# TODO This is really hacky. It just compares the two definitions. This
# fails tests and is in general just a temporary way.
return definition.type == 'comp_for' and origin.get_definition().type != definition.type
def filter_definition_names(names, origin, position=None):
"""
Filter names that are actual definitions in a scope. Names that are just
used will be ignored.
"""
if not names:
return []
# Just calculate the scope from the first
stmt = names[0].get_definition()
scope = stmt.get_parent_scope()
if not (isinstance(scope, er.FunctionExecution) and
isinstance(scope.base, LambdaWrapper)):
names = filter_after_position(names, position, origin)
names = [name for name in names
if name.is_definition() and not is_comprehension_name(name, origin)]
# Private name mangling (compile.c) disallows access on names
# preceeded by two underscores `__` if used outside of the class. Names
# that also end with two underscores (e.g. __id__) are not affected.
for name in list(names):
if name.value.startswith('__') and not name.value.endswith('__'):
if filter_private_variable(scope, origin):
names.remove(name)
return names
from jedi.evaluate.filters import get_global_filters
class NameFinder(object):
@@ -111,8 +52,6 @@ class NameFinder(object):
:params bool attribute_lookup: Tell to logic if we're accessing the
attribute or the contents of e.g. a function.
"""
# TODO rename scopes to names_dicts
names = self.filter_name(filters)
if self._found_predefined_types is not None and names:
check = flow_analysis.reachability_check(
@@ -149,100 +88,6 @@ class NameFinder(object):
else:
return self._context.get_filters(search_global, self._position, origin_scope=origin_scope)
def names_dict_lookup(self, names_dict, position):
def get_param(scope, el):
if isinstance(el.get_parent_until(tree.Param), tree.Param):
return scope.param_by_name(str(el))
return el
try:
names = names_dict[self._string_name]
if not names: # We want names, otherwise stop.
return []
except KeyError:
return []
names = filter_definition_names(names, self._name, position)
name_scope = None
# Only the names defined in the last position are valid definitions.
last_names = []
for name in reversed(sorted(names, key=lambda name: name.start_pos)):
stmt = name.get_definition()
name_scope = self._evaluator.wrap(stmt.get_parent_scope())
if isinstance(self._context, er.Instance) and not isinstance(name_scope, er.Instance):
# Instances should not be checked for positioning, because we
# don't know in which order the functions are called.
last_names.append(name)
continue
if isinstance(name_scope, compiled.CompiledObject):
# Let's test this. TODO need comment. shouldn't this be
# filtered before?
last_names.append(name)
continue
if isinstance(stmt, er.ModuleContext):
# In case of REPL completion, we can infer modules names that
# don't really have a definition (because they are really just
# namespaces). In this case we can just add it.
last_names.append(name)
continue
if isinstance(name, compiled.CompiledName) \
or isinstance(name, er.InstanceName) and isinstance(name._origin_name, compiled.CompiledName):
last_names.append(name)
continue
if isinstance(self._name, tree.Name):
origin_scope = self._name.get_parent_until(tree.Scope, reverse=True)
scope = self._name
check = None
while True:
scope = scope.parent
if scope.type in ("if_stmt", "for_stmt"):
# TODO try removing for_stmt.
try:
name_dict = self.context.predefined_names[scope]
types = set(name_dict[self._string_name])
except KeyError:
continue
else:
if self._name.start_pos < scope.children[1].end_pos:
# It doesn't make any sense to check if
# statements in the if statement itself, just
# deliver types.
self._found_predefined_types = types
else:
check = flow_analysis.reachability_check(
self._context, self._context, origin_scope)
if check is flow_analysis.UNREACHABLE:
self._found_predefined_types = set()
else:
self._found_predefined_types = types
break
if isinstance(scope, tree.IsScope) or scope is None:
break
else:
origin_scope = None
if isinstance(stmt.parent, compiled.CompiledObject):
# TODO seriously? this is stupid.
continue
check = flow_analysis.reachability_check(self._context, name_scope,
stmt, origin_scope)
if check is not flow_analysis.UNREACHABLE:
last_names.append(name)
if check is flow_analysis.REACHABLE:
break
if isinstance(name_scope, er.FunctionExecution):
# Replace params
return [get_param(name_scope, n) for n in last_names]
return last_names
def filter_name(self, filters):
"""
Searches names that are defined in a scope (the different
@@ -270,27 +115,7 @@ class NameFinder(object):
break
debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name,
self._context, names, self._position)
return list(self._clean_names(names))
def _clean_names(self, names):
"""
``NameFinder.filter_name`` should only output names with correct
wrapper parents. We don't want to see AST classes out in the
evaluation, so remove them already here!
"""
return names
#for n in names:
# definition = n.parent
# if isinstance(definition, (compiled.CompiledObject,
# iterable.BuiltinMethod)):
# # TODO this if should really be removed by changing the type of
# # those classes.
# yield n
# elif definition.type in ('funcdef', 'classdef', 'file_input'):
# yield self._evaluator.wrap(definition).name
# else:
# yield n
return list(names)
def _check_getattr(self, inst):
"""Checks for both __getattr__ and __getattribute__ methods"""
@@ -354,9 +179,6 @@ def _name_to_types(evaluator, context, name):
container_types = context.eval_node(node.children[3])
for_types = iterable.py__iter__types(evaluator, container_types, node.children[3])
types = check_tuple_assignments(evaluator, for_types, name)
elif isinstance(node, tree.Param):
return set() # TODO remove
types = _eval_param(evaluator, context, node)
elif node.isinstance(tree.ExprStmt):
types = _remove_statements(evaluator, context, node, name)
elif node.isinstance(tree.WithStmt):
@@ -383,8 +205,7 @@ def _name_to_types(evaluator, context, name):
for t in exceptions
)
else:
raise DeprecationWarning
types = set([node])
raise ValueError("Should not happen.")
return types
@@ -438,11 +259,6 @@ def _remove_statements(evaluator, context, stmt, name):
evaluated.
"""
types = set()
# Remove the statement docstr stuff for now, that has to be
# implemented with the evaluator class.
#if stmt.docstr:
#res_new.append(stmt)
check_instance = None
pep0484types = \
@@ -459,53 +275,6 @@ def _remove_statements(evaluator, context, stmt, name):
return types
def _eval_param(evaluator, context, param, scope):
res_new = set()
func = param.get_parent_scope()
cls = func.parent.get_parent_until((tree.Class, tree.Function))
from jedi.evaluate.param import ExecutedParam, Arguments
if isinstance(cls, tree.Class) and param.position_nr == 0 \
and not isinstance(param, ExecutedParam):
# This is where we add self - if it has never been
# instantiated.
if isinstance(scope, er.InstanceElement):
res_new.add(scope.instance)
else:
inst = er.Instance(evaluator, context.parent_context.parent_context, context.parent_context,
Arguments(evaluator, context),
is_generated=True)
res_new.add(inst)
return res_new
# Instances are typically faked, if the instance is not called from
# outside. Here we check it for __init__ functions and return.
if isinstance(func, er.InstanceElement) \
and func.instance.is_generated and str(func.name) == '__init__':
param = func.var.params[param.position_nr]
# Add pep0484 and docstring knowledge.
pep0484_hints = pep0484.follow_param(evaluator, param)
doc_params = docstrings.follow_param(evaluator, param)
if pep0484_hints or doc_params:
return list(set(pep0484_hints) | set(doc_params))
if isinstance(param, ExecutedParam):
return res_new | param.eval(evaluator)
else:
# Param owns no information itself.
res_new |= dynamic.search_params(evaluator, param)
if not res_new:
if param.stars:
t = 'tuple' if param.stars == 1 else 'dict'
typ = list(evaluator.BUILTINS.py__getattribute__(t))[0]
res_new = evaluator.execute(typ)
if param.default:
res_new |= evaluator.eval_element(context, param.default)
return res_new
def _check_flow_information(context, flow, search_name, pos):
""" Try to find out the type of a variable just with the information that
is given by the flows: e.g. It is also responsible for assert checks.::
@@ -584,31 +353,6 @@ def _check_isinstance_type(context, element, search_name):
return result
def global_names_dict_generator(evaluator, scope, position):
in_func = False
while scope is not None:
if not (scope.type == 'classdef' and in_func):
# Names in methods cannot be resolved within the class.
for names_dict in scope.names_dicts(True):
yield names_dict, position
if hasattr(scope, 'resets_positions'):
# TODO This is so ugly, seriously. However there's
# currently no good way of influencing
# global_names_dict_generator when it comes to certain
# objects.
position = None
if scope.type == 'funcdef':
# The position should be reset if the current scope is a function.
in_func = True
position = None
scope = evaluator.wrap(scope.get_parent_scope())
# Add builtins to the global scope.
for names_dict in evaluator.BUILTINS.names_dicts(True):
yield names_dict, None
def check_tuple_assignments(evaluator, types, name):
"""
Checks if tuples are assigned.
@@ -627,19 +371,3 @@ def check_tuple_assignments(evaluator, types, name):
return set()
types = lazy_context.infer()
return types
def filter_private_variable(scope, origin_node):
"""Check if a variable is defined inside the same class or outside."""
instance = scope.get_parent_scope()
coming_from = origin_node
while coming_from is not None \
and not isinstance(coming_from, (tree.Class, compiled.CompiledObject)):
coming_from = coming_from.get_parent_scope()
# CompiledObjects don't have double underscore attributes, but Jedi abuses
# those for fakes (builtins.pym -> list).
if isinstance(instance, compiled.CompiledObject):
return instance != coming_from
else:
return isinstance(instance, er.Instance) and instance.base.base != coming_from

View File

@@ -42,19 +42,6 @@ def deep_ast_copy(obj, parent=None, new_elements=None):
new_children.append(new_child)
new_obj.children = new_children
# Copy the names_dict (if there is one).
try:
names_dict = obj.names_dict
except AttributeError:
pass
else:
try:
new_obj.names_dict = new_names_dict = {}
except AttributeError: # Impossible to set CompFor.names_dict
pass
else:
for string, names in names_dict.items():
new_names_dict[string] = [new_elements[n] for n in names]
return new_obj
if isinstance(obj, tree.BaseNode):