dynamic_params.py 8.0 KB

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