1st test for traits work
This commit is contained in:
Generated
+6
@@ -23,10 +23,16 @@
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<RunnerSettings RunnerId="Debug">
|
||||
<option name="DEBUG_PORT" value="53032" />
|
||||
<option name="TRANSPORT" value="0" />
|
||||
<option name="LOCAL" value="true" />
|
||||
</RunnerSettings>
|
||||
<RunnerSettings RunnerId="Profile ">
|
||||
<option name="myExternalizedOptions" value=" snapshots-dir= additional-options2=onexit\=snapshot " />
|
||||
</RunnerSettings>
|
||||
<RunnerSettings RunnerId="Run" />
|
||||
<ConfigurationWrapper RunnerId="Debug" />
|
||||
<ConfigurationWrapper RunnerId="Run" />
|
||||
<method />
|
||||
</configuration>
|
||||
|
||||
@@ -71,13 +71,17 @@ public abstract class ClassBodyCodegen {
|
||||
}
|
||||
else if (declaration instanceof JetNamedFunction) {
|
||||
try {
|
||||
functionCodegen.gen((JetNamedFunction) declaration);
|
||||
genNamedFunction((JetNamedFunction) declaration, functionCodegen);
|
||||
} catch (RuntimeException e) {
|
||||
throw new RuntimeException("Error generating method " + myClass.getName() + "." + declaration.getName() + " in " + context, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void genNamedFunction(JetNamedFunction declaration, FunctionCodegen functionCodegen) {
|
||||
functionCodegen.gen(declaration);
|
||||
}
|
||||
|
||||
private void generatePrimaryConstructorProperties(PropertyCodegen propertyCodegen) {
|
||||
OwnerKind kind = context.getContextKind();
|
||||
for (JetParameter p : getPrimaryConstructorParameters()) {
|
||||
@@ -90,7 +94,7 @@ public abstract class ClassBodyCodegen {
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (!(kind instanceof OwnerKind.DelegateKind) && kind != OwnerKind.INTERFACE && state.getBindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)) {
|
||||
if (!(kind instanceof OwnerKind.DelegateKind) && state.getBindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)) {
|
||||
v.visitField(Opcodes.ACC_PRIVATE, p.getName(), state.getTypeMapper().mapType(propertyDescriptor.getOutType()).getDescriptor(), null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.objectweb.asm.commons.InstructionAdapter;
|
||||
|
||||
/**
|
||||
* @author max
|
||||
* @author alex.tkachman
|
||||
*/
|
||||
public class ClassCodegen {
|
||||
private final GenerationState state;
|
||||
@@ -35,6 +36,11 @@ public class ClassCodegen {
|
||||
ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, aClass);
|
||||
ClassVisitor v = state.forClassImplementation(descriptor);
|
||||
new ImplementationBodyCodegen(aClass, parentContext.intoClass(descriptor, kind), v, state).generate();
|
||||
|
||||
if(aClass instanceof JetClass && ((JetClass)aClass).isTrait()) {
|
||||
v = state.forTraitImplementation(descriptor);
|
||||
new TraitImplBodyCodegen(aClass, parentContext.intoClass(descriptor, OwnerKind.TRAIT_IMPL), v, state).generate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -914,7 +914,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> {
|
||||
|
||||
CallableMethod callableMethod;
|
||||
if (declarationPsiElement instanceof PsiMethod || declarationPsiElement instanceof JetNamedFunction) {
|
||||
callableMethod = typeMapper.mapToCallableMethod((PsiNamedElement) declarationPsiElement);
|
||||
callableMethod = typeMapper.mapToCallableMethod((PsiNamedElement) declarationPsiElement, null);
|
||||
}
|
||||
else if (fd instanceof FunctionDescriptor) {
|
||||
callableMethod = ClosureCodegen.asCallableMethod((FunctionDescriptor) fd);
|
||||
@@ -1708,7 +1708,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> {
|
||||
accessor = JetTypeMapper.mapToCallableMethod((PsiMethod) declaration);
|
||||
}
|
||||
else if (declaration instanceof JetNamedFunction) {
|
||||
accessor = typeMapper.mapToCallableMethod((JetNamedFunction) declaration);
|
||||
accessor = typeMapper.mapToCallableMethod((JetNamedFunction) declaration, null);
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException("unknown accessor type: " + declaration);
|
||||
|
||||
@@ -35,7 +35,7 @@ public class FunctionCodegen {
|
||||
}
|
||||
|
||||
public void gen(JetNamedFunction f) {
|
||||
Method method = state.getTypeMapper().mapToCallableMethod(f).getSignature();
|
||||
Method method = state.getTypeMapper().mapToCallableMethod(f, owner.getContextKind()).getSignature();
|
||||
final FunctionDescriptor functionDescriptor = state.getBindingContext().get(BindingContext.FUNCTION, f);
|
||||
generateMethod(f, method, functionDescriptor);
|
||||
}
|
||||
@@ -86,10 +86,10 @@ public class FunctionCodegen {
|
||||
|
||||
OwnerKind kind = context.getContextKind();
|
||||
|
||||
boolean isStatic = kind == OwnerKind.NAMESPACE;
|
||||
boolean isStatic = kind == OwnerKind.NAMESPACE || kind == OwnerKind.TRAIT_IMPL;
|
||||
if (isStatic) flags |= Opcodes.ACC_STATIC;
|
||||
|
||||
boolean isAbstract = bodyExpressions == null;
|
||||
boolean isAbstract = !isStatic && (bodyExpressions == null || TypeUtils.isInterface(functionDescriptor.getContainingDeclaration(), state.getBindingContext()));
|
||||
if (isAbstract) flags |= Opcodes.ACC_ABSTRACT;
|
||||
|
||||
final MethodVisitor mv = v.visitMethod(flags, jvmSignature.getName(), jvmSignature.getDescriptor(), null, null);
|
||||
|
||||
@@ -70,8 +70,8 @@ public class GenerationState {
|
||||
return factory.newVisitor(typeMapper.jvmName(aClass, OwnerKind.IMPLEMENTATION) + ".class");
|
||||
}
|
||||
|
||||
public ClassVisitor forClassDelegatingImplementation(ClassDescriptor aClass) {
|
||||
return factory.newVisitor(JetTypeMapper.jvmNameForDelegatingImplementation(aClass) + ".class");
|
||||
public ClassVisitor forTraitImplementation(ClassDescriptor aClass) {
|
||||
return factory.newVisitor(typeMapper.jvmName(aClass, OwnerKind.TRAIT_IMPL) + ".class");
|
||||
}
|
||||
|
||||
public Pair<String, ClassVisitor> forAnonymousSubclass(JetExpression expression) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.jetbrains.jet.lang.descriptors.*;
|
||||
import org.jetbrains.jet.lang.psi.*;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext;
|
||||
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
|
||||
import org.jetbrains.jet.lang.types.JetStandardClasses;
|
||||
import org.jetbrains.jet.lang.types.JetType;
|
||||
import org.jetbrains.jet.lang.types.Variance;
|
||||
import org.jetbrains.jet.lexer.JetTokens;
|
||||
@@ -215,7 +216,6 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
return;
|
||||
|
||||
ConstructorDescriptor constructorDescriptor = state.getBindingContext().get(BindingContext.CONSTRUCTOR, myClass);
|
||||
// if (constructorDescriptor == null && !(myClass instanceof JetObjectDeclaration) && !isEnum(myClass)) return;
|
||||
|
||||
Method method;
|
||||
CallableMethod callableMethod;
|
||||
@@ -302,6 +302,8 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
|
||||
generateInitializers(codegen, iv);
|
||||
|
||||
generateTraitMethods(codegen);
|
||||
|
||||
int curParam = 0;
|
||||
List<JetParameter> constructorParameters = getPrimaryConstructorParameters();
|
||||
for (JetParameter parameter : constructorParameters) {
|
||||
@@ -320,9 +322,67 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
static boolean isEnum(JetClassOrObject myClass) {
|
||||
final JetModifierList modifierList = myClass.getModifierList();
|
||||
return modifierList != null && modifierList.hasModifier(JetTokens.ENUM_KEYWORD);
|
||||
private void generateTraitMethods(ExpressionCodegen codegen) {
|
||||
if(!(myClass instanceof JetClass) || ((JetClass)myClass).isTrait() || ((JetClass)myClass).hasModifier(JetTokens.ABSTRACT_KEYWORD))
|
||||
return;
|
||||
|
||||
HashSet<FunctionDescriptor> set = new HashSet<FunctionDescriptor>();
|
||||
getAbstractMethods(descriptor.getDefaultType(), set);
|
||||
for(FunctionDescriptor fun: set) {
|
||||
int flags = Opcodes.ACC_PUBLIC; // TODO.
|
||||
|
||||
Method function = state.getTypeMapper().mapSignature(fun.getName(), fun);
|
||||
|
||||
final MethodVisitor mv = v.visitMethod(flags, function.getName(), function.getDescriptor(), null, null);
|
||||
mv.visitCode();
|
||||
|
||||
codegen.generateThisOrOuter(descriptor);
|
||||
|
||||
Type[] argTypes = function.getArgumentTypes();
|
||||
InstructionAdapter iv = new InstructionAdapter(mv);
|
||||
iv.load(0, JetTypeMapper.TYPE_OBJECT);
|
||||
for (int i = 0, reg = 1; i < argTypes.length; i++) {
|
||||
Type argType = argTypes[i];
|
||||
iv.load(reg, argType);
|
||||
//noinspection AssignmentToForLoopParameter
|
||||
reg += argType.getSize();
|
||||
}
|
||||
|
||||
String fdescriptor = function.getDescriptor().replace("(","(L" + state.getTypeMapper().jvmName((ClassDescriptor) fun.getContainingDeclaration(),OwnerKind.IMPLEMENTATION) + ";");
|
||||
iv.invokestatic(state.getTypeMapper().jvmName((ClassDescriptor) fun.getContainingDeclaration(), OwnerKind.TRAIT_IMPL), function.getName(), fdescriptor);
|
||||
iv.areturn(function.getReturnType());
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private void getAbstractMethods(JetType type, Set<FunctionDescriptor> set) {
|
||||
if(type.equals(JetStandardClasses.getAny())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(JetType superType : type.getConstructor().getSupertypes()) {
|
||||
getAbstractMethods(superType, set);
|
||||
}
|
||||
|
||||
PsiElement psiElement = state.getBindingContext().get(BindingContext.DESCRIPTOR_TO_DECLARATION, type.getConstructor().getDeclarationDescriptor());
|
||||
if(psiElement instanceof JetClass) {
|
||||
JetClass jetClass = (JetClass) psiElement;
|
||||
for(JetDeclaration decl : jetClass.getDeclarations()) {
|
||||
if(decl instanceof JetNamedFunction) {
|
||||
JetNamedFunction jetNamedFunction = (JetNamedFunction) decl;
|
||||
FunctionDescriptor funDescriptor = state.getBindingContext().get(BindingContext.FUNCTION, decl);
|
||||
set.removeAll(funDescriptor.getOverriddenDescriptors());
|
||||
|
||||
if(jetNamedFunction.getBodyExpression() == null || jetClass.isTrait()) {
|
||||
set.add(funDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(psiElement instanceof PsiClass) {
|
||||
PsiClass psiClass = (PsiClass) psiElement;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -218,15 +218,15 @@ public class JetTypeMapper {
|
||||
|
||||
public String jvmName(JetClassObject classObject) {
|
||||
final ClassDescriptor descriptor = bindingContext.get(BindingContext.CLASS, classObject.getObjectDeclaration());
|
||||
return jvmName(descriptor, OwnerKind.IMPLEMENTATION);
|
||||
return jvmName(descriptor, OwnerKind.IMPLEMENTATION);
|
||||
}
|
||||
|
||||
public boolean isInterface(ClassDescriptor jetClass, OwnerKind kind) {
|
||||
public boolean isInterface(ClassDescriptor jetClass) {
|
||||
PsiElement declaration = bindingContext.get(BindingContext.DESCRIPTOR_TO_DECLARATION, jetClass);
|
||||
if (declaration instanceof JetObjectDeclaration) {
|
||||
return false;
|
||||
}
|
||||
return jetClass instanceof JetClass ? ((JetClass)jetClass).isTrait() : false;
|
||||
return declaration instanceof JetClass && ((JetClass) declaration).isTrait();
|
||||
}
|
||||
|
||||
private static String jetJvmName(ClassDescriptor jetClass, OwnerKind kind) {
|
||||
@@ -236,6 +236,9 @@ public class JetTypeMapper {
|
||||
else if (kind == OwnerKind.IMPLEMENTATION) {
|
||||
return jvmNameForImplementation(jetClass);
|
||||
}
|
||||
else if (kind == OwnerKind.TRAIT_IMPL) {
|
||||
return jvmNameForTraitImpl(jetClass);
|
||||
}
|
||||
else {
|
||||
assert false : "Unsuitable kind";
|
||||
return "java/lang/Object";
|
||||
@@ -277,8 +280,8 @@ public class JetTypeMapper {
|
||||
return jvmNameForInterface(descriptor);
|
||||
}
|
||||
|
||||
public static String jvmNameForDelegatingImplementation(ClassDescriptor descriptor) {
|
||||
return jvmNameForInterface(descriptor) + "$$DImpl";
|
||||
private static String jvmNameForTraitImpl(ClassDescriptor descriptor) {
|
||||
return jvmNameForInterface(descriptor) + "$$TImpl";
|
||||
}
|
||||
|
||||
public String getOwner(DeclarationDescriptor descriptor, OwnerKind kind) {
|
||||
@@ -426,7 +429,7 @@ public class JetTypeMapper {
|
||||
}
|
||||
|
||||
|
||||
private Method mapSignature(JetNamedFunction f, List<Type> valueParameterTypes) {
|
||||
private Method mapSignature(JetNamedFunction f, List<Type> valueParameterTypes, OwnerKind kind) {
|
||||
final JetTypeReference receiverTypeRef = f.getReceiverTypeRef();
|
||||
final JetType receiverType = receiverTypeRef == null ? null : bindingContext.get(BindingContext.TYPE, receiverTypeRef);
|
||||
final List<JetParameter> parameters = f.getValueParameters();
|
||||
@@ -434,6 +437,13 @@ public class JetTypeMapper {
|
||||
if (receiverType != null) {
|
||||
parameterTypes.add(mapType(receiverType));
|
||||
}
|
||||
FunctionDescriptor functionDescriptor = bindingContext.get(BindingContext.FUNCTION, f);
|
||||
if(kind == OwnerKind.TRAIT_IMPL) {
|
||||
JetType jetType = ((ClassDescriptor)functionDescriptor.getContainingDeclaration()).getDefaultType();
|
||||
final Type type = mapType(jetType);
|
||||
valueParameterTypes.add(type);
|
||||
parameterTypes.add(type);
|
||||
}
|
||||
for (JetParameter parameter : parameters) {
|
||||
final Type type = mapType(bindingContext.get(BindingContext.TYPE, parameter.getTypeReference()));
|
||||
valueParameterTypes.add(type);
|
||||
@@ -445,7 +455,6 @@ public class JetTypeMapper {
|
||||
final JetTypeReference returnTypeRef = f.getReturnTypeRef();
|
||||
Type returnType;
|
||||
if (returnTypeRef == null) {
|
||||
final FunctionDescriptor functionDescriptor = bindingContext.get(BindingContext.FUNCTION, f);
|
||||
assert functionDescriptor != null;
|
||||
final JetType type = functionDescriptor.getReturnType();
|
||||
returnType = mapReturnType(type);
|
||||
@@ -456,7 +465,7 @@ public class JetTypeMapper {
|
||||
return new Method(f.getName(), returnType, parameterTypes.toArray(new Type[parameterTypes.size()]));
|
||||
}
|
||||
|
||||
public CallableMethod mapToCallableMethod(PsiNamedElement declaration) {
|
||||
public CallableMethod mapToCallableMethod(PsiNamedElement declaration, OwnerKind kind) {
|
||||
if (declaration instanceof PsiMethod) {
|
||||
return mapToCallableMethod((PsiMethod) declaration);
|
||||
}
|
||||
@@ -468,7 +477,7 @@ public class JetTypeMapper {
|
||||
assert functionDescriptor != null;
|
||||
final DeclarationDescriptor functionParent = functionDescriptor.getContainingDeclaration();
|
||||
final List<Type> valueParameterTypes = new ArrayList<Type>();
|
||||
Method descriptor = mapSignature(f, valueParameterTypes);
|
||||
Method descriptor = mapSignature(f, valueParameterTypes, kind);
|
||||
String owner;
|
||||
int invokeOpcode;
|
||||
boolean needsReceiver;
|
||||
@@ -481,7 +490,7 @@ public class JetTypeMapper {
|
||||
else if (functionParent instanceof ClassDescriptor) {
|
||||
ClassDescriptor containingClass = (ClassDescriptor) functionParent;
|
||||
owner = jvmName(containingClass, OwnerKind.IMPLEMENTATION);
|
||||
invokeOpcode = isInterface(containingClass, OwnerKind.INTERFACE)
|
||||
invokeOpcode = isInterface(containingClass)
|
||||
? Opcodes.INVOKEINTERFACE
|
||||
: Opcodes.INVOKEVIRTUAL;
|
||||
needsReceiver = true;
|
||||
|
||||
@@ -11,8 +11,8 @@ public class OwnerKind {
|
||||
}
|
||||
|
||||
public static final OwnerKind NAMESPACE = new OwnerKind("namespace");
|
||||
public static final OwnerKind INTERFACE = new OwnerKind("interface");
|
||||
public static final OwnerKind IMPLEMENTATION = new OwnerKind("implementation");
|
||||
public static final OwnerKind TRAIT_IMPL = new OwnerKind("trait implementation");
|
||||
|
||||
public static class DelegateKind extends OwnerKind {
|
||||
private final StackValue delegate;
|
||||
|
||||
@@ -42,24 +42,6 @@ public class PropertyCodegen {
|
||||
generateGetter(p, propertyDescriptor);
|
||||
generateSetter(p, propertyDescriptor);
|
||||
}
|
||||
else if (kind == OwnerKind.INTERFACE) {
|
||||
final JetPropertyAccessor getter = p.getGetter();
|
||||
if ((getter != null && !getter.hasModifier(JetTokens.PRIVATE_KEYWORD) ||
|
||||
(getter == null && isExternallyAccessible(p)))) {
|
||||
v.visitMethod(Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC,
|
||||
getterName(p.getName()),
|
||||
state.getTypeMapper().mapGetterSignature(propertyDescriptor).getDescriptor(),
|
||||
null, null);
|
||||
}
|
||||
final JetPropertyAccessor setter = p.getSetter();
|
||||
if ((setter != null && !setter.hasModifier(JetTokens.PRIVATE_KEYWORD) ||
|
||||
(setter == null && isExternallyAccessible(p) && p.isVar()))) {
|
||||
v.visitMethod(Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC,
|
||||
setterName(p.getName()),
|
||||
state.getTypeMapper().mapSetterSignature(propertyDescriptor).getDescriptor(),
|
||||
null, null);
|
||||
}
|
||||
}
|
||||
else if (kind instanceof OwnerKind.DelegateKind) {
|
||||
generateDefaultGetter(propertyDescriptor, Opcodes.ACC_PUBLIC);
|
||||
if (propertyDescriptor.isVar()) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.jetbrains.jet.codegen;
|
||||
|
||||
import org.jetbrains.jet.lang.psi.JetClass;
|
||||
import org.jetbrains.jet.lang.psi.JetClassOrObject;
|
||||
import org.jetbrains.jet.lang.psi.JetDeclaration;
|
||||
import org.jetbrains.jet.lang.psi.JetNamedFunction;
|
||||
import org.jetbrains.jet.lexer.JetTokens;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TraitImplBodyCodegen extends ClassBodyCodegen {
|
||||
public TraitImplBodyCodegen(JetClassOrObject aClass, ClassContext context, ClassVisitor v, GenerationState state) {
|
||||
super(aClass, context, v, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void generateDeclaration() {
|
||||
v.visit(Opcodes.V1_6,
|
||||
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
|
||||
jvmName(),
|
||||
null,
|
||||
"java/lang/Object",
|
||||
new String[0]
|
||||
);
|
||||
v.visitSource(myClass.getContainingFile().getName(), null);
|
||||
}
|
||||
|
||||
private String jvmName() {
|
||||
return state.getTypeMapper().jvmName(descriptor, OwnerKind.TRAIT_IMPL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void genNamedFunction(JetNamedFunction declaration, FunctionCodegen functionCodegen) {
|
||||
super.genNamedFunction(declaration, functionCodegen); //To change body of overridden methods use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class PsiMethodCall implements IntrinsicMethod {
|
||||
@Override
|
||||
public StackValue generate(ExpressionCodegen codegen, InstructionAdapter v, Type expectedType, PsiElement element,
|
||||
List<JetExpression> arguments, StackValue receiver) {
|
||||
final CallableMethod callableMethod = codegen.getTypeMapper().mapToCallableMethod(myMethod);
|
||||
final CallableMethod callableMethod = codegen.getTypeMapper().mapToCallableMethod(myMethod, null);
|
||||
codegen.invokeMethodWithArguments(callableMethod, (JetCallExpression) element, receiver);
|
||||
return StackValue.onStack(callableMethod.getSignature().getReturnType());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
trait SimpleClass : java.lang.Object {
|
||||
fun foo() : String = "239 " + toString ()
|
||||
}
|
||||
|
||||
class SimpleClassImpl() : SimpleClass {
|
||||
override fun toString() = "SimpleClassImpl"
|
||||
}
|
||||
|
||||
fun box() : String {
|
||||
val c = SimpleClassImpl()
|
||||
return if("239 SimpleClassImpl" == c.foo()) "OK" else "fail"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jetbrains.jet.codegen;
|
||||
|
||||
public class TraitsTest extends CodegenTestCase {
|
||||
@Override
|
||||
protected String getPrefix() {
|
||||
return "traits";
|
||||
}
|
||||
|
||||
public void testSimple () throws Exception {
|
||||
blackBoxFile("traits/simple.jet");
|
||||
System.out.println(generateToText());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user