| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- from abc import abstractmethod
- from contextlib import contextmanager
- from pathlib import Path
- from typing import Optional
- from parso.tree import search_ancestor
- from parso.python.tree import Name
- from jedi.inference.filters import ParserTreeFilter, MergedFilter, \
- GlobalNameFilter
- from jedi.inference.names import AnonymousParamName, TreeNameDefinition
- from jedi.inference.base_value import NO_VALUES, ValueSet
- from jedi.parser_utils import get_parent_scope
- from jedi import debug
- from jedi import parser_utils
- class AbstractContext:
- # Must be defined: inference_state and tree_node and parent_context as an attribute/property
- def __init__(self, inference_state):
- self.inference_state = inference_state
- self.predefined_names = {}
- @abstractmethod
- def get_filters(self, until_position=None, origin_scope=None):
- raise NotImplementedError
- def goto(self, name_or_str, position):
- from jedi.inference import finder
- filters = _get_global_filters_for_name(
- self, name_or_str if isinstance(name_or_str, Name) else None, position,
- )
- names = finder.filter_name(filters, name_or_str)
- debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names)
- return names
- def py__getattribute__(self, name_or_str, name_context=None, position=None,
- analysis_errors=True):
- """
- :param position: Position of the last statement -> tuple of line, column
- """
- if name_context is None:
- name_context = self
- names = self.goto(name_or_str, position)
- string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
- # This paragraph is currently needed for proper branch type inference
- # (static analysis).
- found_predefined_types = None
- if self.predefined_names and isinstance(name_or_str, Name):
- node = name_or_str
- while node is not None and not parser_utils.is_scope(node):
- node = node.parent
- if node.type in ("if_stmt", "for_stmt", "comp_for", 'sync_comp_for'):
- try:
- name_dict = self.predefined_names[node]
- types = name_dict[string_name]
- except KeyError:
- continue
- else:
- found_predefined_types = types
- break
- if found_predefined_types is not None and names:
- from jedi.inference import flow_analysis
- check = flow_analysis.reachability_check(
- context=self,
- value_scope=self.tree_node,
- node=name_or_str,
- )
- if check is flow_analysis.UNREACHABLE:
- values = NO_VALUES
- else:
- values = found_predefined_types
- else:
- values = ValueSet.from_sets(name.infer() for name in names)
- if not names and not values and analysis_errors:
- if isinstance(name_or_str, Name):
- from jedi.inference import analysis
- message = ("NameError: name '%s' is not defined." % string_name)
- analysis.add(name_context, 'name-error', name_or_str, message)
- debug.dbg('context.names_to_types: %s -> %s', names, values)
- if values:
- return values
- return self._check_for_additional_knowledge(name_or_str, name_context, position)
- def _check_for_additional_knowledge(self, name_or_str, name_context, position):
- name_context = name_context or self
- # Add isinstance and other if/assert knowledge.
- if isinstance(name_or_str, Name) and not name_context.is_instance():
- flow_scope = name_or_str
- base_nodes = [name_context.tree_node]
- if any(b.type in ('comp_for', 'sync_comp_for') for b in base_nodes):
- return NO_VALUES
- from jedi.inference.finder import check_flow_information
- while True:
- flow_scope = get_parent_scope(flow_scope, include_flows=True)
- n = check_flow_information(name_context, flow_scope,
- name_or_str, position)
- if n is not None:
- return n
- if flow_scope in base_nodes:
- break
- return NO_VALUES
- def get_root_context(self):
- parent_context = self.parent_context
- if parent_context is None:
- return self
- return parent_context.get_root_context()
- def is_module(self):
- return False
- def is_builtins_module(self):
- return False
- def is_class(self):
- return False
- def is_stub(self):
- return False
- def is_instance(self):
- return False
- def is_compiled(self):
- return False
- def is_bound_method(self):
- return False
- @abstractmethod
- def py__name__(self):
- raise NotImplementedError
- def get_value(self):
- raise NotImplementedError
- @property
- def name(self):
- return None
- def get_qualified_names(self):
- return ()
- def py__doc__(self):
- return ''
- @contextmanager
- def predefine_names(self, flow_scope, dct):
- predefined = self.predefined_names
- predefined[flow_scope] = dct
- try:
- yield
- finally:
- del predefined[flow_scope]
- class ValueContext(AbstractContext):
- """
- Should be defined, otherwise the API returns empty types.
- """
- def __init__(self, value):
- super().__init__(value.inference_state)
- self._value = value
- @property
- def tree_node(self):
- return self._value.tree_node
- @property
- def parent_context(self):
- return self._value.parent_context
- def is_module(self):
- return self._value.is_module()
- def is_builtins_module(self):
- return self._value == self.inference_state.builtins_module
- def is_class(self):
- return self._value.is_class()
- def is_stub(self):
- return self._value.is_stub()
- def is_instance(self):
- return self._value.is_instance()
- def is_compiled(self):
- return self._value.is_compiled()
- def is_bound_method(self):
- return self._value.is_bound_method()
- def py__name__(self):
- return self._value.py__name__()
- @property
- def name(self):
- return self._value.name
- def get_qualified_names(self):
- return self._value.get_qualified_names()
- def py__doc__(self):
- return self._value.py__doc__()
- def get_value(self):
- return self._value
- def __repr__(self):
- return '%s(%s)' % (self.__class__.__name__, self._value)
- class TreeContextMixin:
- def infer_node(self, node):
- from jedi.inference.syntax_tree import infer_node
- return infer_node(self, node)
- def create_value(self, node):
- from jedi.inference import value
- if node == self.tree_node:
- assert self.is_module()
- return self.get_value()
- parent_context = self.create_context(node)
- if node.type in ('funcdef', 'lambdef'):
- func = value.FunctionValue.from_context(parent_context, node)
- if parent_context.is_class():
- class_value = parent_context.parent_context.create_value(parent_context.tree_node)
- instance = value.AnonymousInstance(
- self.inference_state, parent_context.parent_context, class_value)
- func = value.BoundMethod(
- instance=instance,
- class_context=class_value.as_context(),
- function=func
- )
- return func
- elif node.type == 'classdef':
- return value.ClassValue(self.inference_state, parent_context, node)
- else:
- raise NotImplementedError("Probably shouldn't happen: %s" % node)
- def create_context(self, node):
- def from_scope_node(scope_node, is_nested=True):
- if scope_node == self.tree_node:
- return self
- if scope_node.type in ('funcdef', 'lambdef', 'classdef'):
- return self.create_value(scope_node).as_context()
- elif scope_node.type in ('comp_for', 'sync_comp_for'):
- parent_context = from_scope_node(parent_scope(scope_node.parent))
- if node.start_pos >= scope_node.children[-1].start_pos:
- return parent_context
- return CompForContext(parent_context, scope_node)
- raise Exception("There's a scope that was not managed: %s" % scope_node)
- def parent_scope(node):
- while True:
- node = node.parent
- if parser_utils.is_scope(node):
- return node
- elif node.type in ('argument', 'testlist_comp'):
- if node.children[1].type in ('comp_for', 'sync_comp_for'):
- return node.children[1]
- elif node.type == 'dictorsetmaker':
- for n in node.children[1:4]:
- # In dictionaries it can be pretty much anything.
- if n.type in ('comp_for', 'sync_comp_for'):
- return n
- scope_node = parent_scope(node)
- if scope_node.type in ('funcdef', 'classdef'):
- colon = scope_node.children[scope_node.children.index(':')]
- if node.start_pos < colon.start_pos:
- parent = node.parent
- if not (parent.type == 'param' and parent.name == node):
- scope_node = parent_scope(scope_node)
- return from_scope_node(scope_node, is_nested=True)
- def create_name(self, tree_name):
- definition = tree_name.get_definition()
- if definition and definition.type == 'param' and definition.name == tree_name:
- funcdef = search_ancestor(definition, 'funcdef', 'lambdef')
- func = self.create_value(funcdef)
- return AnonymousParamName(func, tree_name)
- else:
- context = self.create_context(tree_name)
- return TreeNameDefinition(context, tree_name)
- class FunctionContext(TreeContextMixin, ValueContext):
- def get_filters(self, until_position=None, origin_scope=None):
- yield ParserTreeFilter(
- self.inference_state,
- parent_context=self,
- until_position=until_position,
- origin_scope=origin_scope
- )
- class ModuleContext(TreeContextMixin, ValueContext):
- def py__file__(self) -> Optional[Path]:
- return self._value.py__file__() # type: ignore[no-any-return]
- def get_filters(self, until_position=None, origin_scope=None):
- filters = self._value.get_filters(origin_scope)
- # Skip the first filter and replace it.
- next(filters, None)
- yield MergedFilter(
- ParserTreeFilter(
- parent_context=self,
- until_position=until_position,
- origin_scope=origin_scope
- ),
- self.get_global_filter(),
- )
- yield from filters
- def get_global_filter(self):
- return GlobalNameFilter(self)
- @property
- def string_names(self):
- return self._value.string_names
- @property
- def code_lines(self):
- return self._value.code_lines
- def get_value(self):
- """
- This is the only function that converts a context back to a value.
- This is necessary for stub -> python conversion and vice versa. However
- this method shouldn't be moved to AbstractContext.
- """
- return self._value
- class NamespaceContext(TreeContextMixin, ValueContext):
- def get_filters(self, until_position=None, origin_scope=None):
- return self._value.get_filters()
- def get_value(self):
- return self._value
- @property
- def string_names(self):
- return self._value.string_names
- def py__file__(self) -> Optional[Path]:
- return self._value.py__file__() # type: ignore[no-any-return]
- class ClassContext(TreeContextMixin, ValueContext):
- def get_filters(self, until_position=None, origin_scope=None):
- yield self.get_global_filter(until_position, origin_scope)
- def get_global_filter(self, until_position=None, origin_scope=None):
- return ParserTreeFilter(
- parent_context=self,
- until_position=until_position,
- origin_scope=origin_scope
- )
- class CompForContext(TreeContextMixin, AbstractContext):
- def __init__(self, parent_context, comp_for):
- super().__init__(parent_context.inference_state)
- self.tree_node = comp_for
- self.parent_context = parent_context
- def get_filters(self, until_position=None, origin_scope=None):
- yield ParserTreeFilter(self)
- def get_value(self):
- return None
- def py__name__(self):
- return '<comprehension context>'
- def __repr__(self):
- return '%s(%s)' % (self.__class__.__name__, self.tree_node)
- class CompiledContext(ValueContext):
- def get_filters(self, until_position=None, origin_scope=None):
- return self._value.get_filters()
- class CompiledModuleContext(CompiledContext):
- code_lines = None
- def get_value(self):
- return self._value
- @property
- def string_names(self):
- return self._value.string_names
- def py__file__(self) -> Optional[Path]:
- return self._value.py__file__() # type: ignore[no-any-return]
- def _get_global_filters_for_name(context, name_or_none, position):
- # For functions and classes the defaults don't belong to the
- # function and get inferred in the value before the function. So
- # make sure to exclude the function/class name.
- if name_or_none is not None:
- ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef', 'lambdef')
- lambdef = None
- if ancestor == 'lambdef':
- # For lambdas it's even more complicated since parts will
- # be inferred later.
- lambdef = ancestor
- ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef')
- if ancestor is not None:
- colon = ancestor.children[-2]
- if position is not None and position < colon.start_pos:
- if lambdef is None or position < lambdef.children[-2].start_pos:
- position = ancestor.start_pos
- return get_global_filters(context, position, name_or_none)
- def get_global_filters(context, until_position, origin_scope):
- """
- Returns all filters in order of priority for name resolution.
- For global name lookups. The filters will handle name resolution
- themselves, but here we gather possible filters downwards.
- >>> from jedi import Script
- >>> script = Script('''
- ... x = ['a', 'b', 'c']
- ... def func():
- ... y = None
- ... ''')
- >>> module_node = script._module_node
- >>> scope = next(module_node.iter_funcdefs())
- >>> scope
- <Function: func@3-5>
- >>> context = script._get_module_context().create_context(scope)
- >>> filters = list(get_global_filters(context, (4, 0), None))
- First we get the names from the function scope.
- >>> print(filters[0]) # doctest: +ELLIPSIS
- MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
- >>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE
- ['<TreeNameDefinition: string_name=func start_pos=(3, 4)>',
- '<TreeNameDefinition: string_name=x start_pos=(2, 0)>']
- >>> filters[0]._filters[0]._until_position
- (4, 0)
- >>> filters[0]._filters[1]._until_position
- Then it yields the names from one level "lower". In this example, this is
- the module scope (including globals).
- As a side note, you can see, that the position in the filter is None on the
- globals filter, because there the whole module is searched.
- >>> list(filters[1].values()) # package modules -> Also empty.
- []
- >>> sorted(name.string_name for name in filters[2].values()) # Module attributes
- ['__doc__', '__name__', '__package__']
- Finally, it yields the builtin filter, if `include_builtin` is
- true (default).
- >>> list(filters[3].values()) # doctest: +ELLIPSIS
- [...]
- """
- base_context = context
- from jedi.inference.value.function import BaseFunctionExecutionContext
- while context is not None:
- # Names in methods cannot be resolved within the class.
- yield from context.get_filters(
- until_position=until_position,
- origin_scope=origin_scope
- )
- if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)):
- # The position should be reset if the current scope is a function.
- until_position = None
- context = context.parent_context
- b = next(base_context.inference_state.builtins_module.get_filters(), None)
- assert b is not None
- # Add builtins to the global scope.
- yield b
|