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