""" Imitate the parser representation. """ import re from functools import partial from jedi import debug from jedi.inference.utils import to_list from jedi._compatibility import force_unicode, Parameter, cast_path from jedi.cache import memoize_method from jedi.inference.filters import AbstractFilter from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \ ParamNameInterface from jedi.inference.base_value import Value, ValueSet, NO_VALUES from jedi.inference.lazy_value import LazyKnownValue from jedi.inference.compiled.access import _sentinel from jedi.inference.cache import inference_state_function_cache from jedi.inference.helpers import reraise_getitem_errors from jedi.inference.signature import BuiltinSignature from jedi.inference.context import CompiledContext, CompiledModuleContext class CheckAttribute(object): """Raises :exc:`AttributeError` if the attribute X is not available.""" def __init__(self, check_name=None): # Remove the py in front of e.g. py__call__. self.check_name = check_name def __call__(self, func): self.func = func if self.check_name is None: self.check_name = force_unicode(func.__name__[2:]) return self def __get__(self, instance, owner): if instance is None: return self # This might raise an AttributeError. That's wanted. instance.access_handle.getattr_paths(self.check_name) return partial(self.func, instance) class CompiledValue(Value): def __init__(self, inference_state, access_handle, parent_context=None): super(CompiledValue, self).__init__(inference_state, parent_context) self.access_handle = access_handle def py__call__(self, arguments): return_annotation = self.access_handle.get_return_annotation() if return_annotation is not None: # TODO the return annotation may also be a string. return create_from_access_path( self.inference_state, return_annotation ).execute_annotation() try: self.access_handle.getattr_paths(u'__call__') except AttributeError: return super(CompiledValue, self).py__call__(arguments) else: if self.access_handle.is_class(): from jedi.inference.value import CompiledInstance return ValueSet([ CompiledInstance(self.inference_state, self.parent_context, self, arguments) ]) else: return ValueSet(self._execute_function(arguments)) @CheckAttribute() def py__class__(self): return create_from_access_path(self.inference_state, self.access_handle.py__class__()) @CheckAttribute() def py__mro__(self): return (self,) + tuple( create_from_access_path(self.inference_state, access) for access in self.access_handle.py__mro__accesses() ) @CheckAttribute() def py__bases__(self): return tuple( create_from_access_path(self.inference_state, access) for access in self.access_handle.py__bases__() ) def get_qualified_names(self): return self.access_handle.get_qualified_names() def py__bool__(self): return self.access_handle.py__bool__() def is_class(self): return self.access_handle.is_class() def is_function(self): return self.access_handle.is_function() def is_module(self): return self.access_handle.is_module() def is_compiled(self): return True def is_stub(self): return False def is_instance(self): return self.access_handle.is_instance() def py__doc__(self): return self.access_handle.py__doc__() @to_list def get_param_names(self): try: signature_params = self.access_handle.get_signature_params() except ValueError: # Has no signature params_str, ret = self._parse_function_doc() if not params_str: tokens = [] else: tokens = params_str.split(',') if self.access_handle.ismethoddescriptor(): tokens.insert(0, 'self') for p in tokens: name, _, default = p.strip().partition('=') yield UnresolvableParamName(self, name, default) else: for signature_param in signature_params: yield SignatureParamName(self, signature_param) def get_signatures(self): _, return_string = self._parse_function_doc() return [BuiltinSignature(self, return_string)] def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr()) @memoize_method def _parse_function_doc(self): doc = self.py__doc__() if doc is None: return '', '' return _parse_function_doc(doc) @property def api_type(self): return self.access_handle.get_api_type() def get_filters(self, is_instance=False, origin_scope=None): yield self._ensure_one_filter(is_instance) @memoize_method def _ensure_one_filter(self, is_instance): return CompiledValueFilter(self.inference_state, self, is_instance) def py__simple_getitem__(self, index): with reraise_getitem_errors(IndexError, KeyError, TypeError): try: access = self.access_handle.py__simple_getitem__(index) except AttributeError: return super(CompiledValue, self).py__simple_getitem__(index) if access is None: return NO_VALUES return ValueSet([create_from_access_path(self.inference_state, access)]) def py__getitem__(self, index_value_set, contextualized_node): all_access_paths = self.access_handle.py__getitem__all_values() if all_access_paths is None: # This means basically that no __getitem__ has been defined on this # object. return super(CompiledValue, self).py__getitem__(index_value_set, contextualized_node) return ValueSet( create_from_access_path(self.inference_state, access) for access in all_access_paths ) def py__iter__(self, contextualized_node=None): # Python iterators are a bit strange, because there's no need for # the __iter__ function as long as __getitem__ is defined (it will # just start with __getitem__(0). This is especially true for # Python 2 strings, where `str.__iter__` is not even defined. if not self.access_handle.has_iter(): for x in super(CompiledValue, self).py__iter__(contextualized_node): yield x access_path_list = self.access_handle.py__iter__list() if access_path_list is None: # There is no __iter__ method on this object. return for access in access_path_list: yield LazyKnownValue(create_from_access_path(self.inference_state, access)) def py__name__(self): return self.access_handle.py__name__() @property def name(self): name = self.py__name__() if name is None: name = self.access_handle.get_repr() return CompiledValueName(self, name) def _execute_function(self, params): from jedi.inference import docstrings from jedi.inference.compiled import builtin_from_name if self.api_type != 'function': return for name in self._parse_function_doc()[1].split(): try: # TODO wtf is this? this is exactly the same as the thing # below. It uses getattr as well. self.inference_state.builtins_module.access_handle.getattr_paths(name) except AttributeError: continue else: bltn_obj = builtin_from_name(self.inference_state, name) for result in self.inference_state.execute(bltn_obj, params): yield result for type_ in docstrings.infer_return_types(self): yield type_ def get_safe_value(self, default=_sentinel): try: return self.access_handle.get_safe_value() except ValueError: if default == _sentinel: raise return default def execute_operation(self, other, operator): try: return ValueSet([create_from_access_path( self.inference_state, self.access_handle.execute_operation(other.access_handle, operator) )]) except TypeError: return NO_VALUES def execute_annotation(self): if self.access_handle.get_repr() == 'None': # None as an annotation doesn't need to be executed. return ValueSet([self]) name, args = self.access_handle.get_annotation_name_and_args() arguments = [ ValueSet([create_from_access_path(self.inference_state, path)]) for path in args ] if name == 'Union': return ValueSet.from_sets(arg.execute_annotation() for arg in arguments) elif name: # While with_generics only exists on very specific objects, we # should probably be fine, because we control all the typing # objects. return ValueSet([ v.with_generics(arguments) for v in self.inference_state.typing_module.py__getattribute__(name) ]).execute_annotation() return super(CompiledValue, self).execute_annotation() def negate(self): return create_from_access_path(self.inference_state, self.access_handle.negate()) def get_metaclasses(self): return NO_VALUES def _as_context(self): return CompiledContext(self) @property def array_type(self): return self.access_handle.get_array_type() def get_key_values(self): return [ create_from_access_path(self.inference_state, k) for k in self.access_handle.get_key_paths() ] def get_type_hint(self, add_class_info=True): if self.access_handle.get_repr() in ('None', ""): return 'None' return None class CompiledModule(CompiledValue): file_io = None # For modules def _as_context(self): return CompiledModuleContext(self) def py__path__(self): paths = self.access_handle.py__path__() if paths is None: return None return map(cast_path, paths) def is_package(self): return self.py__path__() is not None @property def string_names(self): # For modules name = self.py__name__() if name is None: return () return tuple(name.split('.')) def py__file__(self): return cast_path(self.access_handle.py__file__()) class CompiledName(AbstractNameDefinition): def __init__(self, inference_state, parent_value, name): self._inference_state = inference_state self.parent_context = parent_value.as_context() self._parent_value = parent_value self.string_name = name def py__doc__(self): value, = self.infer() return value.py__doc__() def _get_qualified_names(self): parent_qualified_names = self.parent_context.get_qualified_names() if parent_qualified_names is None: return None return parent_qualified_names + (self.string_name,) def get_defining_qualified_value(self): context = self.parent_context if context.is_module() or context.is_class(): return self.parent_context.get_value() # Might be None return None def __repr__(self): try: name = self.parent_context.name # __name__ is not defined all the time except AttributeError: name = None return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name) @property def api_type(self): api = self.infer() # If we can't find the type, assume it is an instance variable if not api: return "instance" return next(iter(api)).api_type @memoize_method def infer(self): return ValueSet([self.infer_compiled_value()]) def infer_compiled_value(self): return create_from_name(self._inference_state, self._parent_value, self.string_name) class SignatureParamName(ParamNameInterface, AbstractNameDefinition): def __init__(self, compiled_value, signature_param): self.parent_context = compiled_value.parent_context self._signature_param = signature_param @property def string_name(self): return self._signature_param.name def to_string(self): s = self._kind_string() + self.string_name if self._signature_param.has_annotation: s += ': ' + self._signature_param.annotation_string if self._signature_param.has_default: s += '=' + self._signature_param.default_string return s def get_kind(self): return getattr(Parameter, self._signature_param.kind_name) def infer(self): p = self._signature_param inference_state = self.parent_context.inference_state values = NO_VALUES if p.has_default: values = ValueSet([create_from_access_path(inference_state, p.default)]) if p.has_annotation: annotation = create_from_access_path(inference_state, p.annotation) values |= annotation.execute_with_values() return values class UnresolvableParamName(ParamNameInterface, AbstractNameDefinition): def __init__(self, compiled_value, name, default): self.parent_context = compiled_value.parent_context self.string_name = name self._default = default def get_kind(self): return Parameter.POSITIONAL_ONLY def to_string(self): string = self.string_name if self._default: string += '=' + self._default return string def infer(self): return NO_VALUES class CompiledValueName(ValueNameMixin, AbstractNameDefinition): def __init__(self, value, name): self.string_name = name self._value = value self.parent_context = value.parent_context class EmptyCompiledName(AbstractNameDefinition): """ Accessing some names will raise an exception. To avoid not having any completions, just give Jedi the option to return this object. It infers to nothing. """ def __init__(self, inference_state, name): self.parent_context = inference_state.builtins_module self.string_name = name def infer(self): return NO_VALUES class CompiledValueFilter(AbstractFilter): def __init__(self, inference_state, compiled_value, is_instance=False): self._inference_state = inference_state self.compiled_value = compiled_value self.is_instance = is_instance def get(self, name): access_handle = self.compiled_value.access_handle return self._get( name, lambda name, unsafe: access_handle.is_allowed_getattr(name, unsafe), lambda name: name in access_handle.dir(), check_has_attribute=True ) def _get(self, name, allowed_getattr_callback, in_dir_callback, check_has_attribute=False): """ To remove quite a few access calls we introduced the callback here. """ # Always use unicode objects in Python 2 from here. name = force_unicode(name) if self._inference_state.allow_descriptor_getattr: pass has_attribute, is_descriptor = allowed_getattr_callback( name, unsafe=self._inference_state.allow_descriptor_getattr ) if check_has_attribute and not has_attribute: return [] if (is_descriptor or not has_attribute) \ and not self._inference_state.allow_descriptor_getattr: return [self._get_cached_name(name, is_empty=True)] if self.is_instance and not in_dir_callback(name): return [] return [self._get_cached_name(name)] @memoize_method def _get_cached_name(self, name, is_empty=False): if is_empty: return EmptyCompiledName(self._inference_state, name) else: return self._create_name(name) def values(self): from jedi.inference.compiled import builtin_from_name names = [] needs_type_completions, dir_infos = self.compiled_value.access_handle.get_dir_infos() # We could use `unsafe` here as well, especially as a parameter to # get_dir_infos. But this would lead to a lot of property executions # that are probably not wanted. The drawback for this is that we # have a different name for `get` and `values`. For `get` we always # execute. for name in dir_infos: names += self._get( name, lambda name, unsafe: dir_infos[name], lambda name: name in dir_infos, ) # ``dir`` doesn't include the type names. if not self.is_instance and needs_type_completions: for filter in builtin_from_name(self._inference_state, u'type').get_filters(): names += filter.values() return names def _create_name(self, name): return CompiledName( self._inference_state, self.compiled_value, name ) def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.compiled_value) docstr_defaults = { 'floating point number': u'float', 'character': u'str', 'integer': u'int', 'dictionary': u'dict', 'string': u'str', } def _parse_function_doc(doc): """ Takes a function and returns the params and return value as a tuple. This is nothing more than a docstring parser. TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None TODO docstrings like 'tuple of integers' """ doc = force_unicode(doc) # parse round parentheses: def func(a, (b,c)) try: count = 0 start = doc.index('(') for i, s in enumerate(doc[start:]): if s == '(': count += 1 elif s == ')': count -= 1 if count == 0: end = start + i break param_str = doc[start + 1:end] except (ValueError, UnboundLocalError): # ValueError for doc.index # UnboundLocalError for undefined end in last line debug.dbg('no brackets found - no param') end = 0 param_str = u'' else: # remove square brackets, that show an optional param ( = None) def change_options(m): args = m.group(1).split(',') for i, a in enumerate(args): if a and '=' not in a: args[i] += '=None' return ','.join(args) while True: param_str, changes = re.subn(r' ?\[([^\[\]]+)\]', change_options, param_str) if changes == 0: break param_str = param_str.replace('-', '_') # see: isinstance.__doc__ # parse return value r = re.search(u'-[>-]* ', doc[end:end + 7]) if r is None: ret = u'' else: index = end + r.end() # get result type, which can contain newlines pattern = re.compile(r'(,\n|[^\n-])+') ret_str = pattern.match(doc, index).group(0).strip() # New object -> object() ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str) ret = docstr_defaults.get(ret_str, ret_str) return param_str, ret def create_from_name(inference_state, compiled_value, name): access_paths = compiled_value.access_handle.getattr_paths(name, default=None) value = None for access_path in access_paths: value = create_cached_compiled_value( inference_state, access_path, parent_context=None if value is None else value.as_context(), ) return value def _normalize_create_args(func): """The cache doesn't care about keyword vs. normal args.""" def wrapper(inference_state, obj, parent_context=None): return func(inference_state, obj, parent_context) return wrapper def create_from_access_path(inference_state, access_path): value = None for name, access in access_path.accesses: value = create_cached_compiled_value( inference_state, access, parent_context=None if value is None else value.as_context() ) return value @_normalize_create_args @inference_state_function_cache() def create_cached_compiled_value(inference_state, access_handle, parent_context): assert not isinstance(parent_context, CompiledValue) if parent_context is None: cls = CompiledModule else: cls = CompiledValue return cls(inference_state, access_handle, parent_context)