mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 06:24:27 +08:00
Cleanup the finder.
This commit is contained in:
@@ -21,9 +21,7 @@ from jedi.common import unite
|
|||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.evaluate import representation as er
|
from jedi.evaluate import representation as er
|
||||||
from jedi.evaluate.instance import AbstractInstanceContext
|
from jedi.evaluate.instance import AbstractInstanceContext
|
||||||
from jedi.evaluate import dynamic
|
|
||||||
from jedi.evaluate import compiled
|
from jedi.evaluate import compiled
|
||||||
from jedi.evaluate import docstrings
|
|
||||||
from jedi.evaluate import pep0484
|
from jedi.evaluate import pep0484
|
||||||
from jedi.evaluate import iterable
|
from jedi.evaluate import iterable
|
||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
@@ -31,64 +29,7 @@ from jedi.evaluate import analysis
|
|||||||
from jedi.evaluate import flow_analysis
|
from jedi.evaluate import flow_analysis
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate import param
|
||||||
from jedi.evaluate import helpers
|
from jedi.evaluate import helpers
|
||||||
from jedi.evaluate.filters import get_global_filters, TreeNameDefinition
|
from jedi.evaluate.filters import get_global_filters
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class NameFinder(object):
|
class NameFinder(object):
|
||||||
@@ -111,8 +52,6 @@ class NameFinder(object):
|
|||||||
:params bool attribute_lookup: Tell to logic if we're accessing the
|
:params bool attribute_lookup: Tell to logic if we're accessing the
|
||||||
attribute or the contents of e.g. a function.
|
attribute or the contents of e.g. a function.
|
||||||
"""
|
"""
|
||||||
# TODO rename scopes to names_dicts
|
|
||||||
|
|
||||||
names = self.filter_name(filters)
|
names = self.filter_name(filters)
|
||||||
if self._found_predefined_types is not None and names:
|
if self._found_predefined_types is not None and names:
|
||||||
check = flow_analysis.reachability_check(
|
check = flow_analysis.reachability_check(
|
||||||
@@ -149,100 +88,6 @@ class NameFinder(object):
|
|||||||
else:
|
else:
|
||||||
return self._context.get_filters(search_global, self._position, origin_scope=origin_scope)
|
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):
|
def filter_name(self, filters):
|
||||||
"""
|
"""
|
||||||
Searches names that are defined in a scope (the different
|
Searches names that are defined in a scope (the different
|
||||||
@@ -270,27 +115,7 @@ class NameFinder(object):
|
|||||||
break
|
break
|
||||||
debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name,
|
debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name,
|
||||||
self._context, names, self._position)
|
self._context, names, self._position)
|
||||||
return list(self._clean_names(names))
|
return list(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
|
|
||||||
|
|
||||||
def _check_getattr(self, inst):
|
def _check_getattr(self, inst):
|
||||||
"""Checks for both __getattr__ and __getattribute__ methods"""
|
"""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])
|
container_types = context.eval_node(node.children[3])
|
||||||
for_types = iterable.py__iter__types(evaluator, container_types, node.children[3])
|
for_types = iterable.py__iter__types(evaluator, container_types, node.children[3])
|
||||||
types = check_tuple_assignments(evaluator, for_types, name)
|
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):
|
elif node.isinstance(tree.ExprStmt):
|
||||||
types = _remove_statements(evaluator, context, node, name)
|
types = _remove_statements(evaluator, context, node, name)
|
||||||
elif node.isinstance(tree.WithStmt):
|
elif node.isinstance(tree.WithStmt):
|
||||||
@@ -383,8 +205,7 @@ def _name_to_types(evaluator, context, name):
|
|||||||
for t in exceptions
|
for t in exceptions
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise DeprecationWarning
|
raise ValueError("Should not happen.")
|
||||||
types = set([node])
|
|
||||||
return types
|
return types
|
||||||
|
|
||||||
|
|
||||||
@@ -438,11 +259,6 @@ def _remove_statements(evaluator, context, stmt, name):
|
|||||||
evaluated.
|
evaluated.
|
||||||
"""
|
"""
|
||||||
types = set()
|
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
|
check_instance = None
|
||||||
|
|
||||||
pep0484types = \
|
pep0484types = \
|
||||||
@@ -459,53 +275,6 @@ def _remove_statements(evaluator, context, stmt, name):
|
|||||||
return types
|
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):
|
def _check_flow_information(context, flow, search_name, pos):
|
||||||
""" Try to find out the type of a variable just with the information that
|
""" 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.::
|
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
|
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):
|
def check_tuple_assignments(evaluator, types, name):
|
||||||
"""
|
"""
|
||||||
Checks if tuples are assigned.
|
Checks if tuples are assigned.
|
||||||
@@ -627,19 +371,3 @@ def check_tuple_assignments(evaluator, types, name):
|
|||||||
return set()
|
return set()
|
||||||
types = lazy_context.infer()
|
types = lazy_context.infer()
|
||||||
return types
|
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
|
|
||||||
|
|||||||
@@ -42,19 +42,6 @@ def deep_ast_copy(obj, parent=None, new_elements=None):
|
|||||||
new_children.append(new_child)
|
new_children.append(new_child)
|
||||||
new_obj.children = new_children
|
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
|
return new_obj
|
||||||
|
|
||||||
if isinstance(obj, tree.BaseNode):
|
if isinstance(obj, tree.BaseNode):
|
||||||
|
|||||||
Reference in New Issue
Block a user