]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
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 | ) |