]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | from abc import abstractmethod |
2 | from contextlib import contextmanager | |
3 | from pathlib import Path | |
4 | from typing import Optional | |
5 | ||
6 | from parso.tree import search_ancestor | |
7 | from parso.python.tree import Name | |
8 | ||
9 | from jedi.inference.filters import ParserTreeFilter, MergedFilter, \ | |
10 | GlobalNameFilter | |
11 | from jedi.inference.names import AnonymousParamName, TreeNameDefinition | |
12 | from jedi.inference.base_value import NO_VALUES, ValueSet | |
13 | from jedi.parser_utils import get_parent_scope | |
14 | from jedi import debug | |
15 | from jedi import parser_utils | |
16 | ||
17 | ||
18 | class AbstractContext: | |
19 | # Must be defined: inference_state and tree_node and parent_context as an attribute/property | |
20 | ||
21 | def __init__(self, inference_state): | |
22 | self.inference_state = inference_state | |
23 | self.predefined_names = {} | |
24 | ||
25 | @abstractmethod | |
26 | def get_filters(self, until_position=None, origin_scope=None): | |
27 | raise NotImplementedError | |
28 | ||
29 | def goto(self, name_or_str, position): | |
30 | from jedi.inference import finder | |
31 | filters = _get_global_filters_for_name( | |
32 | self, name_or_str if isinstance(name_or_str, Name) else None, position, | |
33 | ) | |
34 | names = finder.filter_name(filters, name_or_str) | |
35 | debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names) | |
36 | return names | |
37 | ||
38 | def py__getattribute__(self, name_or_str, name_context=None, position=None, | |
39 | analysis_errors=True): | |
40 | """ | |
41 | :param position: Position of the last statement -> tuple of line, column | |
42 | """ | |
43 | if name_context is None: | |
44 | name_context = self | |
45 | names = self.goto(name_or_str, position) | |
46 | ||
47 | string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str | |
48 | ||
49 | # This paragraph is currently needed for proper branch type inference | |
50 | # (static analysis). | |
51 | found_predefined_types = None | |
52 | if self.predefined_names and isinstance(name_or_str, Name): | |
53 | node = name_or_str | |
54 | while node is not None and not parser_utils.is_scope(node): | |
55 | node = node.parent | |
56 | if node.type in ("if_stmt", "for_stmt", "comp_for", 'sync_comp_for'): | |
57 | try: | |
58 | name_dict = self.predefined_names[node] | |
59 | types = name_dict[string_name] | |
60 | except KeyError: | |
61 | continue | |
62 | else: | |
63 | found_predefined_types = types | |
64 | break | |
65 | if found_predefined_types is not None and names: | |
66 | from jedi.inference import flow_analysis | |
67 | check = flow_analysis.reachability_check( | |
68 | context=self, | |
69 | value_scope=self.tree_node, | |
70 | node=name_or_str, | |
71 | ) | |
72 | if check is flow_analysis.UNREACHABLE: | |
73 | values = NO_VALUES | |
74 | else: | |
75 | values = found_predefined_types | |
76 | else: | |
77 | values = ValueSet.from_sets(name.infer() for name in names) | |
78 | ||
79 | if not names and not values and analysis_errors: | |
80 | if isinstance(name_or_str, Name): | |
81 | from jedi.inference import analysis | |
82 | message = ("NameError: name '%s' is not defined." % string_name) | |
83 | analysis.add(name_context, 'name-error', name_or_str, message) | |
84 | ||
85 | debug.dbg('context.names_to_types: %s -> %s', names, values) | |
86 | if values: | |
87 | return values | |
88 | return self._check_for_additional_knowledge(name_or_str, name_context, position) | |
89 | ||
90 | def _check_for_additional_knowledge(self, name_or_str, name_context, position): | |
91 | name_context = name_context or self | |
92 | # Add isinstance and other if/assert knowledge. | |
93 | if isinstance(name_or_str, Name) and not name_context.is_instance(): | |
94 | flow_scope = name_or_str | |
95 | base_nodes = [name_context.tree_node] | |
96 | ||
97 | if any(b.type in ('comp_for', 'sync_comp_for') for b in base_nodes): | |
98 | return NO_VALUES | |
99 | from jedi.inference.finder import check_flow_information | |
100 | while True: | |
101 | flow_scope = get_parent_scope(flow_scope, include_flows=True) | |
102 | n = check_flow_information(name_context, flow_scope, | |
103 | name_or_str, position) | |
104 | if n is not None: | |
105 | return n | |
106 | if flow_scope in base_nodes: | |
107 | break | |
108 | return NO_VALUES | |
109 | ||
110 | def get_root_context(self): | |
111 | parent_context = self.parent_context | |
112 | if parent_context is None: | |
113 | return self | |
114 | return parent_context.get_root_context() | |
115 | ||
116 | def is_module(self): | |
117 | return False | |
118 | ||
119 | def is_builtins_module(self): | |
120 | return False | |
121 | ||
122 | def is_class(self): | |
123 | return False | |
124 | ||
125 | def is_stub(self): | |
126 | return False | |
127 | ||
128 | def is_instance(self): | |
129 | return False | |
130 | ||
131 | def is_compiled(self): | |
132 | return False | |
133 | ||
134 | def is_bound_method(self): | |
135 | return False | |
136 | ||
137 | @abstractmethod | |
138 | def py__name__(self): | |
139 | raise NotImplementedError | |
140 | ||
141 | def get_value(self): | |
142 | raise NotImplementedError | |
143 | ||
144 | @property | |
145 | def name(self): | |
146 | return None | |
147 | ||
148 | def get_qualified_names(self): | |
149 | return () | |
150 | ||
151 | def py__doc__(self): | |
152 | return '' | |
153 | ||
154 | @contextmanager | |
155 | def predefine_names(self, flow_scope, dct): | |
156 | predefined = self.predefined_names | |
157 | predefined[flow_scope] = dct | |
158 | try: | |
159 | yield | |
160 | finally: | |
161 | del predefined[flow_scope] | |
162 | ||
163 | ||
164 | class ValueContext(AbstractContext): | |
165 | """ | |
166 | Should be defined, otherwise the API returns empty types. | |
167 | """ | |
168 | def __init__(self, value): | |
169 | super().__init__(value.inference_state) | |
170 | self._value = value | |
171 | ||
172 | @property | |
173 | def tree_node(self): | |
174 | return self._value.tree_node | |
175 | ||
176 | @property | |
177 | def parent_context(self): | |
178 | return self._value.parent_context | |
179 | ||
180 | def is_module(self): | |
181 | return self._value.is_module() | |
182 | ||
183 | def is_builtins_module(self): | |
184 | return self._value == self.inference_state.builtins_module | |
185 | ||
186 | def is_class(self): | |
187 | return self._value.is_class() | |
188 | ||
189 | def is_stub(self): | |
190 | return self._value.is_stub() | |
191 | ||
192 | def is_instance(self): | |
193 | return self._value.is_instance() | |
194 | ||
195 | def is_compiled(self): | |
196 | return self._value.is_compiled() | |
197 | ||
198 | def is_bound_method(self): | |
199 | return self._value.is_bound_method() | |
200 | ||
201 | def py__name__(self): | |
202 | return self._value.py__name__() | |
203 | ||
204 | @property | |
205 | def name(self): | |
206 | return self._value.name | |
207 | ||
208 | def get_qualified_names(self): | |
209 | return self._value.get_qualified_names() | |
210 | ||
211 | def py__doc__(self): | |
212 | return self._value.py__doc__() | |
213 | ||
214 | def get_value(self): | |
215 | return self._value | |
216 | ||
217 | def __repr__(self): | |
218 | return '%s(%s)' % (self.__class__.__name__, self._value) | |
219 | ||
220 | ||
221 | class TreeContextMixin: | |
222 | def infer_node(self, node): | |
223 | from jedi.inference.syntax_tree import infer_node | |
224 | return infer_node(self, node) | |
225 | ||
226 | def create_value(self, node): | |
227 | from jedi.inference import value | |
228 | ||
229 | if node == self.tree_node: | |
230 | assert self.is_module() | |
231 | return self.get_value() | |
232 | ||
233 | parent_context = self.create_context(node) | |
234 | ||
235 | if node.type in ('funcdef', 'lambdef'): | |
236 | func = value.FunctionValue.from_context(parent_context, node) | |
237 | if parent_context.is_class(): | |
238 | class_value = parent_context.parent_context.create_value(parent_context.tree_node) | |
239 | instance = value.AnonymousInstance( | |
240 | self.inference_state, parent_context.parent_context, class_value) | |
241 | func = value.BoundMethod( | |
242 | instance=instance, | |
243 | class_context=class_value.as_context(), | |
244 | function=func | |
245 | ) | |
246 | return func | |
247 | elif node.type == 'classdef': | |
248 | return value.ClassValue(self.inference_state, parent_context, node) | |
249 | else: | |
250 | raise NotImplementedError("Probably shouldn't happen: %s" % node) | |
251 | ||
252 | def create_context(self, node): | |
253 | def from_scope_node(scope_node, is_nested=True): | |
254 | if scope_node == self.tree_node: | |
255 | return self | |
256 | ||
257 | if scope_node.type in ('funcdef', 'lambdef', 'classdef'): | |
258 | return self.create_value(scope_node).as_context() | |
259 | elif scope_node.type in ('comp_for', 'sync_comp_for'): | |
260 | parent_context = from_scope_node(parent_scope(scope_node.parent)) | |
261 | if node.start_pos >= scope_node.children[-1].start_pos: | |
262 | return parent_context | |
263 | return CompForContext(parent_context, scope_node) | |
264 | raise Exception("There's a scope that was not managed: %s" % scope_node) | |
265 | ||
266 | def parent_scope(node): | |
267 | while True: | |
268 | node = node.parent | |
269 | ||
270 | if parser_utils.is_scope(node): | |
271 | return node | |
272 | elif node.type in ('argument', 'testlist_comp'): | |
273 | if node.children[1].type in ('comp_for', 'sync_comp_for'): | |
274 | return node.children[1] | |
275 | elif node.type == 'dictorsetmaker': | |
276 | for n in node.children[1:4]: | |
277 | # In dictionaries it can be pretty much anything. | |
278 | if n.type in ('comp_for', 'sync_comp_for'): | |
279 | return n | |
280 | ||
281 | scope_node = parent_scope(node) | |
282 | if scope_node.type in ('funcdef', 'classdef'): | |
283 | colon = scope_node.children[scope_node.children.index(':')] | |
284 | if node.start_pos < colon.start_pos: | |
285 | parent = node.parent | |
286 | if not (parent.type == 'param' and parent.name == node): | |
287 | scope_node = parent_scope(scope_node) | |
288 | return from_scope_node(scope_node, is_nested=True) | |
289 | ||
290 | def create_name(self, tree_name): | |
291 | definition = tree_name.get_definition() | |
292 | if definition and definition.type == 'param' and definition.name == tree_name: | |
293 | funcdef = search_ancestor(definition, 'funcdef', 'lambdef') | |
294 | func = self.create_value(funcdef) | |
295 | return AnonymousParamName(func, tree_name) | |
296 | else: | |
297 | context = self.create_context(tree_name) | |
298 | return TreeNameDefinition(context, tree_name) | |
299 | ||
300 | ||
301 | class FunctionContext(TreeContextMixin, ValueContext): | |
302 | def get_filters(self, until_position=None, origin_scope=None): | |
303 | yield ParserTreeFilter( | |
304 | self.inference_state, | |
305 | parent_context=self, | |
306 | until_position=until_position, | |
307 | origin_scope=origin_scope | |
308 | ) | |
309 | ||
310 | ||
311 | class ModuleContext(TreeContextMixin, ValueContext): | |
312 | def py__file__(self) -> Optional[Path]: | |
313 | return self._value.py__file__() # type: ignore[no-any-return] | |
314 | ||
315 | def get_filters(self, until_position=None, origin_scope=None): | |
316 | filters = self._value.get_filters(origin_scope) | |
317 | # Skip the first filter and replace it. | |
318 | next(filters, None) | |
319 | yield MergedFilter( | |
320 | ParserTreeFilter( | |
321 | parent_context=self, | |
322 | until_position=until_position, | |
323 | origin_scope=origin_scope | |
324 | ), | |
325 | self.get_global_filter(), | |
326 | ) | |
327 | yield from filters | |
328 | ||
329 | def get_global_filter(self): | |
330 | return GlobalNameFilter(self) | |
331 | ||
332 | @property | |
333 | def string_names(self): | |
334 | return self._value.string_names | |
335 | ||
336 | @property | |
337 | def code_lines(self): | |
338 | return self._value.code_lines | |
339 | ||
340 | def get_value(self): | |
341 | """ | |
342 | This is the only function that converts a context back to a value. | |
343 | This is necessary for stub -> python conversion and vice versa. However | |
344 | this method shouldn't be moved to AbstractContext. | |
345 | """ | |
346 | return self._value | |
347 | ||
348 | ||
349 | class NamespaceContext(TreeContextMixin, ValueContext): | |
350 | def get_filters(self, until_position=None, origin_scope=None): | |
351 | return self._value.get_filters() | |
352 | ||
353 | def get_value(self): | |
354 | return self._value | |
355 | ||
356 | @property | |
357 | def string_names(self): | |
358 | return self._value.string_names | |
359 | ||
360 | def py__file__(self) -> Optional[Path]: | |
361 | return self._value.py__file__() # type: ignore[no-any-return] | |
362 | ||
363 | ||
364 | class ClassContext(TreeContextMixin, ValueContext): | |
365 | def get_filters(self, until_position=None, origin_scope=None): | |
366 | yield self.get_global_filter(until_position, origin_scope) | |
367 | ||
368 | def get_global_filter(self, until_position=None, origin_scope=None): | |
369 | return ParserTreeFilter( | |
370 | parent_context=self, | |
371 | until_position=until_position, | |
372 | origin_scope=origin_scope | |
373 | ) | |
374 | ||
375 | ||
376 | class CompForContext(TreeContextMixin, AbstractContext): | |
377 | def __init__(self, parent_context, comp_for): | |
378 | super().__init__(parent_context.inference_state) | |
379 | self.tree_node = comp_for | |
380 | self.parent_context = parent_context | |
381 | ||
382 | def get_filters(self, until_position=None, origin_scope=None): | |
383 | yield ParserTreeFilter(self) | |
384 | ||
385 | def get_value(self): | |
386 | return None | |
387 | ||
388 | def py__name__(self): | |
389 | return '<comprehension context>' | |
390 | ||
391 | def __repr__(self): | |
392 | return '%s(%s)' % (self.__class__.__name__, self.tree_node) | |
393 | ||
394 | ||
395 | class CompiledContext(ValueContext): | |
396 | def get_filters(self, until_position=None, origin_scope=None): | |
397 | return self._value.get_filters() | |
398 | ||
399 | ||
400 | class CompiledModuleContext(CompiledContext): | |
401 | code_lines = None | |
402 | ||
403 | def get_value(self): | |
404 | return self._value | |
405 | ||
406 | @property | |
407 | def string_names(self): | |
408 | return self._value.string_names | |
409 | ||
410 | def py__file__(self) -> Optional[Path]: | |
411 | return self._value.py__file__() # type: ignore[no-any-return] | |
412 | ||
413 | ||
414 | def _get_global_filters_for_name(context, name_or_none, position): | |
415 | # For functions and classes the defaults don't belong to the | |
416 | # function and get inferred in the value before the function. So | |
417 | # make sure to exclude the function/class name. | |
418 | if name_or_none is not None: | |
419 | ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef', 'lambdef') | |
420 | lambdef = None | |
421 | if ancestor == 'lambdef': | |
422 | # For lambdas it's even more complicated since parts will | |
423 | # be inferred later. | |
424 | lambdef = ancestor | |
425 | ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef') | |
426 | if ancestor is not None: | |
427 | colon = ancestor.children[-2] | |
428 | if position is not None and position < colon.start_pos: | |
429 | if lambdef is None or position < lambdef.children[-2].start_pos: | |
430 | position = ancestor.start_pos | |
431 | ||
432 | return get_global_filters(context, position, name_or_none) | |
433 | ||
434 | ||
435 | def get_global_filters(context, until_position, origin_scope): | |
436 | """ | |
437 | Returns all filters in order of priority for name resolution. | |
438 | ||
439 | For global name lookups. The filters will handle name resolution | |
440 | themselves, but here we gather possible filters downwards. | |
441 | ||
442 | >>> from jedi import Script | |
443 | >>> script = Script(''' | |
444 | ... x = ['a', 'b', 'c'] | |
445 | ... def func(): | |
446 | ... y = None | |
447 | ... ''') | |
448 | >>> module_node = script._module_node | |
449 | >>> scope = next(module_node.iter_funcdefs()) | |
450 | >>> scope | |
451 | <Function: func@3-5> | |
452 | >>> context = script._get_module_context().create_context(scope) | |
453 | >>> filters = list(get_global_filters(context, (4, 0), None)) | |
454 | ||
455 | First we get the names from the function scope. | |
456 | ||
457 | >>> print(filters[0]) # doctest: +ELLIPSIS | |
458 | MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>) | |
459 | >>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE | |
460 | ['<TreeNameDefinition: string_name=func start_pos=(3, 4)>', | |
461 | '<TreeNameDefinition: string_name=x start_pos=(2, 0)>'] | |
462 | >>> filters[0]._filters[0]._until_position | |
463 | (4, 0) | |
464 | >>> filters[0]._filters[1]._until_position | |
465 | ||
466 | Then it yields the names from one level "lower". In this example, this is | |
467 | the module scope (including globals). | |
468 | As a side note, you can see, that the position in the filter is None on the | |
469 | globals filter, because there the whole module is searched. | |
470 | ||
471 | >>> list(filters[1].values()) # package modules -> Also empty. | |
472 | [] | |
473 | >>> sorted(name.string_name for name in filters[2].values()) # Module attributes | |
474 | ['__doc__', '__name__', '__package__'] | |
475 | ||
476 | Finally, it yields the builtin filter, if `include_builtin` is | |
477 | true (default). | |
478 | ||
479 | >>> list(filters[3].values()) # doctest: +ELLIPSIS | |
480 | [...] | |
481 | """ | |
482 | base_context = context | |
483 | from jedi.inference.value.function import BaseFunctionExecutionContext | |
484 | while context is not None: | |
485 | # Names in methods cannot be resolved within the class. | |
486 | yield from context.get_filters( | |
487 | until_position=until_position, | |
488 | origin_scope=origin_scope | |
489 | ) | |
490 | if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)): | |
491 | # The position should be reset if the current scope is a function. | |
492 | until_position = None | |
493 | ||
494 | context = context.parent_context | |
495 | ||
496 | b = next(base_context.inference_state.builtins_module.get_filters(), None) | |
497 | assert b is not None | |
498 | # Add builtins to the global scope. | |
499 | yield b |