Do not implicitly propagate deprecations originated in Java
^KT-29604 Fixed
This commit is contained in:
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||
import org.jetbrains.kotlin.idea.MainFunctionDetector
|
||||
import org.jetbrains.kotlin.load.java.components.JavaDeprecationSettings
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
||||
import org.jetbrains.kotlin.modules.TargetId
|
||||
@@ -34,6 +35,7 @@ import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
import org.jetbrains.kotlin.resolve.deprecation.CoroutineCompatibilitySupport
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationSettings
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
@@ -143,7 +145,7 @@ class GenerationState private constructor(
|
||||
CompilerDeserializationConfiguration(configuration.languageVersionSettings)
|
||||
|
||||
val deprecationProvider =
|
||||
DeprecationResolver(LockBasedStorageManager.NO_LOCKS, configuration.languageVersionSettings, CoroutineCompatibilitySupport.ENABLED)
|
||||
DeprecationResolver(LockBasedStorageManager.NO_LOCKS, configuration.languageVersionSettings, CoroutineCompatibilitySupport.ENABLED, JavaDeprecationSettings)
|
||||
|
||||
init {
|
||||
val icComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS)
|
||||
|
||||
@@ -72,6 +72,8 @@ private fun StorageComponentContainer.configureJavaTopDownAnalysis(
|
||||
useInstance(InternalFlexibleTypeTransformer)
|
||||
|
||||
useImpl<CompilerDeserializationConfiguration>()
|
||||
|
||||
useInstance(JavaDeprecationSettings)
|
||||
}
|
||||
|
||||
fun createContainerForLazyResolveWithJava(
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.load.java.components
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationSettings
|
||||
|
||||
object JavaDeprecationSettings : DeprecationSettings {
|
||||
override fun propagatedToOverrides(deprecationAnnotation: AnnotationDescriptor): Boolean {
|
||||
if (deprecationAnnotation is JavaDeprecatedAnnotationDescriptor) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.constants.KClassValue
|
||||
import org.jetbrains.kotlin.resolve.deprecation.CoroutineCompatibilitySupport
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationSettings
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
@@ -215,7 +216,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
// "-Xuse-experimental" arguments. However, it's not easy to do this. This should be solved in the future with the support of
|
||||
// module annotations. For now, we only check deprecations because this is needed to correctly retire unneeded compiler arguments.
|
||||
val deprecationResolver =
|
||||
DeprecationResolver(LockBasedStorageManager("ExperimentalUsageChecker"), languageVersionSettings, CoroutineCompatibilitySupport.ENABLED)
|
||||
DeprecationResolver(LockBasedStorageManager("ExperimentalUsageChecker"), languageVersionSettings, CoroutineCompatibilitySupport.ENABLED, DeprecationSettings.Default)
|
||||
|
||||
// Returns true if fqName refers to a valid experimental API marker.
|
||||
fun checkAnnotation(fqName: String): Boolean {
|
||||
|
||||
@@ -20,7 +20,11 @@ import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue.*
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
internal data class DeprecatedByAnnotation(val annotation: AnnotationDescriptor, override val target: DeclarationDescriptor) : Deprecation {
|
||||
internal data class DeprecatedByAnnotation(
|
||||
val annotation: AnnotationDescriptor,
|
||||
override val target: DeclarationDescriptor,
|
||||
override val propagatesToOverrides: Boolean
|
||||
) : Deprecation {
|
||||
override val deprecationLevel: DeprecationLevelValue
|
||||
get() = when (annotation.argumentValue("level")?.safeAs<EnumValue>()?.enumEntryName?.asString()) {
|
||||
"WARNING" -> WARNING
|
||||
|
||||
+29
-2
@@ -36,7 +36,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
class DeprecationResolver(
|
||||
storageManager: StorageManager,
|
||||
private val languageVersionSettings: LanguageVersionSettings,
|
||||
private val coroutineCompatibilitySupport: CoroutineCompatibilitySupport
|
||||
private val coroutineCompatibilitySupport: CoroutineCompatibilitySupport,
|
||||
private val deprecationSettings: DeprecationSettings
|
||||
) {
|
||||
private val deprecations = storageManager.createMemoizedFunction { descriptor: DeclarationDescriptor ->
|
||||
val deprecations = descriptor.getOwnDeprecations()
|
||||
@@ -126,6 +127,28 @@ class DeprecationResolver(
|
||||
|
||||
if (hasUndeprecatedOverridden || deprecations.isEmpty()) return null
|
||||
|
||||
// We might've filtered out not-propagating deprecations already in the initializer of `deprecationsByAnnotation` in the code above.
|
||||
// But it would lead to treating Java overridden as not-deprecated at all that works controversially in case of mixed J/K override:
|
||||
// interface J {
|
||||
// @Deprecated
|
||||
// void foo();
|
||||
// }
|
||||
//
|
||||
// interface K {
|
||||
// @Deprecated("")
|
||||
// fun foo();
|
||||
// }
|
||||
//
|
||||
// class K1 : K, J {
|
||||
// // We'd probably better treating it as deprecated
|
||||
// // Basically, it's just a corner case and we may change the behavior if it's too annoying
|
||||
// override fun foo() {}
|
||||
// }
|
||||
//
|
||||
// Also, we don't ignore non-propagating deprecations in case of fake overrides
|
||||
// Because we don't want to depend on the choice of the base descriptor
|
||||
if (root.kind.isReal && deprecations.none(Deprecation::propagatesToOverrides)) return null
|
||||
|
||||
return DeprecatedByOverridden(deprecations)
|
||||
}
|
||||
|
||||
@@ -166,7 +189,11 @@ class DeprecationResolver(
|
||||
val annotation = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated)
|
||||
?: annotations.findAnnotation(JAVA_DEPRECATED)
|
||||
if (annotation != null) {
|
||||
val deprecatedByAnnotation = DeprecatedByAnnotation(annotation, this)
|
||||
val deprecatedByAnnotation =
|
||||
DeprecatedByAnnotation(
|
||||
annotation, this,
|
||||
deprecationSettings.propagatedToOverrides(annotation)
|
||||
)
|
||||
val deprecation = when {
|
||||
this is TypeAliasConstructorDescriptor ->
|
||||
DeprecatedTypealiasByAnnotation(typeAliasDescriptor, deprecatedByAnnotation)
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.resolve.deprecation
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.container.DefaultImplementation
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||
@@ -70,3 +71,12 @@ class CoroutineCompatibilitySupport private constructor(val enabled: Boolean) {
|
||||
val DISABLED = CoroutineCompatibilitySupport(false)
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultImplementation(DeprecationSettings.Default::class)
|
||||
interface DeprecationSettings {
|
||||
fun propagatedToOverrides(deprecationAnnotation: AnnotationDescriptor): Boolean
|
||||
|
||||
object Default : DeprecationSettings {
|
||||
override fun propagatedToOverrides(deprecationAnnotation: AnnotationDescriptor) = true
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ public class C extends A {
|
||||
|
||||
fun use(a: A, b: B, c: C) {
|
||||
a.<!DEPRECATION!>f<!>()
|
||||
b.<!DEPRECATION!>f<!>()
|
||||
b.f()
|
||||
c.<!DEPRECATION!>f<!>()
|
||||
|
||||
A.<!DEPRECATION!>D<!>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// FILE: J.java
|
||||
public interface J {
|
||||
@Deprecated
|
||||
public void foo();
|
||||
|
||||
@Deprecated
|
||||
public String bar();
|
||||
|
||||
@Deprecated
|
||||
public CharSequence baz();
|
||||
}
|
||||
// FILE: J2.java
|
||||
public interface J2 extends J, K {
|
||||
}
|
||||
|
||||
// FILE: main.kt
|
||||
interface K {
|
||||
fun bar(): CharSequence
|
||||
fun baz(): String
|
||||
}
|
||||
|
||||
interface A : J, K
|
||||
|
||||
fun main(j: J, j2: J2, a: A) {
|
||||
j.<!DEPRECATION!>foo<!>()
|
||||
j2.<!DEPRECATION!>foo<!>()
|
||||
a.<!DEPRECATION!>foo<!>()
|
||||
|
||||
j.<!DEPRECATION!>bar<!>()
|
||||
j2.<!DEPRECATION!>bar<!>()
|
||||
a.<!DEPRECATION!>bar<!>()
|
||||
|
||||
j.<!DEPRECATION!>baz<!>()
|
||||
j2.baz()
|
||||
a.baz()
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package
|
||||
|
||||
public fun main(/*0*/ j: J, /*1*/ j2: J2, /*2*/ a: A): kotlin.Unit
|
||||
|
||||
public interface A : J, K {
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*2*/ /*fake_override*/ fun bar(): kotlin.String!
|
||||
public abstract override /*2*/ /*fake_override*/ fun baz(): kotlin.String
|
||||
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit
|
||||
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public interface J {
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun bar(): kotlin.String!
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun baz(): kotlin.CharSequence!
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun foo(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public interface J2 : J, K {
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*2*/ /*fake_override*/ fun bar(): kotlin.String
|
||||
public abstract override /*2*/ /*fake_override*/ fun baz(): kotlin.String
|
||||
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit
|
||||
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public interface K {
|
||||
public abstract fun bar(): kotlin.CharSequence
|
||||
public abstract fun baz(): kotlin.String
|
||||
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// FILE: J.java
|
||||
public class J {
|
||||
@Deprecated
|
||||
public void foo() {}
|
||||
}
|
||||
// FILE: J2.java
|
||||
public class J2 extends J implements WithDeprecation {
|
||||
@Override
|
||||
public void foo() {}
|
||||
}
|
||||
|
||||
// FILE: main.kt
|
||||
interface WithDeprecation {
|
||||
@Deprecated("")
|
||||
fun foo()
|
||||
}
|
||||
|
||||
class A : J(), WithDeprecation {
|
||||
override fun foo() {}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
J().<!DEPRECATION!>foo<!>()
|
||||
|
||||
J2().<!DEPRECATION!>foo<!>()
|
||||
A().<!DEPRECATION!>foo<!>()
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package
|
||||
|
||||
public fun main(): kotlin.Unit
|
||||
|
||||
public final class A : J, WithDeprecation {
|
||||
public constructor A()
|
||||
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*2*/ fun foo(): kotlin.Unit
|
||||
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public open class J {
|
||||
public constructor J()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public open fun foo(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public open class J2 : J, WithDeprecation {
|
||||
public constructor J2()
|
||||
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@java.lang.Override public open override /*2*/ fun foo(): kotlin.Unit
|
||||
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public interface WithDeprecation {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@kotlin.Deprecated(message = "") public abstract fun foo(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// FILE: J.java
|
||||
public interface J {
|
||||
@Deprecated
|
||||
public void foo();
|
||||
|
||||
}
|
||||
// FILE: J2.java
|
||||
public interface J2 extends J {
|
||||
@Override
|
||||
public void foo();
|
||||
}
|
||||
|
||||
// FILE: main.kt
|
||||
|
||||
interface A : J {
|
||||
override fun foo()
|
||||
}
|
||||
|
||||
fun main(j: J, j2: J2, a: A) {
|
||||
j.<!DEPRECATION!>foo<!>()
|
||||
j2.foo()
|
||||
a.foo()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package
|
||||
|
||||
public fun main(/*0*/ j: J, /*1*/ j2: J2, /*2*/ a: A): kotlin.Unit
|
||||
|
||||
public interface A : J {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public abstract override /*1*/ fun foo(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public interface J {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@kotlin.Deprecated(message = "Deprecated in Java") public abstract fun foo(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public interface J2 : J {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@java.lang.Override public abstract override /*1*/ fun foo(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
@@ -11978,6 +11978,34 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/j+k/deprecations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Deprecations extends AbstractDiagnosticsTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInDeprecations() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/j+k/deprecations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("forFakeOverrides.kt")
|
||||
public void testForFakeOverrides() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forFakeOverrides.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("forMixedOverride.kt")
|
||||
public void testForMixedOverride() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forMixedOverride.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("forOverrides.kt")
|
||||
public void testForOverrides() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forOverrides.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/j+k/genericConstructor")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Generated
+28
@@ -11973,6 +11973,34 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/j+k/deprecations")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Deprecations extends AbstractDiagnosticsUsingJavacTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInDeprecations() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/j+k/deprecations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("forFakeOverrides.kt")
|
||||
public void testForFakeOverrides() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forFakeOverrides.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("forMixedOverride.kt")
|
||||
public void testForMixedOverride() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forMixedOverride.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("forOverrides.kt")
|
||||
public void testForOverrides() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/j+k/deprecations/forOverrides.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/j+k/genericConstructor")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -12,6 +12,7 @@ interface Deprecation {
|
||||
val deprecationLevel: DeprecationLevelValue
|
||||
val message: String?
|
||||
val target: DeclarationDescriptor
|
||||
val propagatesToOverrides: Boolean get() = true
|
||||
}
|
||||
|
||||
// values from kotlin.DeprecationLevel
|
||||
|
||||
Reference in New Issue
Block a user