Format: text/x-python
⬇ Download File (22.7 KB)
   1  from abc import abstractmethod
   2  from inspect import Parameter
   3  from typing import Optional, Tuple
   4  
   5  from parso.tree import search_ancestor
   6  
   7  from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
   8  from jedi.inference.utils import unite
   9  from jedi.inference.base_value import ValueSet, NO_VALUES
  10  from jedi.inference.cache import inference_state_method_cache
  11  from jedi.inference import docstrings
  12  from jedi.cache import memoize_method
  13  from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
  14  from jedi.plugins import plugin_manager
  15  
  16  
  17  def _merge_name_docs(names):
  18      doc = ''
  19      for name in names:
  20          if doc:
  21              # In case we have multiple values, just return all of them
  22              # separated by a few dashes.
  23              doc += '\n' + '-' * 30 + '\n'
  24          doc += name.py__doc__()
  25      return doc
  26  
  27  
  28  class AbstractNameDefinition:
  29      start_pos: Optional[Tuple[int, int]] = None
  30      string_name: str
  31      parent_context = None
  32      tree_name = None
  33      is_value_name = True
  34      """
  35      Used for the Jedi API to know if it's a keyword or an actual name.
  36      """
  37  
  38      @abstractmethod
  39      def infer(self):
  40          raise NotImplementedError
  41  
  42      @abstractmethod
  43      def goto(self):
  44          # Typically names are already definitions and therefore a goto on that
  45          # name will always result on itself.
  46          return {self}
  47  
  48      def get_qualified_names(self, include_module_names=False):
  49          qualified_names = self._get_qualified_names()
  50          if qualified_names is None or not include_module_names:
  51              return qualified_names
  52  
  53          module_names = self.get_root_context().string_names
  54          if module_names is None:
  55              return None
  56          return module_names + qualified_names
  57  
  58      def _get_qualified_names(self):
  59          # By default, a name has no qualified names.
  60          return None
  61  
  62      def get_root_context(self):
  63          return self.parent_context.get_root_context()
  64  
  65      def get_public_name(self):
  66          return self.string_name
  67  
  68      def __repr__(self):
  69          if self.start_pos is None:
  70              return '<%s: string_name=%s>' % (self.__class__.__name__, self.string_name)
  71          return '<%s: string_name=%s start_pos=%s>' % (self.__class__.__name__,
  72                                                        self.string_name, self.start_pos)
  73  
  74      def is_import(self):
  75          return False
  76  
  77      def py__doc__(self):
  78          return ''
  79  
  80      @property
  81      def api_type(self):
  82          return self.parent_context.api_type
  83  
  84      def get_defining_qualified_value(self):
  85          """
  86          Returns either None or the value that is public and qualified. Won't
  87          return a function, because a name in a function is never public.
  88          """
  89          return None
  90  
  91  
  92  class AbstractArbitraryName(AbstractNameDefinition):
  93      """
  94      When you e.g. want to complete dicts keys, you probably want to complete
  95      string literals, which is not really a name, but for Jedi we use this
  96      concept of Name for completions as well.
  97      """
  98      is_value_name = False
  99  
 100      def __init__(self, inference_state, string):
 101          self.inference_state = inference_state
 102          self.string_name = string
 103          self.parent_context = inference_state.builtins_module
 104  
 105      def infer(self):
 106          return NO_VALUES
 107  
 108  
 109  class AbstractTreeName(AbstractNameDefinition):
 110      def __init__(self, parent_context, tree_name):
 111          self.parent_context = parent_context
 112          self.tree_name = tree_name
 113  
 114      def get_qualified_names(self, include_module_names=False):
 115          import_node = search_ancestor(self.tree_name, 'import_name', 'import_from')
 116          # For import nodes we cannot just have names, because it's very unclear
 117          # how they would look like. For now we just ignore them in most cases.
 118          # In case of level == 1, it works always, because it's like a submodule
 119          # lookup.
 120          if import_node is not None and not (import_node.level == 1
 121                                              and self.get_root_context().get_value().is_package()):
 122              # TODO improve the situation for when level is present.
 123              if include_module_names and not import_node.level:
 124                  return tuple(n.value for n in import_node.get_path_for_name(self.tree_name))
 125              else:
 126                  return None
 127  
 128          return super().get_qualified_names(include_module_names)
 129  
 130      def _get_qualified_names(self):
 131          parent_names = self.parent_context.get_qualified_names()
 132          if parent_names is None:
 133              return None
 134          return parent_names + (self.tree_name.value,)
 135  
 136      def get_defining_qualified_value(self):
 137          if self.is_import():
 138              raise NotImplementedError("Shouldn't really happen, please report")
 139          elif self.parent_context:
 140              return self.parent_context.get_value()  # Might be None
 141          return None
 142  
 143      def goto(self):
 144          context = self.parent_context
 145          name = self.tree_name
 146          definition = name.get_definition(import_name_always=True)
 147          if definition is not None:
 148              type_ = definition.type
 149              if type_ == 'expr_stmt':
 150                  # Only take the parent, because if it's more complicated than just
 151                  # a name it's something you can "goto" again.
 152                  is_simple_name = name.parent.type not in ('power', 'trailer')
 153                  if is_simple_name:
 154                      return [self]
 155              elif type_ in ('import_from', 'import_name'):
 156                  from jedi.inference.imports import goto_import
 157                  module_names = goto_import(context, name)
 158                  return module_names
 159              else:
 160                  return [self]
 161          else:
 162              from jedi.inference.imports import follow_error_node_imports_if_possible
 163              values = follow_error_node_imports_if_possible(context, name)
 164              if values is not None:
 165                  return [value.name for value in values]
 166  
 167          par = name.parent
 168          node_type = par.type
 169          if node_type == 'argument' and par.children[1] == '=' and par.children[0] == name:
 170              # Named param goto.
 171              trailer = par.parent
 172              if trailer.type == 'arglist':
 173                  trailer = trailer.parent
 174              if trailer.type != 'classdef':
 175                  if trailer.type == 'decorator':
 176                      value_set = context.infer_node(trailer.children[1])
 177                  else:
 178                      i = trailer.parent.children.index(trailer)
 179                      to_infer = trailer.parent.children[:i]
 180                      if to_infer[0] == 'await':
 181                          to_infer.pop(0)
 182                      value_set = context.infer_node(to_infer[0])
 183                      from jedi.inference.syntax_tree import infer_trailer
 184                      for trailer in to_infer[1:]:
 185                          value_set = infer_trailer(context, value_set, trailer)
 186                  param_names = []
 187                  for value in value_set:
 188                      for signature in value.get_signatures():
 189                          for param_name in signature.get_param_names():
 190                              if param_name.string_name == name.value:
 191                                  param_names.append(param_name)
 192                  return param_names
 193          elif node_type == 'dotted_name':  # Is a decorator.
 194              index = par.children.index(name)
 195              if index > 0:
 196                  new_dotted = deep_ast_copy(par)
 197                  new_dotted.children[index - 1:] = []
 198                  values = context.infer_node(new_dotted)
 199                  return unite(
 200                      value.goto(name, name_context=context)
 201                      for value in values
 202                  )
 203  
 204          if node_type == 'trailer' and par.children[0] == '.':
 205              values = infer_call_of_leaf(context, name, cut_own_trailer=True)
 206              return values.goto(name, name_context=context)
 207          else:
 208              stmt = search_ancestor(
 209                  name, 'expr_stmt', 'lambdef'
 210              ) or name
 211              if stmt.type == 'lambdef':
 212                  stmt = name
 213              return context.goto(name, position=stmt.start_pos)
 214  
 215      def is_import(self):
 216          imp = search_ancestor(self.tree_name, 'import_from', 'import_name')
 217          return imp is not None
 218  
 219      @property
 220      def string_name(self):
 221          return self.tree_name.value
 222  
 223      @property
 224      def start_pos(self):
 225          return self.tree_name.start_pos
 226  
 227  
 228  class ValueNameMixin:
 229      def infer(self):
 230          return ValueSet([self._value])
 231  
 232      def py__doc__(self):
 233          doc = self._value.py__doc__()
 234          if not doc and self._value.is_stub():
 235              from jedi.inference.gradual.conversion import convert_names
 236              names = convert_names([self], prefer_stub_to_compiled=False)
 237              if self not in names:
 238                  return _merge_name_docs(names)
 239          return doc
 240  
 241      def _get_qualified_names(self):
 242          return self._value.get_qualified_names()
 243  
 244      def get_root_context(self):
 245          if self.parent_context is None:  # A module
 246              return self._value.as_context()
 247          return super().get_root_context()
 248  
 249      def get_defining_qualified_value(self):
 250          context = self.parent_context
 251          if context is not None and (context.is_module() or context.is_class()):
 252              return self.parent_context.get_value()  # Might be None
 253          return None
 254  
 255      @property
 256      def api_type(self):
 257          return self._value.api_type
 258  
 259  
 260  class ValueName(ValueNameMixin, AbstractTreeName):
 261      def __init__(self, value, tree_name):
 262          super().__init__(value.parent_context, tree_name)
 263          self._value = value
 264  
 265      def goto(self):
 266          return ValueSet([self._value.name])
 267  
 268  
 269  class TreeNameDefinition(AbstractTreeName):
 270      _API_TYPES = dict(
 271          import_name='module',
 272          import_from='module',
 273          funcdef='function',
 274          param='param',
 275          classdef='class',
 276      )
 277  
 278      def infer(self):
 279          # Refactor this, should probably be here.
 280          from jedi.inference.syntax_tree import tree_name_to_values
 281          return tree_name_to_values(
 282              self.parent_context.inference_state,
 283              self.parent_context,
 284              self.tree_name
 285          )
 286  
 287      @property
 288      def api_type(self):
 289          definition = self.tree_name.get_definition(import_name_always=True)
 290          if definition is None:
 291              return 'statement'
 292          return self._API_TYPES.get(definition.type, 'statement')
 293  
 294      def assignment_indexes(self):
 295          """
 296          Returns an array of tuple(int, node) of the indexes that are used in
 297          tuple assignments.
 298  
 299          For example if the name is ``y`` in the following code::
 300  
 301              x, (y, z) = 2, ''
 302  
 303          would result in ``[(1, xyz_node), (0, yz_node)]``.
 304  
 305          When searching for b in the case ``a, *b, c = [...]`` it will return::
 306  
 307              [(slice(1, -1), abc_node)]
 308          """
 309          indexes = []
 310          is_star_expr = False
 311          node = self.tree_name.parent
 312          compare = self.tree_name
 313          while node is not None:
 314              if node.type in ('testlist', 'testlist_comp', 'testlist_star_expr', 'exprlist'):
 315                  for i, child in enumerate(node.children):
 316                      if child == compare:
 317                          index = int(i / 2)
 318                          if is_star_expr:
 319                              from_end = int((len(node.children) - i) / 2)
 320                              index = slice(index, -from_end)
 321                          indexes.insert(0, (index, node))
 322                          break
 323                  else:
 324                      raise LookupError("Couldn't find the assignment.")
 325                  is_star_expr = False
 326              elif node.type == 'star_expr':
 327                  is_star_expr = True
 328              elif node.type in ('expr_stmt', 'sync_comp_for'):
 329                  break
 330  
 331              compare = node
 332              node = node.parent
 333          return indexes
 334  
 335      @property
 336      def inference_state(self):
 337          # Used by the cache function below
 338          return self.parent_context.inference_state
 339  
 340      @inference_state_method_cache(default='')
 341      def py__doc__(self):
 342          api_type = self.api_type
 343          if api_type in ('function', 'class', 'property'):
 344              if self.parent_context.get_root_context().is_stub():
 345                  from jedi.inference.gradual.conversion import convert_names
 346                  names = convert_names([self], prefer_stub_to_compiled=False)
 347                  if self not in names:
 348                      return _merge_name_docs(names)
 349  
 350              # Make sure the names are not TreeNameDefinitions anymore.
 351              return clean_scope_docstring(self.tree_name.get_definition())
 352  
 353          if api_type == 'module':
 354              names = self.goto()
 355              if self not in names:
 356                  return _merge_name_docs(names)
 357  
 358          if api_type == 'statement' and self.tree_name.is_definition():
 359              return find_statement_documentation(self.tree_name.get_definition())
 360          return ''
 361  
 362  
 363  class _ParamMixin:
 364      def maybe_positional_argument(self, include_star=True):
 365          options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
 366          if include_star:
 367              options.append(Parameter.VAR_POSITIONAL)
 368          return self.get_kind() in options
 369  
 370      def maybe_keyword_argument(self, include_stars=True):
 371          options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
 372          if include_stars:
 373              options.append(Parameter.VAR_KEYWORD)
 374          return self.get_kind() in options
 375  
 376      def _kind_string(self):
 377          kind = self.get_kind()
 378          if kind == Parameter.VAR_POSITIONAL:  # *args
 379              return '*'
 380          if kind == Parameter.VAR_KEYWORD:  # **kwargs
 381              return '**'
 382          return ''
 383  
 384      def get_qualified_names(self, include_module_names=False):
 385          return None
 386  
 387  
 388  class ParamNameInterface(_ParamMixin):
 389      api_type = 'param'
 390  
 391      def get_kind(self):
 392          raise NotImplementedError
 393  
 394      def to_string(self):
 395          raise NotImplementedError
 396  
 397      def get_executed_param_name(self):
 398          """
 399          For dealing with type inference and working around the graph, we
 400          sometimes want to have the param name of the execution. This feels a
 401          bit strange and we might have to refactor at some point.
 402  
 403          For now however it exists to avoid infering params when we don't really
 404          need them (e.g. when we can just instead use annotations.
 405          """
 406          return None
 407  
 408      @property
 409      def star_count(self):
 410          kind = self.get_kind()
 411          if kind == Parameter.VAR_POSITIONAL:
 412              return 1
 413          if kind == Parameter.VAR_KEYWORD:
 414              return 2
 415          return 0
 416  
 417      def infer_default(self):
 418          return NO_VALUES
 419  
 420  
 421  class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
 422      annotation_node = None
 423      default_node = None
 424  
 425      def to_string(self):
 426          output = self._kind_string() + self.get_public_name()
 427          annotation = self.annotation_node
 428          default = self.default_node
 429          if annotation is not None:
 430              output += ': ' + annotation.get_code(include_prefix=False)
 431          if default is not None:
 432              output += '=' + default.get_code(include_prefix=False)
 433          return output
 434  
 435      def get_public_name(self):
 436          name = self.string_name
 437          if name.startswith('__'):
 438              # Params starting with __ are an equivalent to positional only
 439              # variables in typeshed.
 440              name = name[2:]
 441          return name
 442  
 443      def goto(self, **kwargs):
 444          return [self]
 445  
 446  
 447  class _ActualTreeParamName(BaseTreeParamName):
 448      def __init__(self, function_value, tree_name):
 449          super().__init__(
 450              function_value.get_default_param_context(), tree_name)
 451          self.function_value = function_value
 452  
 453      def _get_param_node(self):
 454          return search_ancestor(self.tree_name, 'param')
 455  
 456      @property
 457      def annotation_node(self):
 458          return self._get_param_node().annotation
 459  
 460      def infer_annotation(self, execute_annotation=True, ignore_stars=False):
 461          from jedi.inference.gradual.annotation import infer_param
 462          values = infer_param(
 463              self.function_value, self._get_param_node(),
 464              ignore_stars=ignore_stars)
 465          if execute_annotation:
 466              values = values.execute_annotation()
 467          return values
 468  
 469      def infer_default(self):
 470          node = self.default_node
 471          if node is None:
 472              return NO_VALUES
 473          return self.parent_context.infer_node(node)
 474  
 475      @property
 476      def default_node(self):
 477          return self._get_param_node().default
 478  
 479      def get_kind(self):
 480          tree_param = self._get_param_node()
 481          if tree_param.star_count == 1:  # *args
 482              return Parameter.VAR_POSITIONAL
 483          if tree_param.star_count == 2:  # **kwargs
 484              return Parameter.VAR_KEYWORD
 485  
 486          # Params starting with __ are an equivalent to positional only
 487          # variables in typeshed.
 488          if tree_param.name.value.startswith('__'):
 489              return Parameter.POSITIONAL_ONLY
 490  
 491          parent = tree_param.parent
 492          param_appeared = False
 493          for p in parent.children:
 494              if param_appeared:
 495                  if p == '/':
 496                      return Parameter.POSITIONAL_ONLY
 497              else:
 498                  if p == '*':
 499                      return Parameter.KEYWORD_ONLY
 500                  if p.type == 'param':
 501                      if p.star_count:
 502                          return Parameter.KEYWORD_ONLY
 503                      if p == tree_param:
 504                          param_appeared = True
 505          return Parameter.POSITIONAL_OR_KEYWORD
 506  
 507      def infer(self):
 508          values = self.infer_annotation()
 509          if values:
 510              return values
 511  
 512          doc_params = docstrings.infer_param(self.function_value, self._get_param_node())
 513          return doc_params
 514  
 515  
 516  class AnonymousParamName(_ActualTreeParamName):
 517      @plugin_manager.decorate(name='goto_anonymous_param')
 518      def goto(self):
 519          return super().goto()
 520  
 521      @plugin_manager.decorate(name='infer_anonymous_param')
 522      def infer(self):
 523          values = super().infer()
 524          if values:
 525              return values
 526          from jedi.inference.dynamic_params import dynamic_param_lookup
 527          param = self._get_param_node()
 528          values = dynamic_param_lookup(self.function_value, param.position_index)
 529          if values:
 530              return values
 531  
 532          if param.star_count == 1:
 533              from jedi.inference.value.iterable import FakeTuple
 534              value = FakeTuple(self.function_value.inference_state, [])
 535          elif param.star_count == 2:
 536              from jedi.inference.value.iterable import FakeDict
 537              value = FakeDict(self.function_value.inference_state, {})
 538          elif param.default is None:
 539              return NO_VALUES
 540          else:
 541              return self.function_value.parent_context.infer_node(param.default)
 542          return ValueSet({value})
 543  
 544  
 545  class ParamName(_ActualTreeParamName):
 546      def __init__(self, function_value, tree_name, arguments):
 547          super().__init__(function_value, tree_name)
 548          self.arguments = arguments
 549  
 550      def infer(self):
 551          values = super().infer()
 552          if values:
 553              return values
 554  
 555          return self.get_executed_param_name().infer()
 556  
 557      def get_executed_param_name(self):
 558          from jedi.inference.param import get_executed_param_names
 559          params_names = get_executed_param_names(self.function_value, self.arguments)
 560          return params_names[self._get_param_node().position_index]
 561  
 562  
 563  class ParamNameWrapper(_ParamMixin):
 564      def __init__(self, param_name):
 565          self._wrapped_param_name = param_name
 566  
 567      def __getattr__(self, name):
 568          return getattr(self._wrapped_param_name, name)
 569  
 570      def __repr__(self):
 571          return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)
 572  
 573  
 574  class ImportName(AbstractNameDefinition):
 575      start_pos = (1, 0)
 576      _level = 0
 577  
 578      def __init__(self, parent_context, string_name):
 579          self._from_module_context = parent_context
 580          self.string_name = string_name
 581  
 582      def get_qualified_names(self, include_module_names=False):
 583          if include_module_names:
 584              if self._level:
 585                  assert self._level == 1, "Everything else is not supported for now"
 586                  module_names = self._from_module_context.string_names
 587                  if module_names is None:
 588                      return module_names
 589                  return module_names + (self.string_name,)
 590              return (self.string_name,)
 591          return ()
 592  
 593      @property
 594      def parent_context(self):
 595          m = self._from_module_context
 596          import_values = self.infer()
 597          if not import_values:
 598              return m
 599          # It's almost always possible to find the import or to not find it. The
 600          # importing returns only one value, pretty much always.
 601          return next(iter(import_values)).as_context()
 602  
 603      @memoize_method
 604      def infer(self):
 605          from jedi.inference.imports import Importer
 606          m = self._from_module_context
 607          return Importer(m.inference_state, [self.string_name], m, level=self._level).follow()
 608  
 609      def goto(self):
 610          return [m.name for m in self.infer()]
 611  
 612      @property
 613      def api_type(self):
 614          return 'module'
 615  
 616      def py__doc__(self):
 617          return _merge_name_docs(self.goto())
 618  
 619  
 620  class SubModuleName(ImportName):
 621      _level = 1
 622  
 623  
 624  class NameWrapper:
 625      def __init__(self, wrapped_name):
 626          self._wrapped_name = wrapped_name
 627  
 628      def __getattr__(self, name):
 629          return getattr(self._wrapped_name, name)
 630  
 631      def __repr__(self):
 632          return '%s(%s)' % (self.__class__.__name__, self._wrapped_name)
 633  
 634  
 635  class StubNameMixin:
 636      def py__doc__(self):
 637          from jedi.inference.gradual.conversion import convert_names
 638          # Stubs are not complicated and we can just follow simple statements
 639          # that have an equals in them, because they typically make something
 640          # else public. See e.g. stubs for `requests`.
 641          names = [self]
 642          if self.api_type == 'statement' and '=' in self.tree_name.get_definition().children:
 643              names = [v.name for v in self.infer()]
 644  
 645          names = convert_names(names, prefer_stub_to_compiled=False)
 646          if self in names:
 647              return super().py__doc__()
 648          else:
 649              # We have signatures ourselves in stubs, so don't use signatures
 650              # from the implementation.
 651              return _merge_name_docs(names)
 652  
 653  
 654  # From here on down we make looking up the sys.version_info fast.
 655  class StubName(StubNameMixin, TreeNameDefinition):
 656      def infer(self):
 657          inferred = super().infer()
 658          if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys':
 659              from jedi.inference.gradual.stub_value import VersionInfo
 660              return ValueSet(VersionInfo(c) for c in inferred)
 661          return inferred
 662  
 663  
 664  class ModuleName(ValueNameMixin, AbstractNameDefinition):
 665      start_pos = 1, 0
 666  
 667      def __init__(self, value, name):
 668          self._value = value
 669          self._name = name
 670  
 671      @property
 672      def string_name(self):
 673          return self._name
 674  
 675  
 676  class StubModuleName(StubNameMixin, ModuleName):
 677      pass
 678