559 lines
18 KiB
Python
559 lines
18 KiB
Python
"""
|
|
Values are the "values" that Python would return. However Values are at the
|
|
same time also the "values" that a user is currently sitting in.
|
|
|
|
A ValueSet is typically used to specify the return of a function or any other
|
|
static analysis operation. In jedi there are always multiple returns and not
|
|
just one.
|
|
"""
|
|
from functools import reduce
|
|
from operator import add
|
|
from itertools import zip_longest
|
|
|
|
from parso.python.tree import Name
|
|
|
|
from jedi import debug
|
|
from jedi.parser_utils import clean_scope_docstring
|
|
from jedi.inference.helpers import SimpleGetItemNotFound
|
|
from jedi.inference.utils import safe_property
|
|
from jedi.inference.cache import inference_state_as_method_param_cache
|
|
from jedi.cache import memoize_method
|
|
|
|
sentinel = object()
|
|
|
|
|
|
class HasNoContext(Exception):
|
|
pass
|
|
|
|
|
|
class HelperValueMixin:
|
|
def get_root_context(self):
|
|
value = self
|
|
if value.parent_context is None:
|
|
return value.as_context()
|
|
|
|
while True:
|
|
if value.parent_context is None:
|
|
return value
|
|
value = value.parent_context
|
|
|
|
def execute(self, arguments):
|
|
return self.inference_state.execute(self, arguments=arguments)
|
|
|
|
def execute_with_values(self, *value_list):
|
|
from jedi.inference.arguments import ValuesArguments
|
|
arguments = ValuesArguments([ValueSet([value]) for value in value_list])
|
|
return self.inference_state.execute(self, arguments)
|
|
|
|
def execute_annotation(self):
|
|
return self.execute_with_values()
|
|
|
|
def gather_annotation_classes(self):
|
|
return ValueSet([self])
|
|
|
|
def merge_types_of_iterate(self, contextualized_node=None, is_async=False):
|
|
return ValueSet.from_sets(
|
|
lazy_value.infer()
|
|
for lazy_value in self.iterate(contextualized_node, is_async)
|
|
)
|
|
|
|
def _get_value_filters(self, name_or_str):
|
|
origin_scope = name_or_str if isinstance(name_or_str, Name) else None
|
|
yield from self.get_filters(origin_scope=origin_scope)
|
|
# This covers the case where a stub files are incomplete.
|
|
if self.is_stub():
|
|
from jedi.inference.gradual.conversion import convert_values
|
|
for c in convert_values(ValueSet({self})):
|
|
yield from c.get_filters()
|
|
|
|
def goto(self, name_or_str, name_context=None, analysis_errors=True):
|
|
from jedi.inference import finder
|
|
filters = self._get_value_filters(name_or_str)
|
|
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, name_context, analysis_errors)
|
|
values = ValueSet.from_sets(name.infer() for name in names)
|
|
if not values:
|
|
n = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
|
|
values = self.py__getattribute__alternatives(n)
|
|
|
|
if not names and not values and analysis_errors:
|
|
if isinstance(name_or_str, Name):
|
|
from jedi.inference import analysis
|
|
analysis.add_attribute_error(
|
|
name_context, self, name_or_str)
|
|
debug.dbg('context.names_to_types: %s -> %s', names, values)
|
|
return values
|
|
|
|
def py__await__(self):
|
|
await_value_set = self.py__getattribute__("__await__")
|
|
if not await_value_set:
|
|
debug.warning('Tried to run __await__ on value %s', self)
|
|
return await_value_set.execute_with_values()
|
|
|
|
def py__name__(self):
|
|
return self.name.string_name
|
|
|
|
def iterate(self, contextualized_node=None, is_async=False):
|
|
debug.dbg('iterate %s', self)
|
|
if is_async:
|
|
from jedi.inference.lazy_value import LazyKnownValues
|
|
# TODO if no __aiter__ values are there, error should be:
|
|
# TypeError: 'async for' requires an object with __aiter__ method, got int
|
|
return iter([
|
|
LazyKnownValues(
|
|
self.py__getattribute__('__aiter__').execute_with_values()
|
|
.py__getattribute__('__anext__').execute_with_values()
|
|
.py__getattribute__('__await__').execute_with_values()
|
|
.py__stop_iteration_returns()
|
|
) # noqa: E124
|
|
])
|
|
return self.py__iter__(contextualized_node)
|
|
|
|
def is_sub_class_of(self, class_value):
|
|
with debug.increase_indent_cm('subclass matching of %s <=> %s' % (self, class_value),
|
|
color='BLUE'):
|
|
for cls in self.py__mro__():
|
|
if cls.is_same_class(class_value):
|
|
debug.dbg('matched subclass True', color='BLUE')
|
|
return True
|
|
debug.dbg('matched subclass False', color='BLUE')
|
|
return False
|
|
|
|
def is_same_class(self, class2):
|
|
# Class matching should prefer comparisons that are not this function.
|
|
if type(class2).is_same_class != HelperValueMixin.is_same_class:
|
|
return class2.is_same_class(self)
|
|
return self == class2
|
|
|
|
@memoize_method
|
|
def as_context(self, *args, **kwargs):
|
|
return self._as_context(*args, **kwargs)
|
|
|
|
|
|
class Value(HelperValueMixin):
|
|
"""
|
|
To be implemented by subclasses.
|
|
"""
|
|
tree_node = None
|
|
# Possible values: None, tuple, list, dict and set. Here to deal with these
|
|
# very important containers.
|
|
array_type = None
|
|
api_type = 'not_defined_please_report_bug'
|
|
|
|
def __init__(self, inference_state, parent_context=None):
|
|
self.inference_state = inference_state
|
|
self.parent_context = parent_context
|
|
|
|
def py__getitem__(self, index_value_set, contextualized_node):
|
|
from jedi.inference import analysis
|
|
# TODO this value is probably not right.
|
|
analysis.add(
|
|
contextualized_node.context,
|
|
'type-error-not-subscriptable',
|
|
contextualized_node.node,
|
|
message="TypeError: '%s' object is not subscriptable" % self
|
|
)
|
|
return NO_VALUES
|
|
|
|
def py__simple_getitem__(self, index):
|
|
raise SimpleGetItemNotFound
|
|
|
|
def py__iter__(self, contextualized_node=None):
|
|
if contextualized_node is not None:
|
|
from jedi.inference import analysis
|
|
analysis.add(
|
|
contextualized_node.context,
|
|
'type-error-not-iterable',
|
|
contextualized_node.node,
|
|
message="TypeError: '%s' object is not iterable" % self)
|
|
return iter([])
|
|
|
|
def py__next__(self, contextualized_node=None):
|
|
return self.py__iter__(contextualized_node)
|
|
|
|
def get_signatures(self):
|
|
return []
|
|
|
|
def is_class(self):
|
|
return False
|
|
|
|
def is_class_mixin(self):
|
|
return False
|
|
|
|
def is_instance(self):
|
|
return False
|
|
|
|
def is_function(self):
|
|
return False
|
|
|
|
def is_module(self):
|
|
return False
|
|
|
|
def is_namespace(self):
|
|
return False
|
|
|
|
def is_compiled(self):
|
|
return False
|
|
|
|
def is_bound_method(self):
|
|
return False
|
|
|
|
def is_builtins_module(self):
|
|
return False
|
|
|
|
def py__bool__(self):
|
|
"""
|
|
Since Wrapper is a super class for classes, functions and modules,
|
|
the return value will always be true.
|
|
"""
|
|
return True
|
|
|
|
def py__doc__(self):
|
|
try:
|
|
self.tree_node.get_doc_node
|
|
except AttributeError:
|
|
return ''
|
|
else:
|
|
return clean_scope_docstring(self.tree_node)
|
|
|
|
def get_safe_value(self, default=sentinel):
|
|
if default is sentinel:
|
|
raise ValueError("There exists no safe value for value %s" % self)
|
|
return default
|
|
|
|
def execute_operation(self, other, operator):
|
|
debug.warning("%s not possible between %s and %s", operator, self, other)
|
|
return NO_VALUES
|
|
|
|
def py__call__(self, arguments):
|
|
debug.warning("no execution possible %s", self)
|
|
return NO_VALUES
|
|
|
|
def py__stop_iteration_returns(self):
|
|
debug.warning("Not possible to return the stop iterations of %s", self)
|
|
return NO_VALUES
|
|
|
|
def py__getattribute__alternatives(self, name_or_str):
|
|
"""
|
|
For now a way to add values in cases like __getattr__.
|
|
"""
|
|
return NO_VALUES
|
|
|
|
def py__get__(self, instance, class_value):
|
|
debug.warning("No __get__ defined on %s", self)
|
|
return ValueSet([self])
|
|
|
|
def py__get__on_class(self, calling_instance, instance, class_value):
|
|
return NotImplemented
|
|
|
|
def get_qualified_names(self):
|
|
# Returns Optional[Tuple[str, ...]]
|
|
return None
|
|
|
|
def is_stub(self):
|
|
# The root value knows if it's a stub or not.
|
|
return self.parent_context.is_stub()
|
|
|
|
def _as_context(self):
|
|
raise HasNoContext
|
|
|
|
@property
|
|
def name(self):
|
|
raise NotImplementedError
|
|
|
|
def get_type_hint(self, add_class_info=True):
|
|
return None
|
|
|
|
def infer_type_vars(self, value_set):
|
|
"""
|
|
When the current instance represents a type annotation, this method
|
|
tries to find information about undefined type vars and returns a dict
|
|
from type var name to value set.
|
|
|
|
This is for example important to understand what `iter([1])` returns.
|
|
According to typeshed, `iter` returns an `Iterator[_T]`:
|
|
|
|
def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...
|
|
|
|
This functions would generate `int` for `_T` in this case, because it
|
|
unpacks the `Iterable`.
|
|
|
|
Parameters
|
|
----------
|
|
|
|
`self`: represents the annotation of the current parameter to infer the
|
|
value for. In the above example, this would initially be the
|
|
`Iterable[_T]` of the `iterable` parameter and then, when recursing,
|
|
just the `_T` generic parameter.
|
|
|
|
`value_set`: represents the actual argument passed to the parameter
|
|
we're inferred for, or (for recursive calls) their types. In the
|
|
above example this would first be the representation of the list
|
|
`[1]` and then, when recursing, just of `1`.
|
|
"""
|
|
return {}
|
|
|
|
|
|
def iterate_values(values, contextualized_node=None, is_async=False):
|
|
"""
|
|
Calls `iterate`, on all values but ignores the ordering and just returns
|
|
all values that the iterate functions yield.
|
|
"""
|
|
return ValueSet.from_sets(
|
|
lazy_value.infer()
|
|
for lazy_value in values.iterate(contextualized_node, is_async=is_async)
|
|
)
|
|
|
|
|
|
class _ValueWrapperBase(HelperValueMixin):
|
|
@safe_property
|
|
def name(self):
|
|
from jedi.inference.names import ValueName
|
|
wrapped_name = self._wrapped_value.name
|
|
if wrapped_name.tree_name is not None:
|
|
return ValueName(self, wrapped_name.tree_name)
|
|
else:
|
|
from jedi.inference.compiled import CompiledValueName
|
|
return CompiledValueName(self, wrapped_name.string_name)
|
|
|
|
@classmethod
|
|
@inference_state_as_method_param_cache()
|
|
def create_cached(cls, inference_state, *args, **kwargs):
|
|
return cls(*args, **kwargs)
|
|
|
|
def __getattr__(self, name):
|
|
assert name != '_wrapped_value', 'Problem with _get_wrapped_value'
|
|
return getattr(self._wrapped_value, name)
|
|
|
|
|
|
class LazyValueWrapper(_ValueWrapperBase):
|
|
@safe_property
|
|
@memoize_method
|
|
def _wrapped_value(self):
|
|
with debug.increase_indent_cm('Resolve lazy value wrapper'):
|
|
return self._get_wrapped_value()
|
|
|
|
def __repr__(self):
|
|
return '<%s>' % (self.__class__.__name__)
|
|
|
|
def _get_wrapped_value(self):
|
|
raise NotImplementedError
|
|
|
|
|
|
class ValueWrapper(_ValueWrapperBase):
|
|
def __init__(self, wrapped_value):
|
|
self._wrapped_value = wrapped_value
|
|
|
|
def __repr__(self):
|
|
return '%s(%s)' % (self.__class__.__name__, self._wrapped_value)
|
|
|
|
|
|
class TreeValue(Value):
|
|
def __init__(self, inference_state, parent_context, tree_node):
|
|
super().__init__(inference_state, parent_context)
|
|
self.tree_node = tree_node
|
|
|
|
def __repr__(self):
|
|
return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
|
|
|
|
|
|
class ContextualizedNode:
|
|
def __init__(self, context, node):
|
|
self.context = context
|
|
self.node = node
|
|
|
|
def get_root_context(self):
|
|
return self.context.get_root_context()
|
|
|
|
def infer(self):
|
|
return self.context.infer_node(self.node)
|
|
|
|
def __repr__(self):
|
|
return '<%s: %s in %s>' % (self.__class__.__name__, self.node, self.context)
|
|
|
|
|
|
def _getitem(value, index_values, contextualized_node):
|
|
# The actual getitem call.
|
|
result = NO_VALUES
|
|
unused_values = set()
|
|
for index_value in index_values:
|
|
index = index_value.get_safe_value(default=None)
|
|
if type(index) in (float, int, str, slice, bytes):
|
|
try:
|
|
result |= value.py__simple_getitem__(index)
|
|
continue
|
|
except SimpleGetItemNotFound:
|
|
pass
|
|
|
|
unused_values.add(index_value)
|
|
|
|
# The index was somehow not good enough or simply a wrong type.
|
|
# Therefore we now iterate through all the values and just take
|
|
# all results.
|
|
if unused_values or not index_values:
|
|
result |= value.py__getitem__(
|
|
ValueSet(unused_values),
|
|
contextualized_node
|
|
)
|
|
debug.dbg('py__getitem__ result: %s', result)
|
|
return result
|
|
|
|
|
|
class ValueSet:
|
|
def __init__(self, iterable):
|
|
self._set = frozenset(iterable)
|
|
for value in iterable:
|
|
assert not isinstance(value, ValueSet)
|
|
|
|
@classmethod
|
|
def _from_frozen_set(cls, frozenset_):
|
|
self = cls.__new__(cls)
|
|
self._set = frozenset_
|
|
return self
|
|
|
|
@classmethod
|
|
def from_sets(cls, sets):
|
|
"""
|
|
Used to work with an iterable of set.
|
|
"""
|
|
aggregated = set()
|
|
for set_ in sets:
|
|
if isinstance(set_, ValueSet):
|
|
aggregated |= set_._set
|
|
else:
|
|
aggregated |= frozenset(set_)
|
|
return cls._from_frozen_set(frozenset(aggregated))
|
|
|
|
def __or__(self, other):
|
|
return self._from_frozen_set(self._set | other._set)
|
|
|
|
def __and__(self, other):
|
|
return self._from_frozen_set(self._set & other._set)
|
|
|
|
def __iter__(self):
|
|
return iter(self._set)
|
|
|
|
def __bool__(self):
|
|
return bool(self._set)
|
|
|
|
def __len__(self):
|
|
return len(self._set)
|
|
|
|
def __repr__(self):
|
|
return 'S{%s}' % (', '.join(str(s) for s in self._set))
|
|
|
|
def filter(self, filter_func):
|
|
return self.__class__(filter(filter_func, self._set))
|
|
|
|
def __getattr__(self, name):
|
|
def mapper(*args, **kwargs):
|
|
return self.from_sets(
|
|
getattr(value, name)(*args, **kwargs)
|
|
for value in self._set
|
|
)
|
|
return mapper
|
|
|
|
def __eq__(self, other):
|
|
return self._set == other._set
|
|
|
|
def __ne__(self, other):
|
|
return not self.__eq__(other)
|
|
|
|
def __hash__(self):
|
|
return hash(self._set)
|
|
|
|
def py__class__(self):
|
|
return ValueSet(c.py__class__() for c in self._set)
|
|
|
|
def iterate(self, contextualized_node=None, is_async=False):
|
|
from jedi.inference.lazy_value import get_merged_lazy_value
|
|
type_iters = [c.iterate(contextualized_node, is_async=is_async) for c in self._set]
|
|
for lazy_values in zip_longest(*type_iters):
|
|
yield get_merged_lazy_value(
|
|
[l for l in lazy_values if l is not None]
|
|
)
|
|
|
|
def execute(self, arguments):
|
|
return ValueSet.from_sets(c.inference_state.execute(c, arguments) for c in self._set)
|
|
|
|
def execute_with_values(self, *args, **kwargs):
|
|
return ValueSet.from_sets(c.execute_with_values(*args, **kwargs) for c in self._set)
|
|
|
|
def goto(self, *args, **kwargs):
|
|
return reduce(add, [c.goto(*args, **kwargs) for c in self._set], [])
|
|
|
|
def py__getattribute__(self, *args, **kwargs):
|
|
return ValueSet.from_sets(c.py__getattribute__(*args, **kwargs) for c in self._set)
|
|
|
|
def get_item(self, *args, **kwargs):
|
|
return ValueSet.from_sets(_getitem(c, *args, **kwargs) for c in self._set)
|
|
|
|
def try_merge(self, function_name):
|
|
value_set = self.__class__([])
|
|
for c in self._set:
|
|
try:
|
|
method = getattr(c, function_name)
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
value_set |= method()
|
|
return value_set
|
|
|
|
def gather_annotation_classes(self):
|
|
return ValueSet.from_sets([c.gather_annotation_classes() for c in self._set])
|
|
|
|
def get_signatures(self):
|
|
return [sig for c in self._set for sig in c.get_signatures()]
|
|
|
|
def get_type_hint(self, add_class_info=True):
|
|
t = [v.get_type_hint(add_class_info=add_class_info) for v in self._set]
|
|
type_hints = sorted(filter(None, t))
|
|
if len(type_hints) == 1:
|
|
return type_hints[0]
|
|
|
|
optional = 'None' in type_hints
|
|
if optional:
|
|
type_hints.remove('None')
|
|
|
|
if len(type_hints) == 0:
|
|
return None
|
|
elif len(type_hints) == 1:
|
|
s = type_hints[0]
|
|
else:
|
|
s = 'Union[%s]' % ', '.join(type_hints)
|
|
if optional:
|
|
s = 'Optional[%s]' % s
|
|
return s
|
|
|
|
def infer_type_vars(self, value_set):
|
|
# Circular
|
|
from jedi.inference.gradual.annotation import merge_type_var_dicts
|
|
|
|
type_var_dict = {}
|
|
for value in self._set:
|
|
merge_type_var_dicts(
|
|
type_var_dict,
|
|
value.infer_type_vars(value_set),
|
|
)
|
|
return type_var_dict
|
|
|
|
|
|
NO_VALUES = ValueSet([])
|
|
|
|
|
|
def iterator_to_value_set(func):
|
|
def wrapper(*args, **kwargs):
|
|
return ValueSet(func(*args, **kwargs))
|
|
|
|
return wrapper
|