forked from VimPlug/jedi
Starting to improve function calls.
This commit is contained in:
@@ -77,6 +77,7 @@ from jedi.evaluate import compiled
|
||||
from jedi.evaluate import precedence
|
||||
from jedi.evaluate import param
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.context import Context
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
@@ -110,8 +111,7 @@ class Evaluator(object):
|
||||
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
|
||||
|
||||
def wrap(self, element, parent_context):
|
||||
if isinstance(element, (er.Wrapper, er.InstanceElement,
|
||||
er.ModuleContext, er.FunctionExecution, er.Instance, compiled.CompiledObject)) or element is None:
|
||||
if isinstance(element, Context) or element is None:
|
||||
# TODO this is so ugly, please refactor.
|
||||
return element
|
||||
|
||||
@@ -328,7 +328,7 @@ class Evaluator(object):
|
||||
elif element.type == 'eval_input':
|
||||
types = self._eval_element_not_cached(context, element.children[0])
|
||||
else:
|
||||
types = precedence.calculate_children(self, element.children)
|
||||
types = precedence.calculate_children(self, context, element.children)
|
||||
debug.dbg('eval_element result %s', types)
|
||||
return types
|
||||
|
||||
@@ -341,13 +341,13 @@ class Evaluator(object):
|
||||
if isinstance(atom, tree.Name):
|
||||
# This is the first global lookup.
|
||||
stmt = atom.get_definition()
|
||||
if isinstance(context, er.FunctionExecution):
|
||||
# Adjust scope: If the name is not in the suite, it's a param
|
||||
# default or annotation and will be resolved as part of the
|
||||
# parent scope.
|
||||
colon = scope.children.index(':')
|
||||
if atom.start_pos < scope.children[colon + 1].start_pos:
|
||||
scope = scope.get_parent_scope()
|
||||
#if isinstance(context, er.FunctionExecution):
|
||||
## Adjust scope: If the name is not in the suite, it's a param
|
||||
## default or annotation and will be resolved as part of the
|
||||
## parent scope.
|
||||
#colon = scope.children.index(':')
|
||||
#if atom.start_pos < scope.children[colon + 1].start_pos:
|
||||
##scope = scope.get_parent_scope()
|
||||
if isinstance(stmt, tree.CompFor):
|
||||
stmt = stmt.get_parent_until((tree.ClassOrFunc, tree.ExprStmt))
|
||||
if stmt.type != 'expr_stmt':
|
||||
|
||||
@@ -56,6 +56,15 @@ class TreeNameDefinition(ContextName):
|
||||
return _name_to_types(self.parent_context._evaluator, self.parent_context, self.name, None)
|
||||
|
||||
|
||||
class ParamName(ContextName):
|
||||
def __init__(self, parent_context, name):
|
||||
self.parent_context = parent_context
|
||||
self.name = name
|
||||
|
||||
def infer(self):
|
||||
return set()
|
||||
|
||||
|
||||
class AbstractFilter(object):
|
||||
_until_position = None
|
||||
|
||||
@@ -89,9 +98,9 @@ class AbstractUsedNamesFilter(AbstractFilter):
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
return self._convert_to_names(self._filter(names))
|
||||
return self._convert_names(self._filter(names))
|
||||
|
||||
def _convert_to_names(self, names):
|
||||
def _convert_names(self, names):
|
||||
return [TreeNameDefinition(self._context, name) for name in names]
|
||||
|
||||
def values(self):
|
||||
@@ -127,7 +136,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
|
||||
|
||||
|
||||
class FunctionExecutionFilter(ParserTreeFilter):
|
||||
def __init__(self, evaluator, context, parser_scope, executed_function, param_by_name,
|
||||
def __init__(self, evaluator, context, parser_scope, param_by_name,
|
||||
until_position=None, origin_scope=None):
|
||||
super(FunctionExecutionFilter, self).__init__(
|
||||
evaluator,
|
||||
@@ -136,16 +145,15 @@ class FunctionExecutionFilter(ParserTreeFilter):
|
||||
until_position,
|
||||
origin_scope
|
||||
)
|
||||
self._executed_function = executed_function
|
||||
self._param_by_name = param_by_name
|
||||
|
||||
def _filter(self, names):
|
||||
names = super(FunctionExecutionFilter, self)._filter(names)
|
||||
|
||||
names = [self._executed_function.name_for_position(name.start_pos) for name in names]
|
||||
names = [self._param_by_name(str(name)) if search_ancestor(name, 'param') else name
|
||||
for name in names]
|
||||
return names
|
||||
def _convert_names(self, names):
|
||||
for name in names:
|
||||
param = search_ancestor(name, 'param')
|
||||
if param:
|
||||
#yield self.context._param_by_name(str(name))
|
||||
yield ParamName(self._context, name)
|
||||
else:
|
||||
yield TreeNameDefinition(self._context, name)
|
||||
|
||||
|
||||
class GlobalNameFilter(AbstractUsedNamesFilter):
|
||||
|
||||
@@ -295,19 +295,19 @@ class NameFinder(object):
|
||||
types = set()
|
||||
|
||||
# Add isinstance and other if/assert knowledge.
|
||||
if isinstance(self.name_str, tree.Name):
|
||||
# Ignore FunctionExecution parents for now.
|
||||
flow_scope = self.name_str
|
||||
until = flow_scope.get_parent_until(er.FunctionExecution)
|
||||
while not isinstance(until, er.FunctionExecution):
|
||||
flow_scope = flow_scope.get_parent_scope(include_flows=True)
|
||||
if flow_scope is None:
|
||||
break
|
||||
# TODO check if result is in scope -> no evaluation necessary
|
||||
n = check_flow_information(self._evaluator, flow_scope,
|
||||
self.name_str, self.position)
|
||||
if n:
|
||||
return n
|
||||
#if isinstance(self.name_str, tree.Name):
|
||||
## Ignore FunctionExecution parents for now.
|
||||
#flow_scope = self.name_str
|
||||
#until = flow_scope.get_parent_until(er.FunctionExecution)
|
||||
#while not isinstance(until, er.FunctionExecution):
|
||||
#flow_scope = flow_scope.get_parent_scope(include_flows=True)
|
||||
#if flow_scope is None:
|
||||
#break
|
||||
## TODO check if result is in scope -> no evaluation necessary
|
||||
#n = check_flow_information(self._evaluator, flow_scope,
|
||||
#self.name_str, self.position)
|
||||
#if n:
|
||||
#return n
|
||||
|
||||
for name in names:
|
||||
new_types = name.infer()
|
||||
|
||||
@@ -83,9 +83,22 @@ def follow_param(evaluator, param):
|
||||
return _evaluate_for_annotation(evaluator, annotation)
|
||||
|
||||
|
||||
def py__annotations__(funcdef):
|
||||
return_annotation = funcdef.annotation()
|
||||
if return_annotation:
|
||||
dct = {'return': return_annotation}
|
||||
else:
|
||||
dct = {}
|
||||
for function_param in funcdef.params:
|
||||
param_annotation = function_param.annotation()
|
||||
if param_annotation is not None:
|
||||
dct[function_param.name.value] = param_annotation
|
||||
return dct
|
||||
|
||||
|
||||
@memoize_default(None, evaluator_is_first_arg=True)
|
||||
def find_return_types(evaluator, func):
|
||||
annotation = func.py__annotations__().get("return", None)
|
||||
annotation = py__annotations__(func).get("return", None)
|
||||
return _evaluate_for_annotation(evaluator, annotation)
|
||||
|
||||
|
||||
|
||||
@@ -30,19 +30,19 @@ def literals_to_types(evaluator, result):
|
||||
if is_literal(typ):
|
||||
# Literals are only valid as long as the operations are
|
||||
# correct. Otherwise add a value-free instance.
|
||||
cls = builtin_from_name(evaluator, typ.name.value)
|
||||
cls = builtin_from_name(evaluator, typ.name.string_name)
|
||||
new_result |= evaluator.execute(cls)
|
||||
else:
|
||||
new_result.add(typ)
|
||||
return new_result
|
||||
|
||||
|
||||
def calculate_children(evaluator, children):
|
||||
def calculate_children(evaluator, context, children):
|
||||
"""
|
||||
Calculate a list of children with operators.
|
||||
"""
|
||||
iterator = iter(children)
|
||||
types = evaluator.eval_element(next(iterator))
|
||||
types = context.eval_node(next(iterator))
|
||||
for operator in iterator:
|
||||
right = next(iterator)
|
||||
if tree.is_node(operator, 'comp_op'): # not in / is not
|
||||
@@ -53,14 +53,14 @@ def calculate_children(evaluator, children):
|
||||
left_bools = set([left.py__bool__() for left in types])
|
||||
if left_bools == set([True]):
|
||||
if operator == 'and':
|
||||
types = evaluator.eval_element(right)
|
||||
types = context.eval_node(right)
|
||||
elif left_bools == set([False]):
|
||||
if operator != 'and':
|
||||
types = evaluator.eval_element(right)
|
||||
types = context.eval_node(right)
|
||||
# Otherwise continue, because of uncertainty.
|
||||
else:
|
||||
types = calculate(evaluator, types, operator,
|
||||
evaluator.eval_element(right))
|
||||
context.eval_node(right))
|
||||
debug.dbg('calculate_children types %s', types)
|
||||
return types
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ class _RecursionNode(object):
|
||||
|
||||
|
||||
def execution_recursion_decorator(func):
|
||||
return func # TODO remove
|
||||
def run(execution, **kwargs):
|
||||
detector = execution._evaluator.execution_recursion_detector
|
||||
if detector.push_execution(execution):
|
||||
@@ -130,9 +131,6 @@ class ExecutionRecursionDetector(object):
|
||||
self.recursion_level -= 1
|
||||
|
||||
def push_execution(self, execution):
|
||||
self.execution_funcs.add(execution.base)
|
||||
self.parent_execution_funcs.append(execution.base)
|
||||
return True # Remove
|
||||
in_par_execution_funcs = execution.base in self.parent_execution_funcs
|
||||
in_execution_funcs = execution.base in self.execution_funcs
|
||||
self.recursion_level += 1
|
||||
|
||||
@@ -57,25 +57,21 @@ from jedi.evaluate import flow_analysis
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
|
||||
GlobalNameFilter, DictFilter, ContextName
|
||||
from jedi.evaluate.context import Context
|
||||
from jedi.evaluate.context import TreeContext
|
||||
|
||||
|
||||
class Executed(Context):
|
||||
class Executed(TreeContext):
|
||||
"""
|
||||
An instance is also an executable - because __init__ is called
|
||||
:param var_args: The param input array, consist of a parser node or a list.
|
||||
"""
|
||||
def __init__(self, evaluator, parent_context, base, var_args):
|
||||
def __init__(self, evaluator, parent_context, var_args):
|
||||
super(Executed, self).__init__(evaluator, parent_context=parent_context)
|
||||
self.base = base
|
||||
self.var_args = var_args
|
||||
|
||||
def is_scope(self):
|
||||
return True
|
||||
|
||||
def get_parent_until(self, *args, **kwargs):
|
||||
return tree.Base.get_parent_until(self, *args, **kwargs)
|
||||
|
||||
|
||||
class Instance(use_metaclass(CachedMetaClass, Executed)):
|
||||
"""
|
||||
@@ -132,7 +128,7 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
|
||||
func = self.get_subscope_by_name('__init__')
|
||||
except KeyError:
|
||||
return None
|
||||
return FunctionExecution(self._evaluator, self, func, self.var_args)
|
||||
return FunctionExecutionContext(self._evaluator, self, func, self.var_args)
|
||||
|
||||
def _get_func_self_name(self, func):
|
||||
"""
|
||||
@@ -257,12 +253,6 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
|
||||
def name(self):
|
||||
return ContextName(self, self.base.name)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in ['start_pos', 'end_pos', 'get_imports', 'type',
|
||||
'doc', 'raw_doc']:
|
||||
return super(Instance, self).__getattribute__(name)
|
||||
return getattr(self.base, name)
|
||||
|
||||
def __repr__(self):
|
||||
dec = ''
|
||||
if self.decorates is not None:
|
||||
@@ -380,7 +370,7 @@ def get_instance_el(evaluator, instance, var, is_class_var=False):
|
||||
return InstanceName(var, parent)
|
||||
elif var.type != 'funcdef' \
|
||||
and isinstance(var, (Instance, compiled.CompiledObject, tree.Leaf,
|
||||
tree.Module, FunctionExecution)):
|
||||
tree.Module, FunctionExecutionContext)):
|
||||
return var
|
||||
|
||||
var = evaluator.wrap(var)
|
||||
@@ -451,9 +441,6 @@ class InstanceElement(use_metaclass(CachedMetaClass, tree.Base)):
|
||||
return get_instance_el(self._evaluator, self.instance, self.var[index],
|
||||
self.is_class_var)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.var, name)
|
||||
|
||||
def isinstance(self, *cls):
|
||||
return isinstance(self.var, cls)
|
||||
|
||||
@@ -496,7 +483,7 @@ class Wrapper(tree.Base):
|
||||
return ContextName(self, name)
|
||||
|
||||
|
||||
class ClassContext(use_metaclass(CachedMetaClass, Context, Wrapper)):
|
||||
class ClassContext(use_metaclass(CachedMetaClass, TreeContext, Wrapper)):
|
||||
"""
|
||||
This class is not only important to extend `tree.Class`, it is also a
|
||||
important for descriptors (if the descriptor methods are evaluated or not).
|
||||
@@ -602,7 +589,7 @@ class ClassContext(use_metaclass(CachedMetaClass, Context, Wrapper)):
|
||||
return "<e%s of %s>" % (type(self).__name__, self.base)
|
||||
|
||||
|
||||
class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
|
||||
class Function(use_metaclass(CachedMetaClass, TreeContext, Wrapper)):
|
||||
"""
|
||||
Needed because of decorators. Decorators are evaluated here.
|
||||
"""
|
||||
@@ -690,21 +677,12 @@ class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
|
||||
if self.base.is_generator():
|
||||
return set([iterable.Generator(self._evaluator, self, params)])
|
||||
else:
|
||||
return FunctionExecution(self._evaluator, self.parent_context, self, params).get_return_types()
|
||||
|
||||
@memoize_default()
|
||||
def py__annotations__(self):
|
||||
parser_func = self.base
|
||||
return_annotation = parser_func.annotation()
|
||||
if return_annotation:
|
||||
dct = {'return': return_annotation}
|
||||
else:
|
||||
dct = {}
|
||||
for function_param in parser_func.params:
|
||||
param_annotation = function_param.annotation()
|
||||
if param_annotation is not None:
|
||||
dct[function_param.name.value] = param_annotation
|
||||
return dct
|
||||
return FunctionExecutionContext(
|
||||
self._evaluator,
|
||||
self.parent_context,
|
||||
self.base,
|
||||
params
|
||||
).get_return_types()
|
||||
|
||||
def py__class__(self):
|
||||
# This differentiation is only necessary for Python2. Python3 does not
|
||||
@@ -715,9 +693,6 @@ class Function(use_metaclass(CachedMetaClass, Context, Wrapper)):
|
||||
name = 'FUNCTION_CLASS'
|
||||
return compiled.get_special_object(self._evaluator, name)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.base_func, name)
|
||||
|
||||
def __repr__(self):
|
||||
dec = ''
|
||||
if self.decorates is not None:
|
||||
@@ -730,7 +705,7 @@ class LambdaWrapper(Function):
|
||||
return self
|
||||
|
||||
|
||||
class FunctionExecution(Executed):
|
||||
class FunctionExecutionContext(Executed):
|
||||
"""
|
||||
This class is used to evaluate functions and their returns.
|
||||
|
||||
@@ -741,10 +716,9 @@ class FunctionExecution(Executed):
|
||||
"""
|
||||
type = 'funcdef'
|
||||
|
||||
def __init__(self, evaluator, parent_context, base, var_args):
|
||||
super(FunctionExecution, self).__init__(evaluator, parent_context, base, var_args)
|
||||
self._copy_dict = {}
|
||||
self._original_function = funcdef = base
|
||||
def __init__(self, evaluator, parent_context, funcdef, var_args):
|
||||
super(FunctionExecutionContext, self).__init__(evaluator, parent_context, var_args)
|
||||
self._funcdef = funcdef
|
||||
if isinstance(funcdef, mixed.MixedObject):
|
||||
# The extra information in mixed is not needed anymore. We can just
|
||||
# unpack it and give it the tree object.
|
||||
@@ -763,11 +737,11 @@ class FunctionExecution(Executed):
|
||||
@memoize_default(default=set())
|
||||
@recursion.execution_recursion_decorator
|
||||
def get_return_types(self, check_yields=False):
|
||||
func = self.base
|
||||
|
||||
if func.isinstance(LambdaWrapper):
|
||||
funcdef = self._funcdef
|
||||
if funcdef.type in ('lambdef', 'lambdef_nocond'):
|
||||
return self._evaluator.eval_element(self.children[-1])
|
||||
|
||||
"""
|
||||
if func.listeners:
|
||||
# Feed the listeners, with the params.
|
||||
for listener in func.listeners:
|
||||
@@ -776,16 +750,19 @@ class FunctionExecution(Executed):
|
||||
# execution ongoing. In this case Jedi is interested in the
|
||||
# inserted params, not in the actual execution of the function.
|
||||
return set()
|
||||
"""
|
||||
|
||||
if check_yields:
|
||||
types = set()
|
||||
returns = self.yields
|
||||
returns = funcdef.yields
|
||||
else:
|
||||
returns = self.returns
|
||||
types = set(docstrings.find_return_types(self._evaluator, func))
|
||||
types |= set(pep0484.find_return_types(self._evaluator, func))
|
||||
returns = funcdef.returns
|
||||
types = set(docstrings.find_return_types(self._evaluator, funcdef))
|
||||
types |= set(pep0484.find_return_types(self._evaluator, funcdef))
|
||||
|
||||
for r in returns:
|
||||
types |= self.eval_node(r.children[1])
|
||||
continue # TODO REMOVE
|
||||
check = flow_analysis.break_check(self._evaluator, self, r)
|
||||
if check is flow_analysis.UNREACHABLE:
|
||||
debug.dbg('Return unreachable: %s', r)
|
||||
@@ -793,26 +770,26 @@ class FunctionExecution(Executed):
|
||||
if check_yields:
|
||||
types |= iterable.unite(self._eval_yield(r))
|
||||
else:
|
||||
types |= self._evaluator.eval_element(r.children[1])
|
||||
types |= self.eval_node(r.children[1])
|
||||
if check is flow_analysis.REACHABLE:
|
||||
debug.dbg('Return reachable: %s', r)
|
||||
break
|
||||
return types
|
||||
|
||||
def _eval_yield(self, yield_expr):
|
||||
element = yield_expr.children[1]
|
||||
if element.type == 'yield_arg':
|
||||
node = yield_expr.children[1]
|
||||
if node.type == 'yield_arg':
|
||||
# It must be a yield from.
|
||||
yield_from_types = self._evaluator.eval_element(element.children[1])
|
||||
for result in iterable.py__iter__(self._evaluator, yield_from_types, element):
|
||||
yield_from_types = self.eval_node(node)
|
||||
for result in iterable.py__iter__(self._evaluator, yield_from_types, node):
|
||||
yield result
|
||||
else:
|
||||
yield self._evaluator.eval_element(element)
|
||||
yield self.eval_node(node)
|
||||
|
||||
@recursion.execution_recursion_decorator
|
||||
def get_yield_types(self):
|
||||
yields = self.yields
|
||||
stopAt = tree.ForStmt, tree.WhileStmt, FunctionExecution, tree.IfStmt
|
||||
stopAt = tree.ForStmt, tree.WhileStmt, tree.IfStmt
|
||||
for_parents = [(x, x.get_parent_until((stopAt))) for x in yields]
|
||||
|
||||
# Calculate if the yields are placed within the same for loop.
|
||||
@@ -857,55 +834,20 @@ class FunctionExecution(Executed):
|
||||
del evaluator.predefined_if_name_dict_dict[for_stmt]
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield FunctionExecutionFilter(self._evaluator, self, self._original_function,
|
||||
self._copied_funcdef,
|
||||
yield FunctionExecutionFilter(self._evaluator, self, self._funcdef,
|
||||
self.param_by_name,
|
||||
until_position,
|
||||
origin_scope=origin_scope)
|
||||
|
||||
@memoize_default(default=NO_DEFAULT)
|
||||
def _get_params(self):
|
||||
"""
|
||||
This returns the params for an TODO and is injected as a
|
||||
'hack' into the tree.Function class.
|
||||
This needs to be here, because Instance can have __init__ functions,
|
||||
which act the same way as normal functions.
|
||||
"""
|
||||
return param.get_params(self._evaluator, self.base, self.var_args)
|
||||
return param.get_params(self._evaluator, self._funcdef, self.var_args)
|
||||
|
||||
def param_by_name(self, name):
|
||||
return [n for n in self._get_params() if str(n) == name][0]
|
||||
|
||||
def name_for_position(self, position):
|
||||
return tree.Function.name_for_position(self, position)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in ['start_pos', 'end_pos', 'imports', 'name', 'type']:
|
||||
return super(FunctionExecution, self).__getattribute__(name)
|
||||
return getattr(self.base, name)
|
||||
|
||||
@common.safe_property
|
||||
@memoize_default()
|
||||
def returns(self):
|
||||
return tree.Scope._search_in_scope(self, tree.ReturnStmt)
|
||||
|
||||
@common.safe_property
|
||||
@memoize_default()
|
||||
def yields(self):
|
||||
return tree.Scope._search_in_scope(self, tree.YieldExpr)
|
||||
|
||||
@common.safe_property
|
||||
@memoize_default()
|
||||
def statements(self):
|
||||
return tree.Scope._search_in_scope(self, tree.ExprStmt)
|
||||
|
||||
@common.safe_property
|
||||
@memoize_default()
|
||||
def subscopes(self):
|
||||
return tree.Scope._search_in_scope(self, tree.Scope)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self.base)
|
||||
return "<%s of %s>" % (type(self).__name__, self._funcdef)
|
||||
|
||||
|
||||
class GlobalName(helpers.FakeName):
|
||||
@@ -919,7 +861,7 @@ class GlobalName(helpers.FakeName):
|
||||
name.start_pos, is_definition=True)
|
||||
|
||||
|
||||
class ModuleContext(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
||||
class ModuleContext(use_metaclass(CachedMetaClass, TreeContext, Wrapper)):
|
||||
parent_context = None
|
||||
|
||||
def __init__(self, evaluator, module, parent_module=None):
|
||||
@@ -1095,8 +1037,5 @@ class ModuleContext(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
||||
def py__class__(self):
|
||||
return compiled.get_special_object(self._evaluator, 'MODULE_CLASS')
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._module, name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (type(self).__name__, self._module)
|
||||
|
||||
@@ -37,8 +37,8 @@ def execute(evaluator, obj, arguments):
|
||||
else:
|
||||
if obj.parent_context == evaluator.BUILTINS:
|
||||
module_name = 'builtins'
|
||||
elif isinstance(obj.parent, tree.Module):
|
||||
module_name = str(obj.parent.name)
|
||||
elif isinstance(obj.parent_context, er.ModuleContext):
|
||||
module_name = obj.parent_context.name.string_name
|
||||
else:
|
||||
module_name = ''
|
||||
|
||||
|
||||
Reference in New Issue
Block a user