[IR] Use full name for expect/actual functions linking, simplify code
Don't put type parameters to expect-actual map since it's useless ^KT-56329 Fixed
This commit is contained in:
committed by
Space Team
parent
4e705d2f40
commit
241f457943
+6
@@ -33123,6 +33123,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
public void testKt_51753_2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/kt-51753-2.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt-56329.kt")
|
||||
public void testKt_56329() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/kt-56329.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-67
@@ -16,21 +16,20 @@ import org.jetbrains.kotlin.ir.util.kotlinFqName
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
internal class ExpectActualCollector(private val mainFragment: IrModuleFragment, private val dependentFragments: List<IrModuleFragment>) {
|
||||
fun collect(): Map<IrSymbol, IrSymbol> {
|
||||
fun collect(): Pair<Map<IrSymbol, IrSymbol>, Map<FqName, FqName>> {
|
||||
val result = mutableMapOf<IrSymbol, IrSymbol>()
|
||||
// Collect and link classifiers at first to make it possible to expand type aliases on the callables linking
|
||||
val (allActualDeclarations, typeAliasMap) = result.appendExpectActualClassifiersMap()
|
||||
result.appendExpectActualCallablesMap(allActualDeclarations, typeAliasMap, dependentFragments)
|
||||
return result
|
||||
return result to typeAliasMap
|
||||
}
|
||||
|
||||
private fun MutableMap<IrSymbol, IrSymbol>.appendExpectActualClassifiersMap(): Pair<Set<IrDeclaration>, Map<FqName, FqName>> {
|
||||
val actualClassifiers = mutableMapOf<FqName, IrSymbol>()
|
||||
// There is no list for builtins declarations, that's why they are being collected from typealiases
|
||||
// There is no list for builtins declarations; that's why they are being collected from typealiases
|
||||
val allActualDeclarations = mutableSetOf<IrDeclaration>()
|
||||
val typeAliasMap = mutableMapOf<FqName, FqName>() // It's used to link members from expect class that have typealias actual
|
||||
|
||||
@@ -78,15 +77,6 @@ internal class ExpectActualCollector(private val mainFragment: IrModuleFragment,
|
||||
visitDeclaration(declaration, data)
|
||||
}
|
||||
|
||||
override fun visitTypeParameter(declaration: IrTypeParameter, data: Boolean) {
|
||||
if (!data && !declaration.isExpect) {
|
||||
actualClassifiers[FqName.fromSegments(
|
||||
listOf(declaration.parent.kotlinFqName.asString(), declaration.name.asString())
|
||||
)] = declaration.symbol
|
||||
}
|
||||
visitDeclaration(declaration, data)
|
||||
}
|
||||
|
||||
override fun visitDeclaration(declaration: IrDeclarationBase, data: Boolean) {
|
||||
if (!data && !declaration.isExpect) {
|
||||
allActualClassifiers.add(declaration)
|
||||
@@ -128,16 +118,6 @@ internal class ExpectActualCollector(private val mainFragment: IrModuleFragment,
|
||||
visitElement(declaration)
|
||||
}
|
||||
|
||||
override fun visitTypeParameter(declaration: IrTypeParameter) {
|
||||
if (declaration.isProperExpect) {
|
||||
addLinkOrReportMissing(
|
||||
declaration,
|
||||
FqName.fromSegments(listOf(declaration.parent.kotlinFqName.asString(), declaration.name.asString()))
|
||||
)
|
||||
}
|
||||
visitElement(declaration)
|
||||
}
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
@@ -148,30 +128,23 @@ internal class ExpectActualCollector(private val mainFragment: IrModuleFragment,
|
||||
typeAliasMap: Map<FqName, FqName>,
|
||||
dependentFragments: List<IrModuleFragment>
|
||||
) {
|
||||
val actualFunctions = mutableMapOf<CallableId, MutableList<IrFunction>>()
|
||||
val actualProperties = mutableMapOf<CallableId, IrProperty>()
|
||||
val actualMembers = mutableMapOf<String, IrDeclarationBase>()
|
||||
|
||||
collectActualCallables(actualFunctions, actualProperties, allActualDeclarations)
|
||||
val collector = CallablesLinkCollector(this, actualFunctions, actualProperties, typeAliasMap)
|
||||
collectActualCallables(this, actualMembers, allActualDeclarations)
|
||||
val collector = CallablesLinkCollector(this, actualMembers, typeAliasMap)
|
||||
dependentFragments.forEach { collector.visitModuleFragment(it) }
|
||||
}
|
||||
|
||||
private fun collectActualCallables(
|
||||
actualFunctions: MutableMap<CallableId, MutableList<IrFunction>>,
|
||||
actualProperties: MutableMap<CallableId, IrProperty>,
|
||||
allActualDeclarations: Set<IrDeclaration>
|
||||
expectActualMap: MutableMap<IrSymbol, IrSymbol>,
|
||||
actualMembers: MutableMap<String, IrDeclarationBase>,
|
||||
allActualDeclarations: Set<IrDeclaration>,
|
||||
) {
|
||||
fun collectActualsCallables(declaration: IrDeclaration) {
|
||||
when (declaration) {
|
||||
is IrFunction -> {
|
||||
actualFunctions.getOrPut(CallableId(declaration.parent.kotlinFqName, declaration.name)) {
|
||||
mutableListOf()
|
||||
}.add(declaration)
|
||||
}
|
||||
is IrFunction,
|
||||
is IrProperty -> {
|
||||
actualProperties.getOrPut(CallableId(declaration.parent.kotlinFqName, declaration.name)) {
|
||||
declaration
|
||||
}
|
||||
actualMembers[generateIrElementFullName(declaration, expectActualMap)] = declaration as IrDeclarationBase
|
||||
}
|
||||
is IrClass -> {
|
||||
for (member in declaration.declarations) {
|
||||
@@ -188,40 +161,23 @@ internal class ExpectActualCollector(private val mainFragment: IrModuleFragment,
|
||||
|
||||
class CallablesLinkCollector(
|
||||
private val expectActualMap: MutableMap<IrSymbol, IrSymbol>,
|
||||
private val actualFunctions: MutableMap<CallableId, MutableList<IrFunction>>,
|
||||
private val actualProperties: MutableMap<CallableId, IrProperty>,
|
||||
private val actualMembers: Map<String, IrDeclarationBase>,
|
||||
private val typeAliasMap: Map<FqName, FqName>
|
||||
) : IrElementVisitorVoid {
|
||||
private fun actualizeCallable(declaration: IrDeclarationWithName): CallableId {
|
||||
val fullName = declaration.parent.kotlinFqName
|
||||
return CallableId(typeAliasMap[fullName] ?: fullName, declaration.name)
|
||||
}
|
||||
override fun visitFunction(declaration: IrFunction) = addLink(declaration)
|
||||
|
||||
override fun visitFunction(declaration: IrFunction) {
|
||||
override fun visitProperty(declaration: IrProperty) = addLink(declaration)
|
||||
|
||||
private fun addLink(declaration: IrDeclarationBase) {
|
||||
if (!declaration.isExpect) return
|
||||
val functions = actualFunctions[actualizeCallable(declaration)]
|
||||
var isActualFunctionFound = false
|
||||
if (functions != null) {
|
||||
for (actualFunction in functions) {
|
||||
if (checkParameters(declaration, actualFunction, expectActualMap)) {
|
||||
expectActualMap[declaration.symbol] = actualFunction.symbol
|
||||
isActualFunctionFound = true
|
||||
break
|
||||
}
|
||||
val member = actualMembers[generateIrElementFullName(declaration, expectActualMap, typeAliasMap)]
|
||||
if (member != null) {
|
||||
expectActualMap[declaration.symbol] = member.symbol
|
||||
if (declaration is IrProperty) {
|
||||
member as IrProperty
|
||||
declaration.getter?.symbol?.let { expectActualMap[it] = member.getter!!.symbol }
|
||||
declaration.setter?.symbol?.let { expectActualMap[it] = member.setter!!.symbol }
|
||||
}
|
||||
}
|
||||
if (!isActualFunctionFound) {
|
||||
reportMissingActual(declaration)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitProperty(declaration: IrProperty) {
|
||||
if (!declaration.isExpect) return
|
||||
val properties = actualProperties[actualizeCallable(declaration)]
|
||||
if (properties != null) {
|
||||
expectActualMap[declaration.symbol] = properties.symbol
|
||||
declaration.getter?.symbol?.let { expectActualMap[it] = properties.getter!!.symbol }
|
||||
declaration.setter?.symbol?.let { expectActualMap[it] = properties.setter!!.symbol }
|
||||
} else {
|
||||
reportMissingActual(declaration)
|
||||
}
|
||||
|
||||
+9
-4
@@ -8,12 +8,13 @@ package org.jetbrains.kotlin.backend.common.actualizer
|
||||
import org.jetbrains.kotlin.backend.common.ir.isProperExpect
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
object IrActualizer {
|
||||
fun actualize(mainFragment: IrModuleFragment, dependentFragments: List<IrModuleFragment>) {
|
||||
val expectActualMap = ExpectActualCollector(mainFragment, dependentFragments).collect()
|
||||
val (expectActualMap, typeAliasMap) = ExpectActualCollector(mainFragment, dependentFragments).collect()
|
||||
removeExpectDeclaration(dependentFragments) // TODO: consider removing this call. See ExpectDeclarationRemover.kt
|
||||
addMissingFakeOverrides(expectActualMap, dependentFragments)
|
||||
addMissingFakeOverrides(expectActualMap, dependentFragments, typeAliasMap)
|
||||
linkExpectToActual(expectActualMap, dependentFragments)
|
||||
mergeIrFragments(mainFragment, dependentFragments)
|
||||
}
|
||||
@@ -26,8 +27,12 @@ object IrActualizer {
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMissingFakeOverrides(expectActualMap: Map<IrSymbol, IrSymbol>, dependentFragments: List<IrModuleFragment>) {
|
||||
MissingFakeOverridesAdder(expectActualMap).apply { dependentFragments.forEach { visitModuleFragment(it) } }
|
||||
private fun addMissingFakeOverrides(
|
||||
expectActualMap: Map<IrSymbol, IrSymbol>,
|
||||
dependentFragments: List<IrModuleFragment>,
|
||||
typeAliasMap: Map<FqName, FqName>
|
||||
) {
|
||||
MissingFakeOverridesAdder(expectActualMap, typeAliasMap).apply { dependentFragments.forEach { visitModuleFragment(it) } }
|
||||
}
|
||||
|
||||
private fun linkExpectToActual(expectActualMap: Map<IrSymbol, IrSymbol>, dependentFragments: List<IrModuleFragment>) {
|
||||
|
||||
+50
-34
@@ -6,55 +6,71 @@
|
||||
package org.jetbrains.kotlin.backend.common.actualizer
|
||||
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.classifierOrFail
|
||||
import org.jetbrains.kotlin.ir.util.kotlinFqName
|
||||
import org.jetbrains.kotlin.ir.util.render
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
fun checkParameters(
|
||||
expectFunction: IrFunction,
|
||||
actualFunction: IrFunction,
|
||||
expectActualTypesMap: Map<IrSymbol, IrSymbol>
|
||||
): Boolean {
|
||||
fun checkParameter(expectParameter: IrValueParameter?, actualParameter: IrValueParameter?): Boolean {
|
||||
if (expectParameter == null) {
|
||||
return actualParameter == null
|
||||
}
|
||||
if (actualParameter == null) {
|
||||
return false
|
||||
}
|
||||
fun generateIrElementFullName(
|
||||
declaration: IrElement,
|
||||
expectActualTypesMap: Map<IrSymbol, IrSymbol>,
|
||||
typeAliasMap: Map<FqName, FqName>? = null
|
||||
): String {
|
||||
return StringBuilder().apply { appendElementFullName(declaration, expectActualTypesMap, this, typeAliasMap) }.toString()
|
||||
}
|
||||
|
||||
val expectParameterTypeSymbol = expectParameter.type.classifierOrFail
|
||||
val actualizedParameterTypeSymbol = expectActualTypesMap[expectParameterTypeSymbol] ?: expectParameterTypeSymbol
|
||||
if (actualizedParameterTypeSymbol != actualParameter.type.classifierOrFail) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
private fun appendElementFullName(
|
||||
declaration: IrElement,
|
||||
expectActualTypesMap: Map<IrSymbol, IrSymbol>,
|
||||
result: StringBuilder,
|
||||
typeAliasMap: Map<FqName, FqName>? = null
|
||||
) {
|
||||
if (declaration !is IrDeclarationBase) return
|
||||
|
||||
val parentName = declaration.parent.kotlinFqName
|
||||
if (parentName.asString().isNotEmpty()) {
|
||||
result.append(typeAliasMap?.get(parentName) ?: parentName.asString())
|
||||
result.append('.')
|
||||
}
|
||||
|
||||
if (expectFunction.valueParameters.size != actualFunction.valueParameters.size ||
|
||||
!checkParameter(expectFunction.extensionReceiverParameter, actualFunction.extensionReceiverParameter)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
for ((expectParameter, actualParameter) in expectFunction.valueParameters.zip(actualFunction.valueParameters)) {
|
||||
if (!checkParameter(expectParameter, actualParameter)) {
|
||||
return false
|
||||
}
|
||||
if (declaration is IrDeclarationWithName) {
|
||||
result.append(declaration.name)
|
||||
}
|
||||
|
||||
return true
|
||||
if (declaration is IrFunction) {
|
||||
fun appendType(type: IrType) {
|
||||
val typeClassifier = type.classifierOrFail
|
||||
val actualizedTypeSymbol = expectActualTypesMap[typeClassifier] ?: typeClassifier
|
||||
appendElementFullName(actualizedTypeSymbol.owner, expectActualTypesMap, result)
|
||||
}
|
||||
|
||||
val extensionReceiverType = declaration.extensionReceiverParameter?.type
|
||||
if (extensionReceiverType != null) {
|
||||
result.append('[')
|
||||
appendType(extensionReceiverType)
|
||||
result.append(']')
|
||||
}
|
||||
|
||||
result.append('(')
|
||||
for ((index, parameter) in declaration.valueParameters.withIndex()) {
|
||||
appendType(parameter.type)
|
||||
if (index < declaration.valueParameters.size - 1) {
|
||||
result.append(',')
|
||||
}
|
||||
}
|
||||
result.append(')')
|
||||
}
|
||||
}
|
||||
|
||||
fun reportMissingActual(irElement: IrElement) {
|
||||
// TODO: set up diagnostics reporting
|
||||
// TODO: setup diagnostics reporting
|
||||
throw AssertionError("Missing actual for ${irElement.render()}")
|
||||
}
|
||||
|
||||
fun reportManyInterfacesMembersNotImplemented(declaration: IrClass, actualMember: IrDeclarationWithName) {
|
||||
// TODO: set up diagnostics reporting
|
||||
// TODO: setup diagnostics reporting
|
||||
throw AssertionError("${declaration.name} must override ${actualMember.name} because it inherits multiple interface methods of it")
|
||||
}
|
||||
+28
-39
@@ -18,11 +18,15 @@ import org.jetbrains.kotlin.ir.types.isAny
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
class MissingFakeOverridesAdder(private val expectActualMap: Map<IrSymbol, IrSymbol>) : IrElementVisitorVoid {
|
||||
class MissingFakeOverridesAdder(
|
||||
private val expectActualMap: Map<IrSymbol, IrSymbol>,
|
||||
private val typeAliasMap: Map<FqName, FqName>
|
||||
) : IrElementVisitorVoid {
|
||||
override fun visitClass(declaration: IrClass) {
|
||||
if (!declaration.isExpect) {
|
||||
processSupertypes(declaration, expectActualMap)
|
||||
processSupertypes(declaration)
|
||||
}
|
||||
visitElement(declaration)
|
||||
}
|
||||
@@ -30,48 +34,33 @@ class MissingFakeOverridesAdder(private val expectActualMap: Map<IrSymbol, IrSym
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processSupertypes(declaration: IrClass, expectActualMap: Map<IrSymbol, IrSymbol>) {
|
||||
val members by lazy(LazyThreadSafetyMode.NONE) {
|
||||
declaration.declarations.filter { !it.isBuiltinMember() }.filterIsInstance<IrDeclarationWithName>()
|
||||
.groupBy { it.name }
|
||||
}
|
||||
private fun processSupertypes(declaration: IrClass) {
|
||||
val members by lazy(LazyThreadSafetyMode.NONE) {
|
||||
declaration.declarations.filter { !it.isBuiltinMember() }.associateBy {
|
||||
generateIrElementFullName(it, expectActualMap, typeAliasMap)
|
||||
}
|
||||
}
|
||||
|
||||
for (superType in declaration.superTypes) {
|
||||
val actualClass = expectActualMap[superType.classifierOrFail]?.owner as? IrClass ?: continue
|
||||
for (actualMember in actualClass.declarations) {
|
||||
if (actualMember.isBuiltinMember()) continue
|
||||
when (actualMember) {
|
||||
is IrFunctionImpl -> {
|
||||
val existingMembers = members[actualMember.name]
|
||||
|
||||
var isActualFunctionFound = false
|
||||
if (existingMembers != null) {
|
||||
for (existingMember in existingMembers) {
|
||||
if (existingMember is IrFunction) {
|
||||
if (checkParameters(existingMember, actualMember, expectActualMap)) {
|
||||
isActualFunctionFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for (superType in declaration.superTypes) {
|
||||
val actualClass = expectActualMap[superType.classifierOrFail]?.owner as? IrClass ?: continue
|
||||
for (actualMember in actualClass.declarations) {
|
||||
if (actualMember.isBuiltinMember()) continue
|
||||
when (actualMember) {
|
||||
is IrFunctionImpl,
|
||||
is IrPropertyImpl -> {
|
||||
if (members[generateIrElementFullName(actualMember, expectActualMap, typeAliasMap)] != null) {
|
||||
reportManyInterfacesMembersNotImplemented(declaration, actualMember as IrDeclarationWithName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (isActualFunctionFound) {
|
||||
reportManyInterfacesMembersNotImplemented(declaration, actualMember)
|
||||
continue
|
||||
val newMember = if (actualMember is IrFunctionImpl) {
|
||||
createFakeOverrideFunction(actualMember, declaration)
|
||||
} else {
|
||||
createFakeOverrideProperty(actualMember as IrPropertyImpl, declaration)
|
||||
}
|
||||
declaration.declarations.add(newMember)
|
||||
}
|
||||
|
||||
declaration.declarations.add(createFakeOverrideFunction(actualMember, declaration))
|
||||
}
|
||||
is IrPropertyImpl -> {
|
||||
if (members[actualMember.name] != null) {
|
||||
reportManyInterfacesMembersNotImplemented(declaration, actualMember)
|
||||
continue
|
||||
}
|
||||
|
||||
declaration.declarations.add(createFakeOverrideProperty(actualMember, declaration))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// TARGET_BACKEND: JVM
|
||||
// !LANGUAGE: +MultiPlatformProjects
|
||||
// ISSUE: KT-56329
|
||||
|
||||
// MODULE: common
|
||||
// TARGET_PLATFORM: Common
|
||||
// FILE: common.kt
|
||||
|
||||
expect class S
|
||||
|
||||
expect fun <T> foo(y: T): String
|
||||
|
||||
expect fun <T> foo(y: T, x: S): String
|
||||
|
||||
fun ok() = foo(1) + foo(2, "K" as S)
|
||||
|
||||
// MODULE: jvm()()(common)
|
||||
// TARGET_PLATFORM: JVM
|
||||
// FILE: main.kt
|
||||
|
||||
actual typealias S = String
|
||||
|
||||
actual fun <T> foo(y: T) = "O"
|
||||
|
||||
actual fun <T> foo(y: T, x: S) = x
|
||||
|
||||
fun box() = ok()
|
||||
+6
@@ -31809,6 +31809,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
public void testGetRidOfDoubleBindingInFir2IrLazyProperty() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/getRidOfDoubleBindingInFir2IrLazyProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt-56329.kt")
|
||||
public void testKt_56329() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/kt-56329.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -33123,6 +33123,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
public void testKt_51753_2() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/kt-51753-2.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt-56329.kt")
|
||||
public void testKt_56329() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/kt-56329.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -27078,6 +27078,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
public void testGetRidOfDoubleBindingInFir2IrLazyProperty() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/getRidOfDoubleBindingInFir2IrLazyProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("kt-56329.kt")
|
||||
public void testKt_56329() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/multiplatform/multiModule/kt-56329.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user