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