Format: text/x-python
⬇ Download File (8.0 KB)
   1  """
   2  One of the really important features of |jedi| is to have an option to
   3  understand code like this::
   4  
   5      def foo(bar):
   6          bar. # completion here
   7      foo(1)
   8  
   9  There's no doubt wheter bar is an ``int`` or not, but if there's also a call
  10  like ``foo('str')``, what would happen? Well, we'll just show both. Because
  11  that's what a human would expect.
  12  
  13  It works as follows:
  14  
  15  - |Jedi| sees a param
  16  - search for function calls named ``foo``
  17  - execute these calls and check the input.
  18  """
  19  
  20  from jedi import settings
  21  from jedi import debug
  22  from jedi.parser_utils import get_parent_scope
  23  from jedi.inference.cache import inference_state_method_cache
  24  from jedi.inference.arguments import TreeArguments
  25  from jedi.inference.param import get_executed_param_names
  26  from jedi.inference.helpers import is_stdlib_path
  27  from jedi.inference.utils import to_list
  28  from jedi.inference.value import instance
  29  from jedi.inference.base_value import ValueSet, NO_VALUES
  30  from jedi.inference.references import get_module_contexts_containing_name
  31  from jedi.inference import recursion
  32  
  33  
  34  MAX_PARAM_SEARCHES = 20
  35  
  36  
  37  def _avoid_recursions(func):
  38      def wrapper(function_value, param_index):
  39          inf = function_value.inference_state
  40          with recursion.execution_allowed(inf, function_value.tree_node) as allowed:
  41              # We need to catch recursions that may occur, because an
  42              # anonymous functions can create an anonymous parameter that is
  43              # more or less self referencing.
  44              if allowed:
  45                  inf.dynamic_params_depth += 1
  46                  try:
  47                      return func(function_value, param_index)
  48                  finally:
  49                      inf.dynamic_params_depth -= 1
  50              return NO_VALUES
  51      return wrapper
  52  
  53  
  54  @debug.increase_indent
  55  @_avoid_recursions
  56  def dynamic_param_lookup(function_value, param_index):
  57      """
  58      A dynamic search for param values. If you try to complete a type:
  59  
  60      >>> def func(foo):
  61      ...     foo
  62      >>> func(1)
  63      >>> func("")
  64  
  65      It is not known what the type ``foo`` without analysing the whole code. You
  66      have to look for all calls to ``func`` to find out what ``foo`` possibly
  67      is.
  68      """
  69      if not function_value.inference_state.do_dynamic_params_search:
  70          return NO_VALUES
  71  
  72      funcdef = function_value.tree_node
  73  
  74      path = function_value.get_root_context().py__file__()
  75      if path is not None and is_stdlib_path(path):
  76          # We don't want to search for references in the stdlib. Usually people
  77          # don't work with it (except if you are a core maintainer, sorry).
  78          # This makes everything slower. Just disable it and run the tests,
  79          # you will see the slowdown, especially in 3.6.
  80          return NO_VALUES
  81  
  82      if funcdef.type == 'lambdef':
  83          string_name = _get_lambda_name(funcdef)
  84          if string_name is None:
  85              return NO_VALUES
  86      else:
  87          string_name = funcdef.name.value
  88      debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA')
  89  
  90      module_context = function_value.get_root_context()
  91      arguments_list = _search_function_arguments(module_context, funcdef, string_name)
  92      values = ValueSet.from_sets(
  93          get_executed_param_names(
  94              function_value, arguments
  95          )[param_index].infer()
  96          for arguments in arguments_list
  97      )
  98      debug.dbg('Dynamic param result finished', color='MAGENTA')
  99      return values
 100  
 101  
 102  @inference_state_method_cache(default=None)
 103  @to_list
 104  def _search_function_arguments(module_context, funcdef, string_name):
 105      """
 106      Returns a list of param names.
 107      """
 108      compare_node = funcdef
 109      if string_name == '__init__':
 110          cls = get_parent_scope(funcdef)
 111          if cls.type == 'classdef':
 112              string_name = cls.name.value
 113              compare_node = cls
 114  
 115      found_arguments = False
 116      i = 0
 117      inference_state = module_context.inference_state
 118  
 119      if settings.dynamic_params_for_other_modules:
 120          module_contexts = get_module_contexts_containing_name(
 121              inference_state, [module_context], string_name,
 122              # Limit the amounts of files to be opened massively.
 123              limit_reduction=5,
 124          )
 125      else:
 126          module_contexts = [module_context]
 127  
 128      for for_mod_context in module_contexts:
 129          for name, trailer in _get_potential_nodes(for_mod_context, string_name):
 130              i += 1
 131  
 132              # This is a simple way to stop Jedi's dynamic param recursion
 133              # from going wild: The deeper Jedi's in the recursion, the less
 134              # code should be inferred.
 135              if i * inference_state.dynamic_params_depth > MAX_PARAM_SEARCHES:
 136                  return
 137  
 138              random_context = for_mod_context.create_context(name)
 139              for arguments in _check_name_for_execution(
 140                      inference_state, random_context, compare_node, name, trailer):
 141                  found_arguments = True
 142                  yield arguments
 143  
 144          # If there are results after processing a module, we're probably
 145          # good to process. This is a speed optimization.
 146          if found_arguments:
 147              return
 148  
 149  
 150  def _get_lambda_name(node):
 151      stmt = node.parent
 152      if stmt.type == 'expr_stmt':
 153          first_operator = next(stmt.yield_operators(), None)
 154          if first_operator == '=':
 155              first = stmt.children[0]
 156              if first.type == 'name':
 157                  return first.value
 158  
 159      return None
 160  
 161  
 162  def _get_potential_nodes(module_value, func_string_name):
 163      try:
 164          names = module_value.tree_node.get_used_names()[func_string_name]
 165      except KeyError:
 166          return
 167  
 168      for name in names:
 169          bracket = name.get_next_leaf()
 170          trailer = bracket.parent
 171          if trailer.type == 'trailer' and bracket == '(':
 172              yield name, trailer
 173  
 174  
 175  def _check_name_for_execution(inference_state, context, compare_node, name, trailer):
 176      from jedi.inference.value.function import BaseFunctionExecutionContext
 177  
 178      def create_args(value):
 179          arglist = trailer.children[1]
 180          if arglist == ')':
 181              arglist = None
 182          args = TreeArguments(inference_state, context, arglist, trailer)
 183          from jedi.inference.value.instance import InstanceArguments
 184          if value.tree_node.type == 'classdef':
 185              created_instance = instance.TreeInstance(
 186                  inference_state,
 187                  value.parent_context,
 188                  value,
 189                  args
 190              )
 191              return InstanceArguments(created_instance, args)
 192          else:
 193              if value.is_bound_method():
 194                  args = InstanceArguments(value.instance, args)
 195              return args
 196  
 197      for value in inference_state.infer(context, name):
 198          value_node = value.tree_node
 199          if compare_node == value_node:
 200              yield create_args(value)
 201          elif isinstance(value.parent_context, BaseFunctionExecutionContext) \
 202                  and compare_node.type == 'funcdef':
 203              # Here we're trying to find decorators by checking the first
 204              # parameter. It's not very generic though. Should find a better
 205              # solution that also applies to nested decorators.
 206              param_names = value.parent_context.get_param_names()
 207              if len(param_names) != 1:
 208                  continue
 209              values = param_names[0].infer()
 210              if [v.tree_node for v in values] == [compare_node]:
 211                  # Found a decorator.
 212                  module_context = context.get_root_context()
 213                  execution_context = value.as_context(create_args(value))
 214                  potential_nodes = _get_potential_nodes(module_context, param_names[0].string_name)
 215                  for name, trailer in potential_nodes:
 216                      if value_node.start_pos < name.start_pos < value_node.end_pos:
 217                          random_context = execution_context.create_context(name)
 218                          yield from _check_name_for_execution(
 219                              inference_state,
 220                              random_context,
 221                              compare_node,
 222                              name,
 223                              trailer
 224                          )
 225