[FIR] Add FINAL_UPPER_BOUND checker
This commit is contained in:
+5
-5
@@ -38,15 +38,15 @@ fun test(z: Int, c: Char) {}
|
||||
|
||||
typealias BA = A
|
||||
|
||||
fun <T> kek(t: T) where T : (String) -> Any?, T : Char {}
|
||||
fun <T> kek(t: T) where T : () -> Boolean, T : String {}
|
||||
fun <T : Int> kek(t: T) {}
|
||||
fun <T> kek(t: T) where T : (String) -> Any?, T : <!FINAL_UPPER_BOUND!>Char<!> {}
|
||||
fun <T> kek(t: T) where T : () -> Boolean, T : <!FINAL_UPPER_BOUND!>String<!> {}
|
||||
fun <T : <!FINAL_UPPER_BOUND!>Int<!>> kek(t: T) {}
|
||||
|
||||
fun lol(a: Array<Int>) {}
|
||||
fun lol(a: Array<Boolean>) {}
|
||||
|
||||
<!CONFLICTING_OVERLOADS!>fun <T> mem(t: T)<!> where T : () -> Boolean, T : String {}
|
||||
<!CONFLICTING_OVERLOADS!>fun <T> mem(t: T)<!> where T : String, T : () -> Boolean {}
|
||||
<!CONFLICTING_OVERLOADS!>fun <T> mem(t: T)<!> where T : () -> Boolean, T : <!FINAL_UPPER_BOUND!>String<!> {}
|
||||
<!CONFLICTING_OVERLOADS!>fun <T> mem(t: T)<!> where T : <!FINAL_UPPER_BOUND!>String<!>, T : () -> Boolean {}
|
||||
|
||||
class M {
|
||||
companion <!REDECLARATION!>object<!> {}
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ open class D() : B() {
|
||||
override var test: <!VAR_TYPE_MISMATCH_ON_OVERRIDE!>Char<!> = '\n'
|
||||
}
|
||||
|
||||
class E<T : Double>(val value: T) : B() {
|
||||
class E<T : <!FINAL_UPPER_BOUND!>Double<!>>(val value: T) : B() {
|
||||
override var test: <!VAR_TYPE_MISMATCH_ON_OVERRIDE!>T<!> = value
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ open class F<T : Number>(val value: T) {
|
||||
open var rest: T = value
|
||||
}
|
||||
|
||||
class G<E : Double>(val balue: E) : F<E>(balue) {
|
||||
class G<E : <!FINAL_UPPER_BOUND!>Double<!>>(val balue: E) : F<E>(balue) {
|
||||
override var rest: E = balue
|
||||
}
|
||||
|
||||
class H<E : String>(val balue: E) : <!INAPPLICABLE_CANDIDATE!>F<E><!>(balue) {
|
||||
class H<E : <!FINAL_UPPER_BOUND!>String<!>>(val balue: E) : <!INAPPLICABLE_CANDIDATE!>F<E><!>(balue) {
|
||||
override var rest: E = balue // no report because of INAPPLICABLE_CANDIDATE
|
||||
}
|
||||
|
||||
class M<E : String>(val balue: E) : F<Double>(3.14) {
|
||||
class M<E : <!FINAL_UPPER_BOUND!>String<!>>(val balue: E) : F<Double>(3.14) {
|
||||
override var rest: <!VAR_TYPE_MISMATCH_ON_OVERRIDE!>E<!> = balue
|
||||
}
|
||||
|
||||
+4
-4
@@ -15,7 +15,7 @@ open class D() : B() {
|
||||
override fun test(): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE!>Char<!> = '\n'
|
||||
}
|
||||
|
||||
class E<T : Double>(val value: T) : B() {
|
||||
class E<T : <!FINAL_UPPER_BOUND!>Double<!>>(val value: T) : B() {
|
||||
override fun test(): T = value
|
||||
}
|
||||
|
||||
@@ -23,15 +23,15 @@ open class F<T : Number>(val value: T) {
|
||||
open fun rest(): T = value
|
||||
}
|
||||
|
||||
class G<E : Double>(val balue: E) : F<E>(balue) {
|
||||
class G<E : <!FINAL_UPPER_BOUND!>Double<!>>(val balue: E) : F<E>(balue) {
|
||||
override fun rest(): E = balue
|
||||
}
|
||||
|
||||
class H<E : String>(val balue: E) : <!INAPPLICABLE_CANDIDATE!>F<E><!>(balue) {
|
||||
class H<E : <!FINAL_UPPER_BOUND!>String<!>>(val balue: E) : <!INAPPLICABLE_CANDIDATE!>F<E><!>(balue) {
|
||||
override fun rest(): E = balue // no report because of INAPPLICABLE_CANDIDATE
|
||||
}
|
||||
|
||||
class M<E : String>(val balue: E) : F<Double>(3.14) {
|
||||
class M<E : <!FINAL_UPPER_BOUND!>String<!>>(val balue: E) : F<Double>(3.14) {
|
||||
override fun rest(): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE!>E<!> = balue
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ fun main(args: Array<String>) {
|
||||
alias<FirRegularClass>("RegularClassChecker")
|
||||
alias<FirConstructor>("ConstructorChecker")
|
||||
alias<FirFile>("FileChecker")
|
||||
alias<FirTypeParameter>("FirTypeParameterChecker")
|
||||
|
||||
additional(
|
||||
fieldName = "controlFlowAnalyserCheckers",
|
||||
|
||||
+4
@@ -289,6 +289,10 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
val TYPE_PARAMETER_AS_REIFIED by error<FirSourceElement, PsiElement> {
|
||||
parameter<FirTypeParameterSymbol>("typeParameter")
|
||||
}
|
||||
|
||||
val FINAL_UPPER_BOUND by warning<FirSourceElement, PsiElement> {
|
||||
parameter<ConeKotlinType>("type")
|
||||
}
|
||||
}
|
||||
|
||||
val REFLECTION by object : DiagnosticGroup("Reflection") {
|
||||
|
||||
+4
@@ -33,6 +33,8 @@ internal class ComposedDeclarationCheckers : DeclarationCheckers() {
|
||||
get() = _constructorCheckers
|
||||
override val fileCheckers: Set<FirFileChecker>
|
||||
get() = _fileCheckers
|
||||
override val typeParameterCheckers: Set<FirTypeParameterChecker>
|
||||
get() = _typeParameterCheckers
|
||||
override val controlFlowAnalyserCheckers: Set<FirControlFlowChecker>
|
||||
get() = _controlFlowAnalyserCheckers
|
||||
override val variableAssignmentCfaBasedCheckers: Set<AbstractFirPropertyInitializationChecker>
|
||||
@@ -47,6 +49,7 @@ internal class ComposedDeclarationCheckers : DeclarationCheckers() {
|
||||
private val _regularClassCheckers: MutableSet<FirRegularClassChecker> = mutableSetOf()
|
||||
private val _constructorCheckers: MutableSet<FirConstructorChecker> = mutableSetOf()
|
||||
private val _fileCheckers: MutableSet<FirFileChecker> = mutableSetOf()
|
||||
private val _typeParameterCheckers: MutableSet<FirTypeParameterChecker> = mutableSetOf()
|
||||
private val _controlFlowAnalyserCheckers: MutableSet<FirControlFlowChecker> = mutableSetOf()
|
||||
private val _variableAssignmentCfaBasedCheckers: MutableSet<AbstractFirPropertyInitializationChecker> = mutableSetOf()
|
||||
|
||||
@@ -61,6 +64,7 @@ internal class ComposedDeclarationCheckers : DeclarationCheckers() {
|
||||
_regularClassCheckers += checkers.regularClassCheckers
|
||||
_constructorCheckers += checkers.constructorCheckers
|
||||
_fileCheckers += checkers.fileCheckers
|
||||
_typeParameterCheckers += checkers.typeParameterCheckers
|
||||
_controlFlowAnalyserCheckers += checkers.controlFlowAnalyserCheckers
|
||||
_variableAssignmentCfaBasedCheckers += checkers.variableAssignmentCfaBasedCheckers
|
||||
}
|
||||
|
||||
+2
@@ -28,6 +28,7 @@ abstract class DeclarationCheckers {
|
||||
open val regularClassCheckers: Set<FirRegularClassChecker> = emptySet()
|
||||
open val constructorCheckers: Set<FirConstructorChecker> = emptySet()
|
||||
open val fileCheckers: Set<FirFileChecker> = emptySet()
|
||||
open val typeParameterCheckers: Set<FirTypeParameterChecker> = emptySet()
|
||||
|
||||
open val controlFlowAnalyserCheckers: Set<FirControlFlowChecker> = emptySet()
|
||||
open val variableAssignmentCfaBasedCheckers: Set<AbstractFirPropertyInitializationChecker> = emptySet()
|
||||
@@ -41,4 +42,5 @@ abstract class DeclarationCheckers {
|
||||
@CheckersComponentInternal internal val allRegularClassCheckers: Set<FirRegularClassChecker> get() = regularClassCheckers + memberDeclarationCheckers + basicDeclarationCheckers + classCheckers
|
||||
@CheckersComponentInternal internal val allConstructorCheckers: Set<FirConstructorChecker> get() = constructorCheckers + functionCheckers + basicDeclarationCheckers + memberDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allFileCheckers: Set<FirFileChecker> get() = fileCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allTypeParameterCheckers: Set<FirTypeParameterChecker> get() = typeParameterCheckers + basicDeclarationCheckers
|
||||
}
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
|
||||
|
||||
typealias FirBasicDeclarationChecker = FirDeclarationChecker<FirDeclaration>
|
||||
typealias FirMemberDeclarationChecker = FirDeclarationChecker<FirMemberDeclaration>
|
||||
@@ -29,3 +30,4 @@ typealias FirClassChecker = FirDeclarationChecker<FirClass<*>>
|
||||
typealias FirRegularClassChecker = FirDeclarationChecker<FirRegularClass>
|
||||
typealias FirConstructorChecker = FirDeclarationChecker<FirConstructor>
|
||||
typealias FirFileChecker = FirDeclarationChecker<FirFile>
|
||||
typealias FirTypeParameterChecker = FirDeclarationChecker<FirTypeParameter>
|
||||
|
||||
@@ -214,6 +214,7 @@ object FirErrors {
|
||||
val INNER_CLASS_OF_GENERIC_THROWABLE_SUBCLASS by error0<FirSourceElement, KtClassOrObject>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE by error1<FirSourceElement, KtNamedDeclaration, FirTypeParameterSymbol>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val TYPE_PARAMETER_AS_REIFIED by error1<FirSourceElement, PsiElement, FirTypeParameterSymbol>()
|
||||
val FINAL_UPPER_BOUND by warning1<FirSourceElement, PsiElement, ConeKotlinType>()
|
||||
|
||||
// Reflection
|
||||
val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1<FirSourceElement, KtExpression, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
|
||||
|
||||
+1
-3
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.fir.BuiltinTypes
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSupertypeOf
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
|
||||
@@ -199,9 +200,6 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun KotlinTypeMarker.isSupertypeOf(context: TypeCheckerProviderContext, type: KotlinTypeMarker?) =
|
||||
type != null && AbstractTypeChecker.isSubtypeOf(context, type, this)
|
||||
|
||||
private fun simpleTypeStatement(realVar: RealVariable, exactType: Boolean, type: ConeKotlinType): MutableTypeStatement {
|
||||
return MutableTypeStatement(
|
||||
realVar,
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers
|
||||
|
||||
import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.fir.*
|
||||
import org.jetbrains.kotlin.fir.analysis.cfa.FirReturnsImpliesAnalyzer.isSupertypeOf
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.modalityModifier
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.overrideModifier
|
||||
@@ -30,16 +28,16 @@ import org.jetbrains.kotlin.fir.scopes.processOverriddenFunctions
|
||||
import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtModifierList
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtParameter.VAL_VAR_TOKEN_SET
|
||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
|
||||
import org.jetbrains.kotlin.types.model.TypeCheckerProviderContext
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
internal fun FirClass<*>.unsubstitutedScope(context: CheckerContext) =
|
||||
@@ -321,3 +319,81 @@ val FirValueParameter.hasValOrVar: Boolean
|
||||
val source = this.source ?: return false
|
||||
return source.getChild(VAL_VAR_TOKEN_SET) != null
|
||||
}
|
||||
|
||||
fun KotlinTypeMarker.isSupertypeOf(context: TypeCheckerProviderContext, type: KotlinTypeMarker?) =
|
||||
type != null && AbstractTypeChecker.isSubtypeOf(context, type, this)
|
||||
|
||||
fun KotlinTypeMarker.isSubtypeOf(context: TypeCheckerProviderContext, type: KotlinTypeMarker?) =
|
||||
type != null && AbstractTypeChecker.isSubtypeOf(context, this, type)
|
||||
|
||||
fun ConeKotlinType.canHaveSubtypes(session: FirSession): Boolean {
|
||||
if (this.isMarkedNullable) {
|
||||
return true
|
||||
}
|
||||
val clazz = toRegularClass(session) ?: return true
|
||||
if (clazz.isEnumClass || clazz.isExpect || clazz.modality != Modality.FINAL) {
|
||||
return true
|
||||
}
|
||||
|
||||
clazz.typeParameters.forEachIndexed { idx, typeParameterRef ->
|
||||
val typeParameter = typeParameterRef.symbol.fir
|
||||
val typeProjection = typeArguments[idx]
|
||||
|
||||
if (typeProjection.isStarProjection) {
|
||||
return true
|
||||
}
|
||||
|
||||
val argument = typeProjection.type!! //safe because it is not a star
|
||||
|
||||
when (typeParameter.variance) {
|
||||
Variance.INVARIANT ->
|
||||
when (typeProjection.kind) {
|
||||
ProjectionKind.INVARIANT ->
|
||||
if (lowerThanBound(session.typeContext, argument, typeParameter) || argument.canHaveSubtypes(session)) {
|
||||
return true
|
||||
}
|
||||
ProjectionKind.IN ->
|
||||
if (lowerThanBound(session.typeContext, argument, typeParameter)) {
|
||||
return true
|
||||
}
|
||||
ProjectionKind.OUT ->
|
||||
if (argument.canHaveSubtypes(session)) {
|
||||
return true
|
||||
}
|
||||
ProjectionKind.STAR ->
|
||||
return true
|
||||
}
|
||||
Variance.IN_VARIANCE ->
|
||||
if (typeProjection.kind != ProjectionKind.OUT) {
|
||||
if (lowerThanBound(session.typeContext, argument, typeParameter)) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (argument.canHaveSubtypes(session)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
Variance.OUT_VARIANCE ->
|
||||
if (typeProjection.kind != ProjectionKind.IN) {
|
||||
if (argument.canHaveSubtypes(session)) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (lowerThanBound(session.typeContext, argument, typeParameter)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun lowerThanBound(context: ConeInferenceContext, argument: ConeKotlinType, typeParameter: FirTypeParameter): Boolean {
|
||||
typeParameter.bounds.forEach { boundTypeRef ->
|
||||
if (argument != boundTypeRef.coneType && argument.isSubtypeOf(context, boundTypeRef.coneType)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.fir.analysis.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.canHaveSubtypes
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
|
||||
object FirTypeParameterBoundsChecker : FirTypeParameterChecker() {
|
||||
|
||||
override fun check(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val containingDeclaration = context.containingDeclarations.lastOrNull()
|
||||
if (containingDeclaration is FirConstructor) return
|
||||
if (containingDeclaration is FirSimpleFunction && containingDeclaration.isOverride) return
|
||||
if (containingDeclaration is FirProperty && containingDeclaration.isOverride) return
|
||||
|
||||
declaration.symbol.fir.bounds.forEach { bound ->
|
||||
if (!bound.coneType.canHaveSubtypes(context.session)) {
|
||||
reporter.reportOn(bound.source, FirErrors.FINAL_UPPER_BOUND, bound.coneType, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -61,7 +61,7 @@ class DeclarationCheckersDiagnosticComponent(
|
||||
}
|
||||
|
||||
override fun visitTypeParameter(typeParameter: FirTypeParameter, data: CheckerContext) {
|
||||
checkers.allBasicDeclarationCheckers.check(typeParameter, data, reporter)
|
||||
checkers.allTypeParameterCheckers.check(typeParameter, data, reporter)
|
||||
}
|
||||
|
||||
override fun visitEnumEntry(enumEntry: FirEnumEntry, data: CheckerContext) {
|
||||
|
||||
+6
@@ -91,6 +91,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPRESSION_OF_NUL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERTY_WITH_BACKING_FIELD
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINAL_UPPER_BOUND
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_VARARG_PARAMETER_TYPE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUNCTION_DECLARATION_WITH_NO_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.GENERIC_THROWABLE_SUBCLASS
|
||||
@@ -438,6 +439,11 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
)
|
||||
|
||||
map.put(TYPE_PARAMETER_AS_REIFIED, "Cannot use ''{0}'' as reified type parameter. Use a class instead", SYMBOL)
|
||||
map.put(
|
||||
FINAL_UPPER_BOUND,
|
||||
"''{0}'' is a final type, and thus a value of the type parameter is predetermined",
|
||||
RENDER_TYPE
|
||||
)
|
||||
|
||||
// Reflection
|
||||
map.put(
|
||||
|
||||
+4
@@ -87,4 +87,8 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||
override val variableAssignmentCfaBasedCheckers: Set<AbstractFirPropertyInitializationChecker> = setOf(
|
||||
FirPropertyInitializationAnalyzer,
|
||||
)
|
||||
|
||||
override val typeParameterCheckers: Set<FirTypeParameterChecker> = setOf(
|
||||
FirTypeParameterBoundsChecker,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user