[K2] Revert commit Pass FirFile to metadata serialization
We will ignore this for now. It is quite painful to bring `FirFile` in all necessary places. #KT-57812
This commit is contained in:
+20
-36
@@ -30,10 +30,8 @@ import org.jetbrains.kotlin.fir.expressions.builder.buildConstExpression
|
|||||||
import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyAnnotationArgumentMapping
|
import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyAnnotationArgumentMapping
|
||||||
import org.jetbrains.kotlin.fir.extensions.extensionService
|
import org.jetbrains.kotlin.fir.extensions.extensionService
|
||||||
import org.jetbrains.kotlin.fir.extensions.typeAttributeExtensions
|
import org.jetbrains.kotlin.fir.extensions.typeAttributeExtensions
|
||||||
import org.jetbrains.kotlin.fir.resolve.ScopeSession
|
import org.jetbrains.kotlin.fir.resolve.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
|
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
|
||||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.scopes.*
|
import org.jetbrains.kotlin.fir.scopes.*
|
||||||
import org.jetbrains.kotlin.fir.scopes.impl.nestedClassifierScope
|
import org.jetbrains.kotlin.fir.scopes.impl.nestedClassifierScope
|
||||||
import org.jetbrains.kotlin.fir.serialization.constant.*
|
import org.jetbrains.kotlin.fir.serialization.constant.*
|
||||||
@@ -78,7 +76,7 @@ class FirElementSerializer private constructor(
|
|||||||
|
|
||||||
fun packagePartProto(
|
fun packagePartProto(
|
||||||
packageFqName: FqName,
|
packageFqName: FqName,
|
||||||
file: FirFile,
|
files: List<FirFile>,
|
||||||
actualizedExpectDeclarations: Set<FirDeclaration>?
|
actualizedExpectDeclarations: Set<FirDeclaration>?
|
||||||
): ProtoBuf.Package.Builder {
|
): ProtoBuf.Package.Builder {
|
||||||
val builder = ProtoBuf.Package.newBuilder()
|
val builder = ProtoBuf.Package.newBuilder()
|
||||||
@@ -88,7 +86,7 @@ class FirElementSerializer private constructor(
|
|||||||
if (!declaration.shouldBeSerialized(actualizedExpectDeclarations)) return
|
if (!declaration.shouldBeSerialized(actualizedExpectDeclarations)) return
|
||||||
when (declaration) {
|
when (declaration) {
|
||||||
is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) }
|
is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) }
|
||||||
is FirSimpleFunction -> privateFunctionProto(declaration)?.let { builder.addFunction(it) }
|
is FirSimpleFunction -> functionProto(declaration)?.let { builder.addFunction(it) }
|
||||||
is FirTypeAlias -> typeAliasProto(declaration)?.let { builder.addTypeAlias(it) }
|
is FirTypeAlias -> typeAliasProto(declaration)?.let { builder.addTypeAlias(it) }
|
||||||
else -> onUnsupportedDeclaration(declaration)
|
else -> onUnsupportedDeclaration(declaration)
|
||||||
}
|
}
|
||||||
@@ -97,39 +95,31 @@ class FirElementSerializer private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processFile(file) {
|
for (file in files) {
|
||||||
for (declaration in file.declarations) {
|
extension.processFile(file) {
|
||||||
addDeclaration(declaration) {}
|
for (declaration in file.declarations) {
|
||||||
}
|
addDeclaration(declaration) {}
|
||||||
extension.serializePackage(packageFqName, builder)
|
|
||||||
|
|
||||||
for (declaration in providedDeclarationsService.getProvidedTopLevelDeclarations(packageFqName, scopeSession)) {
|
|
||||||
addDeclaration(declaration) {
|
|
||||||
error("Unsupported top-level declaration type: ${it.render()}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: figure out how to extract all file dependent processing from `serializePackage`
|
||||||
|
extension.serializePackage(packageFqName, builder)
|
||||||
|
// Next block will process declarations from plugins.
|
||||||
|
// Such declarations don't belong to any file, so there is no need to call `extension.processFile`.
|
||||||
|
for (declaration in providedDeclarationsService.getProvidedTopLevelDeclarations(packageFqName, scopeSession)) {
|
||||||
|
addDeclaration(declaration) {
|
||||||
|
error("Unsupported top-level declaration type: ${it.render()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typeTable.serialize()?.let { builder.typeTable = it }
|
typeTable.serialize()?.let { builder.typeTable = it }
|
||||||
versionRequirementTable?.serialize()?.let { builder.versionRequirementTable = it }
|
versionRequirementTable?.serialize()?.let { builder.versionRequirementTable = it }
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <T> processFile(firFile: FirFile, crossinline action: () -> T): T {
|
fun classProto(klass: FirClass): ProtoBuf.Class.Builder = whileAnalysing(session, klass) {
|
||||||
return extension.processFile(firFile) {
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: we could try to extract FirFile from `session.firProvider.getFirClassifierContainerFile` but it doesn't work for anonymous objects
|
|
||||||
fun classProto(klass: FirClass, firFile: FirFile): ProtoBuf.Class.Builder {
|
|
||||||
return processFile(firFile) {
|
|
||||||
privateClassProto(klass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun privateClassProto(klass: FirClass): ProtoBuf.Class.Builder = whileAnalysing(session, klass) {
|
|
||||||
val builder = ProtoBuf.Class.newBuilder()
|
val builder = ProtoBuf.Class.newBuilder()
|
||||||
|
|
||||||
val regularClass = klass as? FirRegularClass
|
val regularClass = klass as? FirRegularClass
|
||||||
@@ -209,7 +199,7 @@ class FirElementSerializer private constructor(
|
|||||||
if (declaration !is FirEnumEntry && declaration.isStatic) continue // ??? Miss values() & valueOf()
|
if (declaration !is FirEnumEntry && declaration.isStatic) continue // ??? Miss values() & valueOf()
|
||||||
when (declaration) {
|
when (declaration) {
|
||||||
is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) }
|
is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) }
|
||||||
is FirSimpleFunction -> privateFunctionProto(declaration)?.let { builder.addFunction(it) }
|
is FirSimpleFunction -> functionProto(declaration)?.let { builder.addFunction(it) }
|
||||||
is FirEnumEntry -> enumEntryProto(declaration).let { builder.addEnumEntry(it) }
|
is FirEnumEntry -> enumEntryProto(declaration).let { builder.addEnumEntry(it) }
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@@ -500,13 +490,7 @@ class FirElementSerializer private constructor(
|
|||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun functionProto(function: FirFunction, firFile: FirFile): ProtoBuf.Function.Builder? {
|
fun functionProto(function: FirFunction): ProtoBuf.Function.Builder? = whileAnalysing(session, function) {
|
||||||
return processFile(firFile) {
|
|
||||||
privateFunctionProto(function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun privateFunctionProto(function: FirFunction): ProtoBuf.Function.Builder? = whileAnalysing(session, function) {
|
|
||||||
val builder = ProtoBuf.Function.newBuilder()
|
val builder = ProtoBuf.Function.newBuilder()
|
||||||
val simpleFunction = function as? FirSimpleFunction
|
val simpleFunction = function as? FirSimpleFunction
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -35,7 +35,7 @@ fun serializeSingleFirFile(
|
|||||||
// TODO: split package fragment (see klib serializer)
|
// TODO: split package fragment (see klib serializer)
|
||||||
// TODO: handle incremental/monolothic (see klib serializer) - maybe externally
|
// TODO: handle incremental/monolothic (see klib serializer) - maybe externally
|
||||||
|
|
||||||
val packageProto = packageSerializer.packagePartProto(file.packageFqName, file, actualizedExpectDeclarations).build()
|
val packageProto = packageSerializer.packagePartProto(file.packageFqName, listOf(file), actualizedExpectDeclarations).build()
|
||||||
|
|
||||||
val classesProto = mutableListOf<Pair<ProtoBuf.Class, Int>>()
|
val classesProto = mutableListOf<Pair<ProtoBuf.Class, Int>>()
|
||||||
|
|
||||||
@@ -51,12 +51,14 @@ fun serializeSingleFirFile(
|
|||||||
)
|
)
|
||||||
val index = classSerializer.stringTable.getFqNameIndex(klass)
|
val index = classSerializer.stringTable.getFqNameIndex(klass)
|
||||||
|
|
||||||
classesProto += classSerializer.classProto(klass, file).build() to index
|
classesProto += classSerializer.classProto(klass).build() to index
|
||||||
classSerializer.computeNestedClassifiersForClass(symbol).filterIsInstance<FirClassSymbol<*>>().makeClassesProtoWithNested()
|
classSerializer.computeNestedClassifiersForClass(symbol).filterIsInstance<FirClassSymbol<*>>().makeClassesProtoWithNested()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file.declarations.mapNotNull { it.symbol as? FirClassSymbol<*> }.makeClassesProtoWithNested()
|
serializerExtension.processFile(file) {
|
||||||
|
file.declarations.mapNotNull { it.symbol as? FirClassSymbol<*> }.makeClassesProtoWithNested()
|
||||||
|
}
|
||||||
|
|
||||||
val hasTopLevelDeclarations = file.declarations.any {
|
val hasTopLevelDeclarations = file.declarations.any {
|
||||||
it is FirMemberDeclaration && it.shouldBeSerialized(actualizedExpectDeclarations) &&
|
it is FirMemberDeclaration && it.shouldBeSerialized(actualizedExpectDeclarations) &&
|
||||||
|
|||||||
+3
-8
@@ -34,7 +34,6 @@ import org.jetbrains.kotlin.fir.types.*
|
|||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||||
import org.jetbrains.kotlin.ir.declarations.MetadataSource
|
import org.jetbrains.kotlin.ir.declarations.MetadataSource
|
||||||
import org.jetbrains.kotlin.ir.util.file
|
|
||||||
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
|
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
|
||||||
import org.jetbrains.kotlin.modules.TargetId
|
import org.jetbrains.kotlin.modules.TargetId
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
@@ -63,7 +62,6 @@ fun makeFirMetadataSerializerForIrClass(
|
|||||||
approximator, context.defaultTypeMapper, components
|
approximator, context.defaultTypeMapper, components
|
||||||
)
|
)
|
||||||
return FirMetadataSerializer(
|
return FirMetadataSerializer(
|
||||||
(irClass.file.metadata as? FirMetadataSource.File)?.file,
|
|
||||||
context.state.globalSerializationBindings,
|
context.state.globalSerializationBindings,
|
||||||
serializationBindings,
|
serializationBindings,
|
||||||
approximator,
|
approximator,
|
||||||
@@ -76,7 +74,6 @@ fun makeFirMetadataSerializerForIrClass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun makeLocalFirMetadataSerializerForMetadataSource(
|
fun makeLocalFirMetadataSerializerForMetadataSource(
|
||||||
firFile: FirFile,
|
|
||||||
metadata: MetadataSource?,
|
metadata: MetadataSource?,
|
||||||
session: FirSession,
|
session: FirSession,
|
||||||
scopeSession: ScopeSession,
|
scopeSession: ScopeSession,
|
||||||
@@ -110,7 +107,6 @@ fun makeLocalFirMetadataSerializerForMetadataSource(
|
|||||||
constValueProvider = null
|
constValueProvider = null
|
||||||
)
|
)
|
||||||
return FirMetadataSerializer(
|
return FirMetadataSerializer(
|
||||||
firFile,
|
|
||||||
globalSerializationBindings,
|
globalSerializationBindings,
|
||||||
serializationBindings,
|
serializationBindings,
|
||||||
approximator,
|
approximator,
|
||||||
@@ -123,7 +119,6 @@ fun makeLocalFirMetadataSerializerForMetadataSource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FirMetadataSerializer(
|
class FirMetadataSerializer(
|
||||||
private val firFile: FirFile?,
|
|
||||||
private val globalSerializationBindings: JvmSerializationBindings,
|
private val globalSerializationBindings: JvmSerializationBindings,
|
||||||
private val serializationBindings: JvmSerializationBindings,
|
private val serializationBindings: JvmSerializationBindings,
|
||||||
private val approximator: AbstractTypeApproximator,
|
private val approximator: AbstractTypeApproximator,
|
||||||
@@ -134,15 +129,15 @@ class FirMetadataSerializer(
|
|||||||
|
|
||||||
override fun serialize(metadata: MetadataSource): Pair<MessageLite, JvmStringTable>? {
|
override fun serialize(metadata: MetadataSource): Pair<MessageLite, JvmStringTable>? {
|
||||||
val message = when (metadata) {
|
val message = when (metadata) {
|
||||||
is FirMetadataSource.Class -> serializer!!.classProto(metadata.fir, firFile!!).build()
|
is FirMetadataSource.Class -> serializer!!.classProto(metadata.fir).build()
|
||||||
is FirMetadataSource.File ->
|
is FirMetadataSource.File ->
|
||||||
serializer!!.packagePartProto(metadata.file.packageFqName, metadata.file, actualizedExpectDeclarations).build()
|
serializer!!.packagePartProto(metadata.files.first().packageFqName, metadata.files, actualizedExpectDeclarations).build()
|
||||||
is FirMetadataSource.Function -> {
|
is FirMetadataSource.Function -> {
|
||||||
val withTypeParameters = metadata.fir.copyToFreeAnonymousFunction(approximator)
|
val withTypeParameters = metadata.fir.copyToFreeAnonymousFunction(approximator)
|
||||||
serializationBindings.get(FirJvmSerializerExtension.METHOD_FOR_FIR_FUNCTION, metadata.fir)?.let {
|
serializationBindings.get(FirJvmSerializerExtension.METHOD_FOR_FIR_FUNCTION, metadata.fir)?.let {
|
||||||
serializationBindings.put(FirJvmSerializerExtension.METHOD_FOR_FIR_FUNCTION, withTypeParameters, it)
|
serializationBindings.put(FirJvmSerializerExtension.METHOD_FOR_FIR_FUNCTION, withTypeParameters, it)
|
||||||
}
|
}
|
||||||
serializer!!.functionProto(withTypeParameters, firFile!!)?.build()
|
serializer!!.functionProto(withTypeParameters)?.build()
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class Fir2IrVisitor(
|
|||||||
it.toIrDeclaration()
|
it.toIrDeclaration()
|
||||||
}
|
}
|
||||||
annotationGenerator.generate(this, file)
|
annotationGenerator.generate(this, file)
|
||||||
metadata = FirMetadataSource.File(file)
|
metadata = FirMetadataSource.File(listOf(file))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ sealed class FirMetadataSource : MetadataSource {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
class File(val file: FirFile) : FirMetadataSource(), MetadataSource.File {
|
class File(val files: List<FirFile>) : FirMetadataSource(), MetadataSource.File {
|
||||||
override var serializedIr: ByteArray? = null
|
override var serializedIr: ByteArray? = null
|
||||||
|
|
||||||
override val fir: FirDeclaration?
|
override val fir: FirDeclaration?
|
||||||
|
|||||||
+1
-2
@@ -46,7 +46,6 @@ internal fun collectNewDirtySources(
|
|||||||
body: (MetadataSerializer) -> Unit
|
body: (MetadataSerializer) -> Unit
|
||||||
) {
|
) {
|
||||||
val serializer = makeLocalFirMetadataSerializerForMetadataSource(
|
val serializer = makeLocalFirMetadataSerializerForMetadataSource(
|
||||||
it,
|
|
||||||
metadata,
|
metadata,
|
||||||
analyzedOutput.session,
|
analyzedOutput.session,
|
||||||
analyzedOutput.scopeSession,
|
analyzedOutput.scopeSession,
|
||||||
@@ -74,7 +73,7 @@ internal fun collectNewDirtySources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitFile(file: FirFile, data: MutableList<MetadataSerializer>) {
|
override fun visitFile(file: FirFile, data: MutableList<MetadataSerializer>) {
|
||||||
val metadata = FirMetadataSource.File(file)
|
val metadata = FirMetadataSource.File(listOf(file))
|
||||||
withMetadataSerializer(metadata, data) {
|
withMetadataSerializer(metadata, data) {
|
||||||
file.acceptChildren(this, data)
|
file.acceptChildren(this, data)
|
||||||
// TODO: compare package fragments?
|
// TODO: compare package fragments?
|
||||||
|
|||||||
+11
-9
@@ -6,13 +6,15 @@
|
|||||||
// MODULE: lib
|
// MODULE: lib
|
||||||
// FILE: lib.kt
|
// FILE: lib.kt
|
||||||
|
|
||||||
|
// TODO: must uncomment some tests when figure out how to pass `FirFile` to const provider
|
||||||
|
|
||||||
@Target(AnnotationTarget.TYPE)
|
@Target(AnnotationTarget.TYPE)
|
||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
annotation class TypeAnnotation(val str: String)
|
annotation class TypeAnnotation(val str: String)
|
||||||
|
|
||||||
open class A
|
open class A
|
||||||
interface B
|
interface B
|
||||||
class C : @TypeAnnotation("AClass" <!EVALUATED("AClassAnno")!>+ "Anno"<!>) A(), @TypeAnnotation("BInterface" <!EVALUATED("BInterfaceAnno")!>+ "Anno"<!>) B
|
//class C : @TypeAnnotation("AClass" + "Anno") A(), @TypeAnnotation("BInterface" + "Anno") B
|
||||||
|
|
||||||
val a: @TypeAnnotation("Int" <!EVALUATED("IntAnno")!>+ "Anno"<!>) Int = 1
|
val a: @TypeAnnotation("Int" <!EVALUATED("IntAnno")!>+ "Anno"<!>) Int = 1
|
||||||
var b: @TypeAnnotation("List" <!EVALUATED("ListAnno")!>+ "Anno"<!>) List<
|
var b: @TypeAnnotation("List" <!EVALUATED("ListAnno")!>+ "Anno"<!>) List<
|
||||||
@@ -30,7 +32,7 @@ fun foo(a: @TypeAnnotation("String" <!EVALUATED("StringAnno")!>+ "Anno"<!>) Stri
|
|||||||
fun <T: @TypeAnnotation("SuperT" <!EVALUATED("SuperTAnno")!>+ "Anno"<!>) Any> bar(a: @TypeAnnotation("T" <!EVALUATED("TAnno")!>+ "Anno"<!>) T) {}
|
fun <T: @TypeAnnotation("SuperT" <!EVALUATED("SuperTAnno")!>+ "Anno"<!>) Any> bar(a: @TypeAnnotation("T" <!EVALUATED("TAnno")!>+ "Anno"<!>) T) {}
|
||||||
|
|
||||||
fun example(computeAny: @TypeAnnotation("Fun" <!EVALUATED("FunAnno")!>+ "Anno"<!>) () -> Any) {
|
fun example(computeAny: @TypeAnnotation("Fun" <!EVALUATED("FunAnno")!>+ "Anno"<!>) () -> Any) {
|
||||||
val memoizedFoo: @TypeAnnotation("LocalDelegate" <!EVALUATED("LocalDelegateAnno")!>+ "Anno"<!>) Any by lazy(computeAny)
|
// val memoizedFoo: @TypeAnnotation("LocalDelegate" + "Anno") Any by lazy(computeAny)
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias Fun = @TypeAnnotation("TypeAlias" <!EVALUATED("TypeAliasAnno")!>+ "Anno"<!>) (Int, Int) -> Int
|
typealias Fun = @TypeAnnotation("TypeAlias" <!EVALUATED("TypeAliasAnno")!>+ "Anno"<!>) (Int, Int) -> Int
|
||||||
@@ -53,7 +55,7 @@ fun withAnonymousObject() {
|
|||||||
|
|
||||||
class Outer {
|
class Outer {
|
||||||
inner class Inner {
|
inner class Inner {
|
||||||
fun foo(): @TypeAnnotation("InsideInner" <!EVALUATED("InsideInnerAnno")!>+ "Anno"<!>) Int = 0
|
// fun foo(): @TypeAnnotation("InsideInner" + "Anno") Int = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +63,12 @@ fun functionWithLambda(action: (Int, String) -> Any) {
|
|||||||
action(0, "")
|
action(0, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lambda() {
|
//fun lambda() {
|
||||||
functionWithLambda { integer: @TypeAnnotation("InsideLambdaInt" <!EVALUATED("InsideLambdaIntAnno")!>+ "Anno"<!>) Int, string ->
|
// functionWithLambda { integer: @TypeAnnotation("InsideLambdaInt" + "Anno") Int, string ->
|
||||||
val a: @TypeAnnotation("InsideLambda" <!EVALUATED("InsideLambdaAnno")!>+ "Anno"<!>) Int = 0
|
// val a: @TypeAnnotation("InsideLambda" + "Anno") Int = 0
|
||||||
a
|
// a
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
val inProjection: MutableList<in @TypeAnnotation("InProjection" <!EVALUATED("InProjectionAnno")!>+ "Anno"<!>) String> = mutableListOf()
|
val inProjection: MutableList<in @TypeAnnotation("InProjection" <!EVALUATED("InProjectionAnno")!>+ "Anno"<!>) String> = mutableListOf()
|
||||||
val outProjection: MutableList<out @TypeAnnotation("OutProjection" <!EVALUATED("OutProjectionAnno")!>+ "Anno"<!>) String> = mutableListOf()
|
val outProjection: MutableList<out @TypeAnnotation("OutProjection" <!EVALUATED("OutProjectionAnno")!>+ "Anno"<!>) String> = mutableListOf()
|
||||||
|
|||||||
Reference in New Issue
Block a user