forked from VimPlug/jedi
949 lines
36 KiB
Python
949 lines
36 KiB
Python
"""
|
|
Like described in the :mod:`parsing_representation` module, there's a need for
|
|
an ast like module to represent the states of parsed modules.
|
|
|
|
But now there are also structures in Python that need a little bit more than
|
|
that. An ``Instance`` for example is only a ``Class`` before it is
|
|
instantiated. This class represents these cases.
|
|
|
|
So, why is there also a ``Class`` class here? Well, there are decorators and
|
|
they change classes in Python 3.
|
|
"""
|
|
from __future__ import with_statement
|
|
|
|
import copy
|
|
import itertools
|
|
|
|
from jedi._compatibility import use_metaclass, next, hasattr, unicode
|
|
from jedi.parser import representation as pr
|
|
from jedi import cache
|
|
from jedi import helpers
|
|
from jedi import debug
|
|
from jedi import common
|
|
from jedi.evaluate import imports
|
|
from jedi.evaluate import builtin
|
|
from jedi.evaluate import recursion
|
|
from jedi import docstrings
|
|
from jedi import dynamic
|
|
|
|
|
|
class Executable(pr.IsScope):
|
|
"""
|
|
An instance is also an executable - because __init__ is called
|
|
:param var_args: The param input array, consist of `pr.Array` or list.
|
|
"""
|
|
def __init__(self, evaluator, base, var_args=()):
|
|
self.evaluator = evaluator
|
|
self.base = base
|
|
self.var_args = var_args
|
|
|
|
def get_parent_until(self, *args, **kwargs):
|
|
return self._decorated.get_parent_until(*args, **kwargs)
|
|
|
|
@property
|
|
def parent(self):
|
|
return self._decorated.parent
|
|
|
|
@property
|
|
def _decorated(self):
|
|
"""
|
|
Instance doesn't care about decorators and Execution overrides this
|
|
"""
|
|
return self.base
|
|
|
|
|
|
class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
|
|
"""
|
|
This class is used to evaluate instances.
|
|
"""
|
|
def __init__(self, evaluator, base, var_args=()):
|
|
super(Instance, self).__init__(evaluator, base, var_args)
|
|
if str(base.name) in ['list', 'set'] \
|
|
and builtin.Builtin.scope == base.get_parent_until():
|
|
# compare the module path with the builtin name.
|
|
self.var_args = dynamic.check_array_instances(self)
|
|
else:
|
|
# need to execute the __init__ function, because the dynamic param
|
|
# searching needs it.
|
|
with common.ignored(KeyError):
|
|
self.execute_subscope_by_name('__init__', self.var_args)
|
|
# Generated instances are classes that are just generated by self
|
|
# (No var_args) used.
|
|
self.is_generated = False
|
|
|
|
@cache.memoize_default()
|
|
def _get_method_execution(self, func):
|
|
func = InstanceElement(self, func, True)
|
|
return Execution(func, self.var_args)
|
|
|
|
def _get_func_self_name(self, func):
|
|
"""
|
|
Returns the name of the first param in a class method (which is
|
|
normally self.
|
|
"""
|
|
try:
|
|
return str(func.params[0].get_name())
|
|
except IndexError:
|
|
return None
|
|
|
|
@cache.memoize_default([])
|
|
def _get_self_attributes(self):
|
|
def add_self_dot_name(name):
|
|
"""
|
|
Need to copy and rewrite the name, because names are now
|
|
``instance_usage.variable`` instead of ``self.variable``.
|
|
"""
|
|
n = copy.copy(name)
|
|
n.names = n.names[1:]
|
|
names.append(InstanceElement(self, n))
|
|
|
|
names = []
|
|
# This loop adds the names of the self object, copies them and removes
|
|
# the self.
|
|
for sub in self.base.subscopes:
|
|
if isinstance(sub, pr.Class):
|
|
continue
|
|
# Get the self name, if there's one.
|
|
self_name = self._get_func_self_name(sub)
|
|
if not self_name:
|
|
continue
|
|
|
|
if sub.name.get_code() == '__init__':
|
|
# ``__init__`` is special because the params need are injected
|
|
# this way. Therefore an execution is necessary.
|
|
if not sub.decorators:
|
|
# __init__ decorators should generally just be ignored,
|
|
# because to follow them and their self variables is too
|
|
# complicated.
|
|
sub = self._get_method_execution(sub)
|
|
for n in sub.get_set_vars():
|
|
# Only names with the selfname are being added.
|
|
# It is also important, that they have a len() of 2,
|
|
# because otherwise, they are just something else
|
|
if n.names[0] == self_name and len(n.names) == 2:
|
|
add_self_dot_name(n)
|
|
|
|
for s in self.base.get_super_classes():
|
|
names += Instance(s)._get_self_attributes()
|
|
|
|
return names
|
|
|
|
def get_subscope_by_name(self, name):
|
|
sub = self.base.get_subscope_by_name(name)
|
|
return InstanceElement(self, sub, True)
|
|
|
|
def execute_subscope_by_name(self, name, args=()):
|
|
method = self.get_subscope_by_name(name)
|
|
return Execution(method, args).get_return_types()
|
|
|
|
def get_descriptor_return(self, obj):
|
|
""" Throws a KeyError if there's no method. """
|
|
# Arguments in __get__ descriptors are obj, class.
|
|
# `method` is the new parent of the array, don't know if that's good.
|
|
args = [obj, obj.base] if isinstance(obj, Instance) else [None, obj]
|
|
return self.execute_subscope_by_name('__get__', args)
|
|
|
|
@cache.memoize_default([])
|
|
def get_defined_names(self):
|
|
"""
|
|
Get the instance vars of a class. This includes the vars of all
|
|
classes
|
|
"""
|
|
names = self._get_self_attributes()
|
|
|
|
class_names = self.base.instance_names()
|
|
for var in class_names:
|
|
names.append(InstanceElement(self, var, True))
|
|
return names
|
|
|
|
def scope_generator(self):
|
|
"""
|
|
An Instance has two scopes: The scope with self names and the class
|
|
scope. Instance variables have priority over the class scope.
|
|
"""
|
|
yield self, self._get_self_attributes()
|
|
|
|
names = []
|
|
class_names = self.base.instance_names()
|
|
for var in class_names:
|
|
names.append(InstanceElement(self, var, True))
|
|
yield self, names
|
|
|
|
def get_index_types(self, index=None):
|
|
args = [] if index is None else [index]
|
|
try:
|
|
return self.execute_subscope_by_name('__getitem__', args)
|
|
except KeyError:
|
|
debug.warning('No __getitem__, cannot access the array.')
|
|
return []
|
|
|
|
def __getattr__(self, name):
|
|
if name not in ['start_pos', 'end_pos', 'name', 'get_imports',
|
|
'doc', 'docstr', 'asserts']:
|
|
raise AttributeError("Instance %s: Don't touch this (%s)!"
|
|
% (self, name))
|
|
return getattr(self.base, name)
|
|
|
|
def __repr__(self):
|
|
return "<e%s of %s (var_args: %s)>" % \
|
|
(type(self).__name__, self.base, len(self.var_args or []))
|
|
|
|
|
|
class InstanceElement(use_metaclass(cache.CachedMetaClass, pr.Base)):
|
|
"""
|
|
InstanceElement is a wrapper for any object, that is used as an instance
|
|
variable (e.g. self.variable or class methods).
|
|
"""
|
|
def __init__(self, instance, var, is_class_var=False):
|
|
if isinstance(var, pr.Function):
|
|
var = Function(var)
|
|
elif isinstance(var, pr.Class):
|
|
var = Class(var)
|
|
self.instance = instance
|
|
self.var = var
|
|
self.is_class_var = is_class_var
|
|
|
|
@property
|
|
@cache.memoize_default()
|
|
def parent(self):
|
|
par = self.var.parent
|
|
if isinstance(par, Class) and par == self.instance.base \
|
|
or isinstance(par, pr.Class) \
|
|
and par == self.instance.base.base:
|
|
par = self.instance
|
|
elif not isinstance(par, pr.Module):
|
|
par = InstanceElement(self.instance, par, self.is_class_var)
|
|
return par
|
|
|
|
def get_parent_until(self, *args, **kwargs):
|
|
return pr.Simple.get_parent_until(self, *args, **kwargs)
|
|
|
|
def get_decorated_func(self):
|
|
""" Needed because the InstanceElement should not be stripped """
|
|
func = self.var.get_decorated_func(self.instance)
|
|
if func == self.var:
|
|
return self
|
|
return func
|
|
|
|
def get_commands(self):
|
|
# Copy and modify the array.
|
|
return [InstanceElement(self.instance, command, self.is_class_var)
|
|
if not isinstance(command, unicode) else command
|
|
for command in self.var.get_commands()]
|
|
|
|
def __iter__(self):
|
|
for el in self.var.__iter__():
|
|
yield InstanceElement(self.instance, el, self.is_class_var)
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self.var, name)
|
|
|
|
def isinstance(self, *cls):
|
|
return isinstance(self.var, cls)
|
|
|
|
def __repr__(self):
|
|
return "<%s of %s>" % (type(self).__name__, self.var)
|
|
|
|
|
|
class Class(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
|
|
"""
|
|
This class is not only important to extend `pr.Class`, it is also a
|
|
important for descriptors (if the descriptor methods are evaluated or not).
|
|
"""
|
|
def __init__(self, evaluator, base):
|
|
self.evaluator = evaluator
|
|
self.base = base
|
|
|
|
@cache.memoize_default(default=())
|
|
def get_super_classes(self):
|
|
supers = []
|
|
# TODO care for mro stuff (multiple super classes).
|
|
for s in self.base.supers:
|
|
# Super classes are statements.
|
|
for cls in self.evaluator.follow_statement(s):
|
|
if not isinstance(cls, Class):
|
|
debug.warning('Received non class, as a super class')
|
|
continue # Just ignore other stuff (user input error).
|
|
supers.append(cls)
|
|
if not supers and self.base.parent != builtin.Builtin.scope:
|
|
# add `object` to classes
|
|
supers += self.evaluator.find_name(builtin.Builtin.scope, 'object')
|
|
return supers
|
|
|
|
@cache.memoize_default(default=())
|
|
def instance_names(self):
|
|
def in_iterable(name, iterable):
|
|
""" checks if the name is in the variable 'iterable'. """
|
|
for i in iterable:
|
|
# Only the last name is important, because these names have a
|
|
# maximal length of 2, with the first one being `self`.
|
|
if i.names[-1] == name.names[-1]:
|
|
return True
|
|
return False
|
|
|
|
result = self.base.get_defined_names()
|
|
super_result = []
|
|
# TODO mro!
|
|
for cls in self.get_super_classes():
|
|
# Get the inherited names.
|
|
for i in cls.instance_names():
|
|
if not in_iterable(i, result):
|
|
super_result.append(i)
|
|
result += super_result
|
|
return result
|
|
|
|
@cache.memoize_default(default=())
|
|
def get_defined_names(self):
|
|
result = self.instance_names()
|
|
type_cls = self.evaluator.find_name(builtin.Builtin.scope, 'type')[0]
|
|
return result + type_cls.base.get_defined_names()
|
|
|
|
def get_subscope_by_name(self, name):
|
|
for sub in reversed(self.subscopes):
|
|
if sub.name.get_code() == name:
|
|
return sub
|
|
raise KeyError("Couldn't find subscope.")
|
|
|
|
@property
|
|
def name(self):
|
|
return self.base.name
|
|
|
|
def __getattr__(self, name):
|
|
if name not in ['start_pos', 'end_pos', 'parent', 'asserts', 'docstr',
|
|
'doc', 'get_imports', 'get_parent_until', 'get_code',
|
|
'subscopes']:
|
|
raise AttributeError("Don't touch this: %s of %s !" % (name, self))
|
|
return getattr(self.base, name)
|
|
|
|
def __repr__(self):
|
|
return "<e%s of %s>" % (type(self).__name__, self.base)
|
|
|
|
|
|
class Function(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
|
|
"""
|
|
Needed because of decorators. Decorators are evaluated here.
|
|
"""
|
|
def __init__(self, evaluator, func, is_decorated=False):
|
|
""" This should not be called directly """
|
|
self.evaluator = evaluator
|
|
self.base_func = func
|
|
self.is_decorated = is_decorated
|
|
|
|
@cache.memoize_default()
|
|
def _decorated_func(self, instance=None):
|
|
"""
|
|
Returns the function, that is to be executed in the end.
|
|
This is also the places where the decorators are processed.
|
|
"""
|
|
f = self.base_func
|
|
|
|
# Only enter it, if has not already been processed.
|
|
if not self.is_decorated:
|
|
for dec in reversed(self.base_func.decorators):
|
|
debug.dbg('decorator:', dec, f)
|
|
dec_results = set(self.evaluator.follow_statement(dec))
|
|
if not len(dec_results):
|
|
debug.warning('decorator not found: %s on %s' %
|
|
(dec, self.base_func))
|
|
return None
|
|
decorator = dec_results.pop()
|
|
if dec_results:
|
|
debug.warning('multiple decorators found', self.base_func,
|
|
dec_results)
|
|
# Create param array.
|
|
old_func = Function(f, is_decorated=True)
|
|
if instance is not None and decorator.isinstance(Function):
|
|
old_func = InstanceElement(instance, old_func)
|
|
instance = None
|
|
|
|
wrappers = Execution(decorator, (old_func,)).get_return_types()
|
|
if not len(wrappers):
|
|
debug.warning('no wrappers found', self.base_func)
|
|
return None
|
|
if len(wrappers) > 1:
|
|
debug.warning('multiple wrappers found', self.base_func,
|
|
wrappers)
|
|
# This is here, that the wrapper gets executed.
|
|
f = wrappers[0]
|
|
|
|
debug.dbg('decorator end', f)
|
|
if f != self.base_func and isinstance(f, pr.Function):
|
|
f = Function(f)
|
|
return f
|
|
|
|
def get_decorated_func(self, instance=None):
|
|
decorated_func = self._decorated_func(instance)
|
|
if decorated_func == self.base_func:
|
|
return self
|
|
if decorated_func is None:
|
|
# If the decorator func is not found, just ignore the decorator
|
|
# function, because sometimes decorators are just really
|
|
# complicated.
|
|
return Function(self.base_func, True)
|
|
return decorated_func
|
|
|
|
def get_magic_method_names(self):
|
|
return builtin.Builtin.magic_function_scope.get_defined_names()
|
|
|
|
def get_magic_method_scope(self):
|
|
return builtin.Builtin.magic_function_scope
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self.base_func, name)
|
|
|
|
def __repr__(self):
|
|
dec = ''
|
|
if self._decorated_func() != self.base_func:
|
|
dec = " is " + repr(self._decorated_func())
|
|
return "<e%s of %s%s>" % (type(self).__name__, self.base_func, dec)
|
|
|
|
|
|
class Execution(Executable):
|
|
"""
|
|
This class is used to evaluate functions and their returns.
|
|
|
|
This is the most complicated class, because it contains the logic to
|
|
transfer parameters. It is even more complicated, because there may be
|
|
multiple calls to functions and recursion has to be avoided. But this is
|
|
responsibility of the decorators.
|
|
"""
|
|
def _follow_var_arg(self, index):
|
|
try:
|
|
stmt = self.var_args[index]
|
|
except IndexError:
|
|
return []
|
|
else:
|
|
if isinstance(stmt, pr.Statement):
|
|
return self.evaluator.follow_statement(stmt)
|
|
else:
|
|
return [stmt] # just some arbitrary object
|
|
|
|
@property
|
|
@cache.memoize_default()
|
|
def _decorated(self):
|
|
"""Get the decorated version of the input"""
|
|
base = self.base
|
|
if self.base.isinstance(Function):
|
|
base = base.get_decorated_func()
|
|
return base
|
|
|
|
@cache.memoize_default(default=())
|
|
@recursion.ExecutionRecursionDecorator
|
|
def get_return_types(self, evaluate_generator=False):
|
|
""" Get the return types of a function. """
|
|
base = self._decorated
|
|
stmts = []
|
|
if base.parent == builtin.Builtin.scope \
|
|
and not isinstance(base, (Generator, Array)):
|
|
func_name = str(base.name)
|
|
|
|
# some implementations of builtins:
|
|
if func_name == 'getattr':
|
|
# follow the first param
|
|
objects = self._follow_var_arg(0)
|
|
names = self._follow_var_arg(1)
|
|
for obj in objects:
|
|
if not isinstance(obj, (Instance, Class, pr.Module)):
|
|
debug.warning('getattr called without instance')
|
|
continue
|
|
|
|
for arr_name in names:
|
|
if not isinstance(arr_name, Instance):
|
|
debug.warning('getattr called without str')
|
|
continue
|
|
if len(arr_name.var_args) != 1:
|
|
debug.warning('jedi getattr is too simple')
|
|
key = arr_name.var_args[0]
|
|
stmts += self.evaluator.follow_path(iter([key]), obj, base)
|
|
return stmts
|
|
elif func_name == 'type':
|
|
# otherwise it would be a metaclass
|
|
if len(self.var_args) == 1:
|
|
objects = self._follow_var_arg(0)
|
|
return [o.base for o in objects if isinstance(o, Instance)]
|
|
elif func_name == 'super':
|
|
# TODO make this able to detect multiple inheritance supers
|
|
accept = (pr.Function,)
|
|
func = self.var_args.get_parent_until(accept)
|
|
if func.isinstance(*accept):
|
|
cls = func.get_parent_until(accept + (pr.Class,),
|
|
include_current=False)
|
|
if isinstance(cls, pr.Class):
|
|
cls = Class(cls)
|
|
su = cls.get_super_classes()
|
|
if su:
|
|
return [Instance(su[0])]
|
|
return []
|
|
|
|
if base.isinstance(Class):
|
|
# There maybe executions of executions.
|
|
return [Instance(base, self.var_args)]
|
|
elif isinstance(base, Generator):
|
|
return base.iter_content()
|
|
else:
|
|
try:
|
|
base.returns # Test if it is a function
|
|
except AttributeError:
|
|
if hasattr(base, 'execute_subscope_by_name'):
|
|
try:
|
|
stmts = base.execute_subscope_by_name('__call__',
|
|
self.var_args)
|
|
except KeyError:
|
|
debug.warning("no __call__ func available", base)
|
|
else:
|
|
debug.warning("no execution possible", base)
|
|
else:
|
|
stmts = self._get_function_returns(base, evaluate_generator)
|
|
|
|
debug.dbg('exec result: %s in %s' % (stmts, self))
|
|
|
|
return imports.strip_imports(stmts)
|
|
|
|
def _get_function_returns(self, func, evaluate_generator):
|
|
""" A normal Function execution """
|
|
# Feed the listeners, with the params.
|
|
for listener in func.listeners:
|
|
listener.execute(self._get_params())
|
|
if func.is_generator and not evaluate_generator:
|
|
return [Generator(func, self.var_args)]
|
|
else:
|
|
stmts = docstrings.find_return_types(func)
|
|
for r in self.returns:
|
|
if r is not None:
|
|
stmts += self.evaluator.follow_statement(r)
|
|
return stmts
|
|
|
|
@cache.memoize_default(default=())
|
|
def _get_params(self):
|
|
"""
|
|
This returns the params for an Execution/Instance and is injected as a
|
|
'hack' into the pr.Function class.
|
|
This needs to be here, because Instance can have __init__ functions,
|
|
which act the same way as normal functions.
|
|
"""
|
|
def gen_param_name_copy(param, keys=(), values=(), array_type=None):
|
|
"""
|
|
Create a param with the original scope (of varargs) as parent.
|
|
"""
|
|
if isinstance(self.var_args, pr.Array):
|
|
parent = self.var_args.parent
|
|
start_pos = self.var_args.start_pos
|
|
else:
|
|
parent = self._decorated
|
|
start_pos = 0, 0
|
|
|
|
new_param = copy.copy(param)
|
|
new_param.is_generated = True
|
|
if parent is not None:
|
|
new_param.parent = parent
|
|
|
|
# create an Array (-> needed for *args/**kwargs tuples/dicts)
|
|
arr = pr.Array(self._sub_module, start_pos, array_type, parent)
|
|
arr.values = values
|
|
key_stmts = []
|
|
for key in keys:
|
|
stmt = pr.Statement(self._sub_module, [], start_pos, None)
|
|
stmt._commands = [key]
|
|
key_stmts.append(stmt)
|
|
arr.keys = key_stmts
|
|
arr.type = array_type
|
|
|
|
new_param._commands = [arr]
|
|
|
|
name = copy.copy(param.get_name())
|
|
name.parent = new_param
|
|
return name
|
|
|
|
result = []
|
|
start_offset = 0
|
|
if isinstance(self._decorated, InstanceElement):
|
|
# Care for self -> just exclude it and add the instance
|
|
start_offset = 1
|
|
self_name = copy.copy(self._decorated.params[0].get_name())
|
|
self_name.parent = self._decorated.instance
|
|
result.append(self_name)
|
|
|
|
param_dict = {}
|
|
for param in self._decorated.params:
|
|
param_dict[str(param.get_name())] = param
|
|
# There may be calls, which don't fit all the params, this just ignores
|
|
# it.
|
|
var_arg_iterator = self._get_var_args_iterator()
|
|
|
|
non_matching_keys = []
|
|
keys_used = set()
|
|
keys_only = False
|
|
for param in self._decorated.params[start_offset:]:
|
|
# The value and key can both be null. There, the defaults apply.
|
|
# args / kwargs will just be empty arrays / dicts, respectively.
|
|
# Wrong value count is just ignored. If you try to test cases that
|
|
# are not allowed in Python, Jedi will maybe not show any
|
|
# completions.
|
|
key, value = next(var_arg_iterator, (None, None))
|
|
while key:
|
|
keys_only = True
|
|
try:
|
|
key_param = param_dict[str(key)]
|
|
except KeyError:
|
|
non_matching_keys.append((key, value))
|
|
else:
|
|
keys_used.add(str(key))
|
|
result.append(gen_param_name_copy(key_param,
|
|
values=[value]))
|
|
key, value = next(var_arg_iterator, (None, None))
|
|
|
|
commands = param.get_commands()
|
|
keys = []
|
|
values = []
|
|
array_type = None
|
|
ignore_creation = False
|
|
if commands[0] == '*':
|
|
# *args param
|
|
array_type = pr.Array.TUPLE
|
|
if value:
|
|
values.append(value)
|
|
for key, value in var_arg_iterator:
|
|
# Iterate until a key argument is found.
|
|
if key:
|
|
var_arg_iterator.push_back((key, value))
|
|
break
|
|
values.append(value)
|
|
elif commands[0] == '**':
|
|
# **kwargs param
|
|
array_type = pr.Array.DICT
|
|
if non_matching_keys:
|
|
keys, values = zip(*non_matching_keys)
|
|
elif not keys_only:
|
|
# normal param
|
|
if value is not None:
|
|
values = [value]
|
|
else:
|
|
if param.assignment_details:
|
|
# No value: return the default values.
|
|
ignore_creation = True
|
|
result.append(param.get_name())
|
|
param.is_generated = True
|
|
else:
|
|
# If there is no assignment detail, that means there is
|
|
# no assignment, just the result. Therefore nothing has
|
|
# to be returned.
|
|
values = []
|
|
|
|
# Just ignore all the params that are without a key, after one
|
|
# keyword argument was set.
|
|
if not ignore_creation and (not keys_only or commands[0] == '**'):
|
|
keys_used.add(str(key))
|
|
result.append(gen_param_name_copy(param, keys=keys,
|
|
values=values, array_type=array_type))
|
|
|
|
if keys_only:
|
|
# sometimes param arguments are not completely written (which would
|
|
# create an Exception, but we have to handle that).
|
|
for k in set(param_dict) - keys_used:
|
|
result.append(gen_param_name_copy(param_dict[k]))
|
|
return result
|
|
|
|
def _get_var_args_iterator(self):
|
|
"""
|
|
Yields a key/value pair, the key is None, if its not a named arg.
|
|
"""
|
|
def iterate():
|
|
# `var_args` is typically an Array, and not a list.
|
|
for stmt in self.var_args:
|
|
if not isinstance(stmt, pr.Statement):
|
|
if stmt is None:
|
|
yield None, None
|
|
continue
|
|
old = stmt
|
|
# generate a statement if it's not already one.
|
|
module = builtin.Builtin.scope
|
|
stmt = pr.Statement(module, [], (0, 0), None)
|
|
stmt._commands = [old]
|
|
|
|
# *args
|
|
commands = stmt.get_commands()
|
|
if not len(commands):
|
|
continue
|
|
if commands[0] == '*':
|
|
arrays = self.evaluator.follow_call_list(commands[1:])
|
|
# *args must be some sort of an array, otherwise -> ignore
|
|
|
|
for array in arrays:
|
|
if isinstance(array, Array):
|
|
for field_stmt in array: # yield from plz!
|
|
yield None, field_stmt
|
|
elif isinstance(array, Generator):
|
|
for field_stmt in array.iter_content():
|
|
yield None, helpers.FakeStatement(field_stmt)
|
|
# **kwargs
|
|
elif commands[0] == '**':
|
|
arrays = self.evaluator.follow_call_list(commands[1:])
|
|
for array in arrays:
|
|
if isinstance(array, Array):
|
|
for key_stmt, value_stmt in array.items():
|
|
# first index, is the key if syntactically correct
|
|
call = key_stmt.get_commands()[0]
|
|
if isinstance(call, pr.Name):
|
|
yield call, value_stmt
|
|
elif isinstance(call, pr.Call):
|
|
yield call.name, value_stmt
|
|
# Normal arguments (including key arguments).
|
|
else:
|
|
if stmt.assignment_details:
|
|
key_arr, op = stmt.assignment_details[0]
|
|
# named parameter
|
|
if key_arr and isinstance(key_arr[0], pr.Call):
|
|
yield key_arr[0].name, stmt
|
|
else:
|
|
yield None, stmt
|
|
|
|
return iter(common.PushBackIterator(iterate()))
|
|
|
|
def get_defined_names(self):
|
|
"""
|
|
Call the default method with the own instance (self implements all
|
|
the necessary functions). Add also the params.
|
|
"""
|
|
return self._get_params() + pr.Scope.get_set_vars(self)
|
|
|
|
get_set_vars = get_defined_names
|
|
|
|
@common.rethrow_uncaught
|
|
def _copy_properties(self, prop):
|
|
"""
|
|
Literally copies a property of a Function. Copying is very expensive,
|
|
because it is something like `copy.deepcopy`. However, these copied
|
|
objects can be used for the executions, as if they were in the
|
|
execution.
|
|
"""
|
|
# Copy all these lists into this local function.
|
|
attr = getattr(self._decorated, prop)
|
|
objects = []
|
|
for element in attr:
|
|
if element is None:
|
|
copied = element
|
|
else:
|
|
copied = helpers.fast_parent_copy(element)
|
|
copied.parent = self._scope_copy(copied.parent)
|
|
if isinstance(copied, pr.Function):
|
|
copied = Function(copied)
|
|
objects.append(copied)
|
|
return objects
|
|
|
|
def __getattr__(self, name):
|
|
if name not in ['start_pos', 'end_pos', 'imports', '_sub_module']:
|
|
raise AttributeError('Tried to access %s: %s. Why?' % (name, self))
|
|
return getattr(self._decorated, name)
|
|
|
|
@cache.memoize_default()
|
|
@common.rethrow_uncaught
|
|
def _scope_copy(self, scope):
|
|
""" Copies a scope (e.g. if) in an execution """
|
|
# TODO method uses different scopes than the subscopes property.
|
|
|
|
# just check the start_pos, sometimes it's difficult with closures
|
|
# to compare the scopes directly.
|
|
if scope.start_pos == self.start_pos:
|
|
return self
|
|
else:
|
|
copied = helpers.fast_parent_copy(scope)
|
|
copied.parent = self._scope_copy(copied.parent)
|
|
return copied
|
|
|
|
@property
|
|
@cache.memoize_default()
|
|
def returns(self):
|
|
return self._copy_properties('returns')
|
|
|
|
@property
|
|
@cache.memoize_default()
|
|
def asserts(self):
|
|
return self._copy_properties('asserts')
|
|
|
|
@property
|
|
@cache.memoize_default()
|
|
def statements(self):
|
|
return self._copy_properties('statements')
|
|
|
|
@property
|
|
@cache.memoize_default()
|
|
def subscopes(self):
|
|
return self._copy_properties('subscopes')
|
|
|
|
def get_statement_for_position(self, pos):
|
|
return pr.Scope.get_statement_for_position(self, pos)
|
|
|
|
def __repr__(self):
|
|
return "<%s of %s>" % \
|
|
(type(self).__name__, self._decorated)
|
|
|
|
|
|
class Generator(use_metaclass(cache.CachedMetaClass, pr.Base)):
|
|
""" Cares for `yield` statements. """
|
|
def __init__(self, func, var_args):
|
|
super(Generator, self).__init__()
|
|
self.func = func
|
|
self.var_args = var_args
|
|
|
|
def get_defined_names(self):
|
|
"""
|
|
Returns a list of names that define a generator, which can return the
|
|
content of a generator.
|
|
"""
|
|
names = []
|
|
none_pos = (0, 0)
|
|
executes_generator = ('__next__', 'send')
|
|
for n in ('close', 'throw') + executes_generator:
|
|
name = pr.Name(builtin.Builtin.scope, [(n, none_pos)],
|
|
none_pos, none_pos)
|
|
if n in executes_generator:
|
|
name.parent = self
|
|
else:
|
|
name.parent = builtin.Builtin.scope
|
|
names.append(name)
|
|
debug.dbg('generator names', names)
|
|
return names
|
|
|
|
def iter_content(self):
|
|
""" returns the content of __iter__ """
|
|
return Execution(self.func, self.var_args).get_return_types(True)
|
|
|
|
def get_index_types(self, index=None):
|
|
debug.warning('Tried to get array access on a generator', self)
|
|
return []
|
|
|
|
def __getattr__(self, name):
|
|
if name not in ['start_pos', 'end_pos', 'parent', 'get_imports',
|
|
'asserts', 'doc', 'docstr', 'get_parent_until', 'get_code',
|
|
'subscopes']:
|
|
raise AttributeError("Accessing %s of %s is not allowed."
|
|
% (self, name))
|
|
return getattr(self.func, name)
|
|
|
|
def __repr__(self):
|
|
return "<%s of %s>" % (type(self).__name__, self.func)
|
|
|
|
|
|
class Array(use_metaclass(cache.CachedMetaClass, pr.Base)):
|
|
"""
|
|
Used as a mirror to pr.Array, if needed. It defines some getter
|
|
methods which are important in this module.
|
|
"""
|
|
def __init__(self, evaluator, array):
|
|
self.evaluator = evaluator
|
|
self._array = array
|
|
|
|
def get_index_types(self, index_arr=None):
|
|
""" Get the types of a specific index or all, if not given """
|
|
if index_arr is not None:
|
|
if index_arr and [x for x in index_arr if ':' in x.get_commands()]:
|
|
# array slicing
|
|
return [self]
|
|
|
|
index_possibilities = self._follow_values(index_arr)
|
|
if len(index_possibilities) == 1:
|
|
# This is indexing only one element, with a fixed index number,
|
|
# otherwise it just ignores the index (e.g. [1+1]).
|
|
index = index_possibilities[0]
|
|
if isinstance(index, Instance) \
|
|
and str(index.name) in ['int', 'str'] \
|
|
and len(index.var_args) == 1:
|
|
# TODO this is just very hackish and a lot of use cases are
|
|
# being ignored
|
|
with common.ignored(KeyError, IndexError,
|
|
UnboundLocalError, TypeError):
|
|
return self.get_exact_index_types(index.var_args[0])
|
|
|
|
result = list(self._follow_values(self._array.values))
|
|
result += dynamic.check_array_additions(self)
|
|
return set(result)
|
|
|
|
def get_exact_index_types(self, mixed_index):
|
|
""" Here the index is an int/str. Raises IndexError/KeyError """
|
|
index = mixed_index
|
|
if self.type == pr.Array.DICT:
|
|
index = None
|
|
for i, key_statement in enumerate(self._array.keys):
|
|
# Because we only want the key to be a string.
|
|
key_commands = key_statement.get_commands()
|
|
if len(key_commands) != 1: # cannot deal with complex strings
|
|
continue
|
|
key = key_commands[0]
|
|
if isinstance(key, pr.String):
|
|
str_key = key.value
|
|
elif isinstance(key, pr.Name):
|
|
str_key = str(key)
|
|
|
|
if mixed_index == str_key:
|
|
index = i
|
|
break
|
|
if index is None:
|
|
raise KeyError('No key found in dictionary')
|
|
|
|
# Can raise an IndexError
|
|
values = [self._array.values[index]]
|
|
return self._follow_values(values)
|
|
|
|
def _follow_values(self, values):
|
|
""" helper function for the index getters """
|
|
return list(itertools.chain.from_iterable(self.evaluator.follow_statement(v)
|
|
for v in values))
|
|
|
|
def get_defined_names(self):
|
|
"""
|
|
This method generates all `ArrayMethod` for one pr.Array.
|
|
It returns e.g. for a list: append, pop, ...
|
|
"""
|
|
# `array.type` is a string with the type, e.g. 'list'.
|
|
scope = self.evaluator.find_name(builtin.Builtin.scope, self._array.type)[0]
|
|
scope = Instance(scope)
|
|
names = scope.get_defined_names()
|
|
return [ArrayMethod(n) for n in names]
|
|
|
|
@property
|
|
def parent(self):
|
|
return builtin.Builtin.scope
|
|
|
|
def get_parent_until(self):
|
|
return builtin.Builtin.scope
|
|
|
|
def __getattr__(self, name):
|
|
if name not in ['type', 'start_pos', 'get_only_subelement', 'parent',
|
|
'get_parent_until', 'items']:
|
|
raise AttributeError('Strange access on %s: %s.' % (self, name))
|
|
return getattr(self._array, name)
|
|
|
|
def __getitem__(self):
|
|
return self._array.__getitem__()
|
|
|
|
def __iter__(self):
|
|
return self._array.__iter__()
|
|
|
|
def __len__(self):
|
|
return self._array.__len__()
|
|
|
|
def __repr__(self):
|
|
return "<e%s of %s>" % (type(self).__name__, self._array)
|
|
|
|
|
|
class ArrayMethod(object):
|
|
"""
|
|
A name, e.g. `list.append`, it is used to access the original array
|
|
methods.
|
|
"""
|
|
def __init__(self, name):
|
|
super(ArrayMethod, self).__init__()
|
|
self.name = name
|
|
|
|
def __getattr__(self, name):
|
|
# Set access privileges:
|
|
if name not in ['parent', 'names', 'start_pos', 'end_pos', 'get_code']:
|
|
raise AttributeError('Strange accesson %s: %s.' % (self, name))
|
|
return getattr(self.name, name)
|
|
|
|
def get_parent_until(self):
|
|
return builtin.Builtin.scope
|
|
|
|
def __repr__(self):
|
|
return "<%s of %s>" % (type(self).__name__, self.name)
|