Format: text/x-python
⬇ Download File (11.1 KB)
   1  """
   2  Used only for REPL Completion.
   3  """
   4  
   5  import inspect
   6  from pathlib import Path
   7  
   8  from jedi.parser_utils import get_cached_code_lines
   9  
  10  from jedi import settings
  11  from jedi.cache import memoize_method
  12  from jedi.inference import compiled
  13  from jedi.file_io import FileIO
  14  from jedi.inference.names import NameWrapper
  15  from jedi.inference.base_value import ValueSet, ValueWrapper, NO_VALUES
  16  from jedi.inference.value import ModuleValue
  17  from jedi.inference.cache import inference_state_function_cache, \
  18      inference_state_method_cache
  19  from jedi.inference.compiled.access import ALLOWED_GETITEM_TYPES, get_api_type
  20  from jedi.inference.gradual.conversion import to_stub
  21  from jedi.inference.context import CompiledContext, CompiledModuleContext, \
  22      TreeContextMixin
  23  
  24  _sentinel = object()
  25  
  26  
  27  class MixedObject(ValueWrapper):
  28      """
  29      A ``MixedObject`` is used in two ways:
  30  
  31      1. It uses the default logic of ``parser.python.tree`` objects,
  32      2. except for getattr calls and signatures. The names dicts are generated
  33         in a fashion like ``CompiledValue``.
  34  
  35      This combined logic makes it possible to provide more powerful REPL
  36      completion. It allows side effects that are not noticable with the default
  37      parser structure to still be completable.
  38  
  39      The biggest difference from CompiledValue to MixedObject is that we are
  40      generally dealing with Python code and not with C code. This will generate
  41      fewer special cases, because we in Python you don't have the same freedoms
  42      to modify the runtime.
  43      """
  44      def __init__(self, compiled_value, tree_value):
  45          super().__init__(tree_value)
  46          self.compiled_value = compiled_value
  47          self.access_handle = compiled_value.access_handle
  48  
  49      def get_filters(self, *args, **kwargs):
  50          yield MixedObjectFilter(
  51              self.inference_state, self.compiled_value, self._wrapped_value)
  52  
  53      def get_signatures(self):
  54          # Prefer `inspect.signature` over somehow analyzing Python code. It
  55          # should be very precise, especially for stuff like `partial`.
  56          return self.compiled_value.get_signatures()
  57  
  58      @inference_state_method_cache(default=NO_VALUES)
  59      def py__call__(self, arguments):
  60          # Fallback to the wrapped value if to stub returns no values.
  61          values = to_stub(self._wrapped_value)
  62          if not values:
  63              values = self._wrapped_value
  64          return values.py__call__(arguments)
  65  
  66      def get_safe_value(self, default=_sentinel):
  67          if default is _sentinel:
  68              return self.compiled_value.get_safe_value()
  69          else:
  70              return self.compiled_value.get_safe_value(default)
  71  
  72      @property
  73      def array_type(self):
  74          return self.compiled_value.array_type
  75  
  76      def get_key_values(self):
  77          return self.compiled_value.get_key_values()
  78  
  79      def py__simple_getitem__(self, index):
  80          python_object = self.compiled_value.access_handle.access._obj
  81          if type(python_object) in ALLOWED_GETITEM_TYPES:
  82              return self.compiled_value.py__simple_getitem__(index)
  83          return self._wrapped_value.py__simple_getitem__(index)
  84  
  85      def negate(self):
  86          return self.compiled_value.negate()
  87  
  88      def _as_context(self):
  89          if self.parent_context is None:
  90              return MixedModuleContext(self)
  91          return MixedContext(self)
  92  
  93      def __repr__(self):
  94          return '<%s: %s; %s>' % (
  95              type(self).__name__,
  96              self.access_handle.get_repr(),
  97              self._wrapped_value,
  98          )
  99  
 100  
 101  class MixedContext(CompiledContext, TreeContextMixin):
 102      @property
 103      def compiled_value(self):
 104          return self._value.compiled_value
 105  
 106  
 107  class MixedModuleContext(CompiledModuleContext, MixedContext):
 108      pass
 109  
 110  
 111  class MixedName(NameWrapper):
 112      """
 113      The ``CompiledName._compiled_value`` is our MixedObject.
 114      """
 115      def __init__(self, wrapped_name, parent_tree_value):
 116          super().__init__(wrapped_name)
 117          self._parent_tree_value = parent_tree_value
 118  
 119      @property
 120      def start_pos(self):
 121          values = list(self.infer())
 122          if not values:
 123              # This means a start_pos that doesn't exist (compiled objects).
 124              return 0, 0
 125          return values[0].name.start_pos
 126  
 127      @memoize_method
 128      def infer(self):
 129          compiled_value = self._wrapped_name.infer_compiled_value()
 130          tree_value = self._parent_tree_value
 131          if tree_value.is_instance() or tree_value.is_class():
 132              tree_values = tree_value.py__getattribute__(self.string_name)
 133              if compiled_value.is_function():
 134                  return ValueSet({MixedObject(compiled_value, v) for v in tree_values})
 135  
 136          module_context = tree_value.get_root_context()
 137          return _create(self._inference_state, compiled_value, module_context)
 138  
 139  
 140  class MixedObjectFilter(compiled.CompiledValueFilter):
 141      def __init__(self, inference_state, compiled_value, tree_value):
 142          super().__init__(inference_state, compiled_value)
 143          self._tree_value = tree_value
 144  
 145      def _create_name(self, *args, **kwargs):
 146          return MixedName(
 147              super()._create_name(*args, **kwargs),
 148              self._tree_value,
 149          )
 150  
 151  
 152  @inference_state_function_cache()
 153  def _load_module(inference_state, path):
 154      return inference_state.parse(
 155          path=path,
 156          cache=True,
 157          diff_cache=settings.fast_parser,
 158          cache_path=settings.cache_directory
 159      ).get_root_node()
 160  
 161  
 162  def _get_object_to_check(python_object):
 163      """Check if inspect.getfile has a chance to find the source."""
 164      try:
 165          python_object = inspect.unwrap(python_object)
 166      except ValueError:
 167          # Can return a ValueError when it wraps around
 168          pass
 169  
 170      if (inspect.ismodule(python_object)
 171              or inspect.isclass(python_object)
 172              or inspect.ismethod(python_object)
 173              or inspect.isfunction(python_object)
 174              or inspect.istraceback(python_object)
 175              or inspect.isframe(python_object)
 176              or inspect.iscode(python_object)):
 177          return python_object
 178  
 179      try:
 180          return python_object.__class__
 181      except AttributeError:
 182          raise TypeError  # Prevents computation of `repr` within inspect.
 183  
 184  
 185  def _find_syntax_node_name(inference_state, python_object):
 186      original_object = python_object
 187      try:
 188          python_object = _get_object_to_check(python_object)
 189          path = inspect.getsourcefile(python_object)
 190      except (OSError, TypeError):
 191          # The type might not be known (e.g. class_with_dict.__weakref__)
 192          return None
 193      path = None if path is None else Path(path)
 194      try:
 195          if path is None or not path.exists():
 196              # The path might not exist or be e.g. <stdin>.
 197              return None
 198      except OSError:
 199          # Might raise an OSError on Windows:
 200          #
 201          #     [WinError 123] The filename, directory name, or volume label
 202          #     syntax is incorrect: '<string>'
 203          return None
 204  
 205      file_io = FileIO(path)
 206      module_node = _load_module(inference_state, path)
 207  
 208      if inspect.ismodule(python_object):
 209          # We don't need to check names for modules, because there's not really
 210          # a way to write a module in a module in Python (and also __name__ can
 211          # be something like ``email.utils``).
 212          code_lines = get_cached_code_lines(inference_state.grammar, path)
 213          return module_node, module_node, file_io, code_lines
 214  
 215      try:
 216          name_str = python_object.__name__
 217      except AttributeError:
 218          # Stuff like python_function.__code__.
 219          return None
 220  
 221      if name_str == '<lambda>':
 222          return None  # It's too hard to find lambdas.
 223  
 224      # Doesn't always work (e.g. os.stat_result)
 225      names = module_node.get_used_names().get(name_str, [])
 226      # Only functions and classes are relevant. If a name e.g. points to an
 227      # import, it's probably a builtin (like collections.deque) and needs to be
 228      # ignored.
 229      names = [
 230          n for n in names
 231          if n.parent.type in ('funcdef', 'classdef') and n.parent.name == n
 232      ]
 233      if not names:
 234          return None
 235  
 236      try:
 237          code = python_object.__code__
 238          # By using the line number of a code object we make the lookup in a
 239          # file pretty easy. There's still a possibility of people defining
 240          # stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people
 241          # do so we just don't care.
 242          line_nr = code.co_firstlineno
 243      except AttributeError:
 244          pass
 245      else:
 246          line_names = [name for name in names if name.start_pos[0] == line_nr]
 247          # There's a chance that the object is not available anymore, because
 248          # the code has changed in the background.
 249          if line_names:
 250              names = line_names
 251  
 252      code_lines = get_cached_code_lines(inference_state.grammar, path)
 253      # It's really hard to actually get the right definition, here as a last
 254      # resort we just return the last one. This chance might lead to odd
 255      # completions at some points but will lead to mostly correct type
 256      # inference, because people tend to define a public name in a module only
 257      # once.
 258      tree_node = names[-1].parent
 259      if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
 260          # If an instance is given and we're landing on a function (e.g.
 261          # partial in 3.5), something is completely wrong and we should not
 262          # return that.
 263          return None
 264      return module_node, tree_node, file_io, code_lines
 265  
 266  
 267  @inference_state_function_cache()
 268  def _create(inference_state, compiled_value, module_context):
 269      # TODO accessing this is bad, but it probably doesn't matter that much,
 270      # because we're working with interpreters only here.
 271      python_object = compiled_value.access_handle.access._obj
 272      result = _find_syntax_node_name(inference_state, python_object)
 273      if result is None:
 274          # TODO Care about generics from stuff like `[1]` and don't return like this.
 275          if type(python_object) in (dict, list, tuple):
 276              return ValueSet({compiled_value})
 277  
 278          tree_values = to_stub(compiled_value)
 279          if not tree_values:
 280              return ValueSet({compiled_value})
 281      else:
 282          module_node, tree_node, file_io, code_lines = result
 283  
 284          if module_context is None or module_context.tree_node != module_node:
 285              root_compiled_value = compiled_value.get_root_context().get_value()
 286              # TODO this __name__ might be wrong.
 287              name = root_compiled_value.py__name__()
 288              string_names = tuple(name.split('.'))
 289              module_value = ModuleValue(
 290                  inference_state, module_node,
 291                  file_io=file_io,
 292                  string_names=string_names,
 293                  code_lines=code_lines,
 294                  is_package=root_compiled_value.is_package(),
 295              )
 296              if name is not None:
 297                  inference_state.module_cache.add(string_names, ValueSet([module_value]))
 298              module_context = module_value.as_context()
 299  
 300          tree_values = ValueSet({module_context.create_value(tree_node)})
 301          if tree_node.type == 'classdef':
 302              if not compiled_value.is_class():
 303                  # Is an instance, not a class.
 304                  tree_values = tree_values.execute_with_values()
 305  
 306      return ValueSet(
 307          MixedObject(compiled_value, tree_value=tree_value)
 308          for tree_value in tree_values
 309      )
 310