Format: text/x-python
⬇ Download File (16.8 KB)
   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
 500