Introduce 'SuspendFunction$n<...>' types.
This commit is contained in:
committed by
Stanislav Erokhin
parent
a15d423db4
commit
6e1340da82
@@ -134,7 +134,7 @@ class TypeResolver(
|
|||||||
|
|
||||||
val typeElement = typeReference.typeElement
|
val typeElement = typeReference.typeElement
|
||||||
|
|
||||||
val type = resolveTypeElement(c, annotations, typeElement)
|
val type = resolveTypeElement(c, annotations, typeReference.modifierList, typeElement)
|
||||||
c.trace.recordScope(c.scope, typeReference)
|
c.trace.recordScope(c.scope, typeReference)
|
||||||
|
|
||||||
if (!type.isBare) {
|
if (!type.isBare) {
|
||||||
@@ -175,8 +175,14 @@ class TypeResolver(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveTypeElement(c: TypeResolutionContext, annotations: Annotations, typeElement: KtTypeElement?): PossiblyBareType {
|
private fun resolveTypeElement(c: TypeResolutionContext, annotations: Annotations, modifiers: KtModifierList?, typeElement: KtTypeElement?): PossiblyBareType {
|
||||||
var result: PossiblyBareType? = null
|
var result: PossiblyBareType? = null
|
||||||
|
|
||||||
|
val hasSuspendModifier = modifiers?.hasModifier(KtTokens.SUSPEND_KEYWORD) ?: false
|
||||||
|
if (hasSuspendModifier && typeElement !is KtFunctionType) {
|
||||||
|
c.trace.report(Errors.WRONG_MODIFIER_TARGET.on(modifiers!!.getModifier(KtTokens.SUSPEND_KEYWORD)!!, KtTokens.SUSPEND_KEYWORD, "non-functional type"))
|
||||||
|
}
|
||||||
|
|
||||||
typeElement?.accept(object : KtVisitorVoid() {
|
typeElement?.accept(object : KtVisitorVoid() {
|
||||||
override fun visitUserType(type: KtUserType) {
|
override fun visitUserType(type: KtUserType) {
|
||||||
val qualifierResolutionResult = resolveDescriptorForType(c.scope, type, c.trace, c.isDebuggerContext)
|
val qualifierResolutionResult = resolveDescriptorForType(c.scope, type, c.trace, c.isDebuggerContext)
|
||||||
@@ -198,7 +204,7 @@ class TypeResolver(
|
|||||||
|
|
||||||
override fun visitNullableType(nullableType: KtNullableType) {
|
override fun visitNullableType(nullableType: KtNullableType) {
|
||||||
val innerType = nullableType.getInnerType()
|
val innerType = nullableType.getInnerType()
|
||||||
val baseType = resolveTypeElement(c, annotations, innerType)
|
val baseType = resolveTypeElement(c, annotations, modifiers, innerType)
|
||||||
if (baseType.isNullable || innerType is KtNullableType || innerType is KtDynamicType) {
|
if (baseType.isNullable || innerType is KtNullableType || innerType is KtDynamicType) {
|
||||||
c.trace.report(REDUNDANT_NULLABLE.on(nullableType))
|
c.trace.report(REDUNDANT_NULLABLE.on(nullableType))
|
||||||
}
|
}
|
||||||
@@ -219,7 +225,8 @@ class TypeResolver(
|
|||||||
moduleDescriptor.builtIns, annotations, receiverType,
|
moduleDescriptor.builtIns, annotations, receiverType,
|
||||||
parameterDescriptors.map { it.type },
|
parameterDescriptors.map { it.type },
|
||||||
parameterDescriptors.map { it.name },
|
parameterDescriptors.map { it.name },
|
||||||
returnType
|
returnType,
|
||||||
|
suspendFunction = hasSuspendModifier
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
typealias Action = () -> Unit
|
||||||
|
|
||||||
|
interface SAM {
|
||||||
|
fun run()
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias Test1 = suspend () -> Unit
|
||||||
|
typealias Test2 = suspend Int.(String) -> Unit
|
||||||
|
typealias Test3 = <!WRONG_MODIFIER_TARGET!>suspend<!> Function0<Unit>
|
||||||
|
typealias Test4 = <!WRONG_MODIFIER_TARGET!>suspend<!> Action
|
||||||
|
typealias Test5 = List<suspend () -> Unit>
|
||||||
|
typealias Test6 = <!WRONG_MODIFIER_TARGET!>suspend<!> List<() -> Unit>
|
||||||
|
typealias Test7 = <!WRONG_MODIFIER_TARGET!>suspend<!> SAM
|
||||||
|
typealias Test8 = SuspendFunction0<Unit>
|
||||||
Vendored
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public interface SAM {
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public abstract fun run(): kotlin.Unit
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
public typealias Action = () -> kotlin.Unit
|
||||||
|
public typealias Test1 = suspend () -> kotlin.Unit
|
||||||
|
public typealias Test2 = suspend kotlin.Int.(kotlin.String) -> kotlin.Unit
|
||||||
|
public typealias Test3 = () -> kotlin.Unit
|
||||||
|
public typealias Test4 = Action
|
||||||
|
public typealias Test5 = kotlin.collections.List<suspend () -> kotlin.Unit>
|
||||||
|
public typealias Test6 = kotlin.collections.List<() -> kotlin.Unit>
|
||||||
|
public typealias Test7 = SAM
|
||||||
|
public typealias Test8 = suspend () -> kotlin.Unit
|
||||||
@@ -523,6 +523,11 @@ public abstract class KotlinBuiltIns {
|
|||||||
return "Function" + parameterCount;
|
return "Function" + parameterCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static String getSuspendFunctionName(int parameterCount) {
|
||||||
|
return "SuspendFunction" + parameterCount;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static ClassId getFunctionClassId(int parameterCount) {
|
public static ClassId getFunctionClassId(int parameterCount) {
|
||||||
return new ClassId(BUILT_INS_PACKAGE_FQ_NAME, Name.identifier(getFunctionName(parameterCount)));
|
return new ClassId(BUILT_INS_PACKAGE_FQ_NAME, Name.identifier(getFunctionName(parameterCount)));
|
||||||
@@ -533,6 +538,11 @@ public abstract class KotlinBuiltIns {
|
|||||||
return getBuiltInClassByName(getFunctionName(parameterCount));
|
return getBuiltInClassByName(getFunctionName(parameterCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public ClassDescriptor getSuspendFunction(int parameterCount) {
|
||||||
|
return getBuiltInClassByName(getSuspendFunctionName(parameterCount));
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public ClassDescriptor getThrowable() {
|
public ClassDescriptor getThrowable() {
|
||||||
return getBuiltInClassByName("Throwable");
|
return getBuiltInClassByName("Throwable");
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ package org.jetbrains.kotlin.builtins
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.builtins.functions.BuiltInFictitiousFunctionClassFactory
|
import org.jetbrains.kotlin.builtins.functions.BuiltInFictitiousFunctionClassFactory
|
||||||
import org.jetbrains.kotlin.descriptors.SourceElement
|
import org.jetbrains.kotlin.descriptors.SourceElement
|
||||||
|
import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationsImpl
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationsImpl
|
||||||
@@ -57,16 +60,23 @@ val KotlinType.isFunctionTypeOrSubtype: Boolean
|
|||||||
)
|
)
|
||||||
|
|
||||||
val KotlinType.isFunctionType: Boolean
|
val KotlinType.isFunctionType: Boolean
|
||||||
|
get() = constructor.declarationDescriptor?.getFunctionalClassKind() == FunctionClassDescriptor.Kind.Function
|
||||||
|
|
||||||
|
val KotlinType.isSuspendFunctionType: Boolean
|
||||||
|
get() = constructor.declarationDescriptor?.getFunctionalClassKind() == FunctionClassDescriptor.Kind.SuspendFunction
|
||||||
|
|
||||||
|
val KotlinType.isBuiltinFunctionalType: Boolean
|
||||||
get() {
|
get() {
|
||||||
val descriptor = constructor.declarationDescriptor
|
val kind = constructor.declarationDescriptor?.getFunctionalClassKind()
|
||||||
return descriptor != null && isNumberedFunctionClassFqName(descriptor.fqNameUnsafe)
|
return kind == FunctionClassDescriptor.Kind.Function ||
|
||||||
|
kind == FunctionClassDescriptor.Kind.SuspendFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
val KotlinType.isNonExtensionFunctionType: Boolean
|
val KotlinType.isNonExtensionFunctionType: Boolean
|
||||||
get() = isFunctionType && !isTypeAnnotatedWithExtensionFunctionType
|
get() = isBuiltinFunctionalType && !isTypeAnnotatedWithExtensionFunctionType
|
||||||
|
|
||||||
val KotlinType.isExtensionFunctionType: Boolean
|
val KotlinType.isExtensionFunctionType: Boolean
|
||||||
get() = isFunctionType && isTypeAnnotatedWithExtensionFunctionType
|
get() = isBuiltinFunctionalType && isTypeAnnotatedWithExtensionFunctionType
|
||||||
|
|
||||||
private val KotlinType.isTypeAnnotatedWithExtensionFunctionType: Boolean
|
private val KotlinType.isTypeAnnotatedWithExtensionFunctionType: Boolean
|
||||||
get() = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.extensionFunctionType) != null
|
get() = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.extensionFunctionType) != null
|
||||||
@@ -85,18 +95,29 @@ fun isNumberedFunctionClassFqName(fqName: FqNameUnsafe): Boolean {
|
|||||||
return BuiltInFictitiousFunctionClassFactory.isFunctionClassName(shortName, KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME)
|
return BuiltInFictitiousFunctionClassFactory.isFunctionClassName(shortName, KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun DeclarationDescriptor.getFunctionalClassKind(): FunctionClassDescriptor.Kind? {
|
||||||
|
if (this !is ClassDescriptor) return null
|
||||||
|
|
||||||
|
val fqNameUnsafe = this.fqNameUnsafe
|
||||||
|
if (!fqNameUnsafe.isSafe || fqNameUnsafe.isRoot) return null
|
||||||
|
val fqName = fqNameUnsafe.toSafe()
|
||||||
|
|
||||||
|
return BuiltInFictitiousFunctionClassFactory.getFunctionalClassKind(fqName.shortName().asString(), fqName.parent())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun KotlinType.getReceiverTypeFromFunctionType(): KotlinType? {
|
fun KotlinType.getReceiverTypeFromFunctionType(): KotlinType? {
|
||||||
assert(isFunctionType) { "Not a function type: ${this}" }
|
assert(isBuiltinFunctionalType) { "Not a function type: ${this}" }
|
||||||
return if (isTypeAnnotatedWithExtensionFunctionType) arguments.first().type else null
|
return if (isTypeAnnotatedWithExtensionFunctionType) arguments.first().type else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KotlinType.getReturnTypeFromFunctionType(): KotlinType {
|
fun KotlinType.getReturnTypeFromFunctionType(): KotlinType {
|
||||||
assert(isFunctionType) { "Not a function type: ${this}" }
|
assert(isBuiltinFunctionalType) { "Not a function type: ${this}" }
|
||||||
return arguments.last().type
|
return arguments.last().type
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KotlinType.getValueParameterTypesFromFunctionType(): List<TypeProjection> {
|
fun KotlinType.getValueParameterTypesFromFunctionType(): List<TypeProjection> {
|
||||||
assert(isFunctionType) { "Not a function type: ${this}" }
|
assert(isBuiltinFunctionalType) { "Not a function type: ${this}" }
|
||||||
val arguments = arguments
|
val arguments = arguments
|
||||||
val first = if (isExtensionFunctionType) 1 else 0
|
val first = if (isExtensionFunctionType) 1 else 0
|
||||||
val last = arguments.size - 1
|
val last = arguments.size - 1
|
||||||
@@ -147,17 +168,20 @@ fun getFunctionTypeArgumentProjections(
|
|||||||
return arguments
|
return arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
fun createFunctionType(
|
fun createFunctionType(
|
||||||
builtIns: KotlinBuiltIns,
|
builtIns: KotlinBuiltIns,
|
||||||
annotations: Annotations,
|
annotations: Annotations,
|
||||||
receiverType: KotlinType?,
|
receiverType: KotlinType?,
|
||||||
parameterTypes: List<KotlinType>,
|
parameterTypes: List<KotlinType>,
|
||||||
parameterNames: List<Name>?,
|
parameterNames: List<Name>?,
|
||||||
returnType: KotlinType
|
returnType: KotlinType,
|
||||||
|
suspendFunction: Boolean = false
|
||||||
): SimpleType {
|
): SimpleType {
|
||||||
val arguments = getFunctionTypeArgumentProjections(receiverType, parameterTypes, parameterNames, returnType, builtIns)
|
val arguments = getFunctionTypeArgumentProjections(receiverType, parameterTypes, parameterNames, returnType, builtIns)
|
||||||
val size = parameterTypes.size
|
val size = parameterTypes.size
|
||||||
val classDescriptor = builtIns.getFunction(if (receiverType == null) size else size + 1)
|
val parameterCount = if (receiverType == null) size else size + 1
|
||||||
|
val classDescriptor = if (suspendFunction) builtIns.getSuspendFunction(parameterCount) else builtIns.getFunction(parameterCount)
|
||||||
|
|
||||||
val typeAnnotations =
|
val typeAnnotations =
|
||||||
if (receiverType == null || annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.extensionFunctionType) != null) {
|
if (receiverType == null || annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.extensionFunctionType) != null) {
|
||||||
|
|||||||
+9
-4
@@ -38,10 +38,9 @@ class BuiltInFictitiousFunctionClassFactory(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun parseClassName(className: String, packageFqName: FqName): KindWithArity? {
|
private fun parseClassName(className: String, packageFqName: FqName): KindWithArity? {
|
||||||
val kind = FunctionClassDescriptor.Kind.byPackage(packageFqName) ?: return null
|
val kind = FunctionClassDescriptor.Kind.byClassNamePrefix(packageFqName, className) ?: return null
|
||||||
|
|
||||||
val prefix = kind.classNamePrefix
|
val prefix = kind.classNamePrefix
|
||||||
if (!className.startsWith(prefix)) return null
|
|
||||||
|
|
||||||
val arity = toInt(className.substring(prefix.length)) ?: return null
|
val arity = toInt(className.substring(prefix.length)) ?: return null
|
||||||
|
|
||||||
@@ -49,8 +48,14 @@ class BuiltInFictitiousFunctionClassFactory(
|
|||||||
return KindWithArity(kind, arity)
|
return KindWithArity(kind, arity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun getFunctionalClassKind(className: String, packageFqName: FqName) =
|
||||||
|
parseClassName(className, packageFqName)?.kind
|
||||||
|
|
||||||
@JvmStatic fun isFunctionClassName(className: String, packageFqName: FqName) =
|
@JvmStatic fun isFunctionClassName(className: String, packageFqName: FqName) =
|
||||||
parseClassName(className, packageFqName) != null
|
getFunctionalClassKind(className, packageFqName) == Kind.Function
|
||||||
|
|
||||||
|
@JvmStatic fun isSuspendFunctionClassName(className: String, packageFqName: FqName) =
|
||||||
|
getFunctionalClassKind(className, packageFqName) == Kind.SuspendFunction
|
||||||
|
|
||||||
private fun toInt(s: String): Int? {
|
private fun toInt(s: String): Int? {
|
||||||
if (s.isEmpty()) return null
|
if (s.isEmpty()) return null
|
||||||
@@ -67,7 +72,7 @@ class BuiltInFictitiousFunctionClassFactory(
|
|||||||
|
|
||||||
override fun shouldCreateClass(packageFqName: FqName, name: Name): Boolean {
|
override fun shouldCreateClass(packageFqName: FqName, name: Name): Boolean {
|
||||||
val string = name.asString()
|
val string = name.asString()
|
||||||
return (string.startsWith("Function") || string.startsWith("KFunction")) // an optimization
|
return (string.startsWith("Function") || string.startsWith("KFunction") || string.startsWith("SuspendFunction")) // an optimization
|
||||||
&& parseClassName(string, packageFqName) != null
|
&& parseClassName(string, packageFqName) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl
|
|||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||||
import org.jetbrains.kotlin.storage.StorageManager
|
import org.jetbrains.kotlin.storage.StorageManager
|
||||||
import org.jetbrains.kotlin.types.*
|
import org.jetbrains.kotlin.types.*
|
||||||
@@ -49,6 +50,7 @@ class FunctionClassDescriptor(
|
|||||||
|
|
||||||
enum class Kind(val packageFqName: FqName, val classNamePrefix: String) {
|
enum class Kind(val packageFqName: FqName, val classNamePrefix: String) {
|
||||||
Function(BUILT_INS_PACKAGE_FQ_NAME, "Function"),
|
Function(BUILT_INS_PACKAGE_FQ_NAME, "Function"),
|
||||||
|
SuspendFunction(BUILT_INS_PACKAGE_FQ_NAME, "SuspendFunction"),
|
||||||
KFunction(KOTLIN_REFLECT_FQ_NAME, "KFunction");
|
KFunction(KOTLIN_REFLECT_FQ_NAME, "KFunction");
|
||||||
|
|
||||||
fun numberedClassName(arity: Int) = Name.identifier("$classNamePrefix$arity")
|
fun numberedClassName(arity: Int) = Name.identifier("$classNamePrefix$arity")
|
||||||
@@ -59,6 +61,9 @@ class FunctionClassDescriptor(
|
|||||||
KOTLIN_REFLECT_FQ_NAME -> KFunction
|
KOTLIN_REFLECT_FQ_NAME -> KFunction
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun byClassNamePrefix(packageFqName: FqName, className: String) =
|
||||||
|
Kind.values().firstOrNull { it.packageFqName == packageFqName && className.startsWith(it.classNamePrefix) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,8 +133,15 @@ class FunctionClassDescriptor(
|
|||||||
result.add(KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, descriptor, arguments))
|
result.add(KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, descriptor, arguments))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add unnumbered base class, e.g. Function for Function{n}, KFunction for KFunction{n}
|
|
||||||
add(containingDeclaration, Name.identifier(functionKind.classNamePrefix))
|
if (functionKind == Kind.SuspendFunction) {
|
||||||
|
// SuspendFunction$N<...> <: Any
|
||||||
|
result.add(containingDeclaration.builtIns.anyType)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Add unnumbered base class, e.g. Function for Function{n}, KFunction for KFunction{n}
|
||||||
|
add(containingDeclaration, Name.identifier(functionKind.classNamePrefix))
|
||||||
|
}
|
||||||
|
|
||||||
// For KFunction{n}, add corresponding numbered Function{n} class, e.g. Function2 for KFunction2
|
// For KFunction{n}, add corresponding numbered Function{n} class, e.g. Function2 for KFunction2
|
||||||
if (functionKind == Kind.KFunction) {
|
if (functionKind == Kind.KFunction) {
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ internal class DescriptorRendererImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldRenderAsPrettyFunctionType(type: KotlinType): Boolean {
|
private fun shouldRenderAsPrettyFunctionType(type: KotlinType): Boolean {
|
||||||
return type.isFunctionType && type.arguments.none { it.isStarProjection }
|
return type.isBuiltinFunctionalType && type.arguments.none { it.isStarProjection }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renderFlexibleType(lowerRendered: String, upperRendered: String, builtIns: KotlinBuiltIns): String {
|
override fun renderFlexibleType(lowerRendered: String, upperRendered: String, builtIns: KotlinBuiltIns): String {
|
||||||
@@ -322,6 +322,10 @@ internal class DescriptorRendererImpl(
|
|||||||
|
|
||||||
if (needParenthesis) append("(")
|
if (needParenthesis) append("(")
|
||||||
|
|
||||||
|
if (type.isSuspendFunctionType) {
|
||||||
|
append("suspend ") // anything special about modifier?
|
||||||
|
}
|
||||||
|
|
||||||
if (receiverType != null) {
|
if (receiverType != null) {
|
||||||
val surroundReceiver = shouldRenderAsPrettyFunctionType(receiverType) && !receiverType.isMarkedNullable
|
val surroundReceiver = shouldRenderAsPrettyFunctionType(receiverType) && !receiverType.isMarkedNullable
|
||||||
if (surroundReceiver) {
|
if (surroundReceiver) {
|
||||||
|
|||||||
Reference in New Issue
Block a user