227 lines
8.0 KiB
Python
227 lines
8.0 KiB
Python
"""
|
|
One of the really important features of |jedi| is to have an option to
|
|
understand code like this::
|
|
|
|
def foo(bar):
|
|
bar. # completion here
|
|
foo(1)
|
|
|
|
There's no doubt wheter bar is an ``int`` or not, but if there's also a call
|
|
like ``foo('str')``, what would happen? Well, we'll just show both. Because
|
|
that's what a human would expect.
|
|
|
|
It works as follows:
|
|
|
|
- |Jedi| sees a param
|
|
- search for function calls named ``foo``
|
|
- execute these calls and check the input.
|
|
"""
|
|
|
|
from jedi import settings
|
|
from jedi import debug
|
|
from jedi.parser_utils import get_parent_scope
|
|
from jedi.inference.cache import inference_state_method_cache
|
|
from jedi.inference.arguments import TreeArguments
|
|
from jedi.inference.param import get_executed_param_names
|
|
from jedi.inference.helpers import is_stdlib_path
|
|
from jedi.inference.utils import to_list
|
|
from jedi.inference.value import instance
|
|
from jedi.inference.base_value import ValueSet, NO_VALUES
|
|
from jedi.inference.references import get_module_contexts_containing_name
|
|
from jedi.inference import recursion
|
|
|
|
|
|
MAX_PARAM_SEARCHES = 20
|
|
|
|
|
|
def _avoid_recursions(func):
|
|
def wrapper(function_value, param_index):
|
|
inf = function_value.inference_state
|
|
with recursion.execution_allowed(inf, function_value.tree_node) as allowed:
|
|
# We need to catch recursions that may occur, because an
|
|
# anonymous functions can create an anonymous parameter that is
|
|
# more or less self referencing.
|
|
if allowed:
|
|
inf.dynamic_params_depth += 1
|
|
try:
|
|
return func(function_value, param_index)
|
|
finally:
|
|
inf.dynamic_params_depth -= 1
|
|
return NO_VALUES
|
|
return wrapper
|
|
|
|
|
|
@debug.increase_indent
|
|
@_avoid_recursions
|
|
def dynamic_param_lookup(function_value, param_index):
|
|
"""
|
|
A dynamic search for param values. If you try to complete a type:
|
|
|
|
>>> def func(foo):
|
|
... foo
|
|
>>> func(1)
|
|
>>> func("")
|
|
|
|
It is not known what the type ``foo`` without analysing the whole code. You
|
|
have to look for all calls to ``func`` to find out what ``foo`` possibly
|
|
is.
|
|
"""
|
|
funcdef = function_value.tree_node
|
|
|
|
if not settings.dynamic_params:
|
|
return NO_VALUES
|
|
|
|
path = function_value.get_root_context().py__file__()
|
|
if path is not None and is_stdlib_path(path):
|
|
# We don't want to search for references in the stdlib. Usually people
|
|
# don't work with it (except if you are a core maintainer, sorry).
|
|
# This makes everything slower. Just disable it and run the tests,
|
|
# you will see the slowdown, especially in 3.6.
|
|
return NO_VALUES
|
|
|
|
if funcdef.type == 'lambdef':
|
|
string_name = _get_lambda_name(funcdef)
|
|
if string_name is None:
|
|
return NO_VALUES
|
|
else:
|
|
string_name = funcdef.name.value
|
|
debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA')
|
|
|
|
module_context = function_value.get_root_context()
|
|
arguments_list = _search_function_arguments(module_context, funcdef, string_name)
|
|
values = ValueSet.from_sets(
|
|
get_executed_param_names(
|
|
function_value, arguments
|
|
)[param_index].infer()
|
|
for arguments in arguments_list
|
|
)
|
|
debug.dbg('Dynamic param result finished', color='MAGENTA')
|
|
return values
|
|
|
|
|
|
@inference_state_method_cache(default=None)
|
|
@to_list
|
|
def _search_function_arguments(module_context, funcdef, string_name):
|
|
"""
|
|
Returns a list of param names.
|
|
"""
|
|
compare_node = funcdef
|
|
if string_name == '__init__':
|
|
cls = get_parent_scope(funcdef)
|
|
if cls.type == 'classdef':
|
|
string_name = cls.name.value
|
|
compare_node = cls
|
|
|
|
found_arguments = False
|
|
i = 0
|
|
inference_state = module_context.inference_state
|
|
|
|
if settings.dynamic_params_for_other_modules:
|
|
module_contexts = get_module_contexts_containing_name(
|
|
inference_state, [module_context], string_name,
|
|
# Limit the amounts of files to be opened massively.
|
|
limit_reduction=5,
|
|
)
|
|
else:
|
|
module_contexts = [module_context]
|
|
|
|
for for_mod_context in module_contexts:
|
|
for name, trailer in _get_potential_nodes(for_mod_context, string_name):
|
|
i += 1
|
|
|
|
# This is a simple way to stop Jedi's dynamic param recursion
|
|
# from going wild: The deeper Jedi's in the recursion, the less
|
|
# code should be inferred.
|
|
if i * inference_state.dynamic_params_depth > MAX_PARAM_SEARCHES:
|
|
return
|
|
|
|
random_context = for_mod_context.create_context(name)
|
|
for arguments in _check_name_for_execution(
|
|
inference_state, random_context, compare_node, name, trailer):
|
|
found_arguments = True
|
|
yield arguments
|
|
|
|
# If there are results after processing a module, we're probably
|
|
# good to process. This is a speed optimization.
|
|
if found_arguments:
|
|
return
|
|
|
|
|
|
def _get_lambda_name(node):
|
|
stmt = node.parent
|
|
if stmt.type == 'expr_stmt':
|
|
first_operator = next(stmt.yield_operators(), None)
|
|
if first_operator == '=':
|
|
first = stmt.children[0]
|
|
if first.type == 'name':
|
|
return first.value
|
|
|
|
return None
|
|
|
|
|
|
def _get_potential_nodes(module_value, func_string_name):
|
|
try:
|
|
names = module_value.tree_node.get_used_names()[func_string_name]
|
|
except KeyError:
|
|
return
|
|
|
|
for name in names:
|
|
bracket = name.get_next_leaf()
|
|
trailer = bracket.parent
|
|
if trailer.type == 'trailer' and bracket == '(':
|
|
yield name, trailer
|
|
|
|
|
|
def _check_name_for_execution(inference_state, context, compare_node, name, trailer):
|
|
from jedi.inference.value.function import BaseFunctionExecutionContext
|
|
|
|
def create_args(value):
|
|
arglist = trailer.children[1]
|
|
if arglist == ')':
|
|
arglist = None
|
|
args = TreeArguments(inference_state, context, arglist, trailer)
|
|
from jedi.inference.value.instance import InstanceArguments
|
|
if value.tree_node.type == 'classdef':
|
|
created_instance = instance.TreeInstance(
|
|
inference_state,
|
|
value.parent_context,
|
|
value,
|
|
args
|
|
)
|
|
return InstanceArguments(created_instance, args)
|
|
else:
|
|
if value.is_bound_method():
|
|
args = InstanceArguments(value.instance, args)
|
|
return args
|
|
|
|
for value in inference_state.infer(context, name):
|
|
value_node = value.tree_node
|
|
if compare_node == value_node:
|
|
yield create_args(value)
|
|
elif isinstance(value.parent_context, BaseFunctionExecutionContext) \
|
|
and compare_node.type == 'funcdef':
|
|
# Here we're trying to find decorators by checking the first
|
|
# parameter. It's not very generic though. Should find a better
|
|
# solution that also applies to nested decorators.
|
|
param_names = value.parent_context.get_param_names()
|
|
if len(param_names) != 1:
|
|
continue
|
|
values = param_names[0].infer()
|
|
if [v.tree_node for v in values] == [compare_node]:
|
|
# Found a decorator.
|
|
module_context = context.get_root_context()
|
|
execution_context = value.as_context(create_args(value))
|
|
potential_nodes = _get_potential_nodes(module_context, param_names[0].string_name)
|
|
for name, trailer in potential_nodes:
|
|
if value_node.start_pos < name.start_pos < value_node.end_pos:
|
|
random_context = execution_context.create_context(name)
|
|
iterator = _check_name_for_execution(
|
|
inference_state,
|
|
random_context,
|
|
compare_node,
|
|
name,
|
|
trailer
|
|
)
|
|
for arguments in iterator:
|
|
yield arguments
|