[FIR] Add DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED diagnostics, fix tests
This commit is contained in:
committed by
TeamCityServer
parent
3191e0b925
commit
77c137df22
+1
@@ -54,6 +54,7 @@ fun main(args: Array<String>) {
|
||||
alias<FirConstructor>("ConstructorChecker")
|
||||
alias<FirFile>("FileChecker")
|
||||
alias<FirTypeParameter>("FirTypeParameterChecker")
|
||||
alias<FirAnnotatedDeclaration>("FirAnnotatedDeclarationChecker")
|
||||
|
||||
additional(
|
||||
fieldName = "controlFlowAnalyserCheckers",
|
||||
|
||||
+1
@@ -159,6 +159,7 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
}
|
||||
val DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS by error<PsiElement>()
|
||||
val DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS by error<PsiElement>()
|
||||
val DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED by error<PsiElement>()
|
||||
}
|
||||
|
||||
val EXPOSED_VISIBILITY by object : DiagnosticGroup("Exposed visibility") {
|
||||
|
||||
+4
@@ -35,6 +35,8 @@ class ComposedDeclarationCheckers : DeclarationCheckers() {
|
||||
get() = _fileCheckers
|
||||
override val typeParameterCheckers: Set<FirTypeParameterChecker>
|
||||
get() = _typeParameterCheckers
|
||||
override val annotatedDeclarationCheckers: Set<FirAnnotatedDeclarationChecker>
|
||||
get() = _annotatedDeclarationCheckers
|
||||
override val controlFlowAnalyserCheckers: Set<FirControlFlowChecker>
|
||||
get() = _controlFlowAnalyserCheckers
|
||||
override val variableAssignmentCfaBasedCheckers: Set<AbstractFirPropertyInitializationChecker>
|
||||
@@ -50,6 +52,7 @@ class ComposedDeclarationCheckers : DeclarationCheckers() {
|
||||
private val _constructorCheckers: MutableSet<FirConstructorChecker> = mutableSetOf()
|
||||
private val _fileCheckers: MutableSet<FirFileChecker> = mutableSetOf()
|
||||
private val _typeParameterCheckers: MutableSet<FirTypeParameterChecker> = mutableSetOf()
|
||||
private val _annotatedDeclarationCheckers: MutableSet<FirAnnotatedDeclarationChecker> = mutableSetOf()
|
||||
private val _controlFlowAnalyserCheckers: MutableSet<FirControlFlowChecker> = mutableSetOf()
|
||||
private val _variableAssignmentCfaBasedCheckers: MutableSet<AbstractFirPropertyInitializationChecker> = mutableSetOf()
|
||||
|
||||
@@ -65,6 +68,7 @@ class ComposedDeclarationCheckers : DeclarationCheckers() {
|
||||
_constructorCheckers += checkers.constructorCheckers
|
||||
_fileCheckers += checkers.fileCheckers
|
||||
_typeParameterCheckers += checkers.typeParameterCheckers
|
||||
_annotatedDeclarationCheckers += checkers.annotatedDeclarationCheckers
|
||||
_controlFlowAnalyserCheckers += checkers.controlFlowAnalyserCheckers
|
||||
_variableAssignmentCfaBasedCheckers += checkers.variableAssignmentCfaBasedCheckers
|
||||
}
|
||||
|
||||
+11
-9
@@ -29,18 +29,20 @@ abstract class DeclarationCheckers {
|
||||
open val constructorCheckers: Set<FirConstructorChecker> = emptySet()
|
||||
open val fileCheckers: Set<FirFileChecker> = emptySet()
|
||||
open val typeParameterCheckers: Set<FirTypeParameterChecker> = emptySet()
|
||||
open val annotatedDeclarationCheckers: Set<FirAnnotatedDeclarationChecker> = emptySet()
|
||||
|
||||
open val controlFlowAnalyserCheckers: Set<FirControlFlowChecker> = emptySet()
|
||||
open val variableAssignmentCfaBasedCheckers: Set<AbstractFirPropertyInitializationChecker> = emptySet()
|
||||
|
||||
@CheckersComponentInternal internal val allBasicDeclarationCheckers: Set<FirBasicDeclarationChecker> get() = basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allMemberDeclarationCheckers: Set<FirMemberDeclarationChecker> get() = memberDeclarationCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allFunctionCheckers: Set<FirFunctionChecker> get() = functionCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allSimpleFunctionCheckers: Set<FirSimpleFunctionChecker> get() = simpleFunctionCheckers + functionCheckers + basicDeclarationCheckers + memberDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allPropertyCheckers: Set<FirPropertyChecker> get() = propertyCheckers + basicDeclarationCheckers + memberDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allClassCheckers: Set<FirClassChecker> get() = classCheckers + basicDeclarationCheckers
|
||||
@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
|
||||
@CheckersComponentInternal internal val allMemberDeclarationCheckers: Set<FirMemberDeclarationChecker> get() = memberDeclarationCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allFunctionCheckers: Set<FirFunctionChecker> get() = functionCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allSimpleFunctionCheckers: Set<FirSimpleFunctionChecker> get() = simpleFunctionCheckers + functionCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers + memberDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allPropertyCheckers: Set<FirPropertyChecker> get() = propertyCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers + memberDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allClassCheckers: Set<FirClassChecker> get() = classCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allRegularClassCheckers: Set<FirRegularClassChecker> get() = regularClassCheckers + memberDeclarationCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers + classCheckers
|
||||
@CheckersComponentInternal internal val allConstructorCheckers: Set<FirConstructorChecker> get() = constructorCheckers + functionCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers + memberDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allFileCheckers: Set<FirFileChecker> get() = fileCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allTypeParameterCheckers: Set<FirTypeParameterChecker> get() = typeParameterCheckers + annotatedDeclarationCheckers + basicDeclarationCheckers
|
||||
@CheckersComponentInternal internal val allAnnotatedDeclarationCheckers: Set<FirAnnotatedDeclarationChecker> get() = annotatedDeclarationCheckers + basicDeclarationCheckers
|
||||
}
|
||||
|
||||
+2
@@ -10,6 +10,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
* DO NOT MODIFY IT MANUALLY
|
||||
*/
|
||||
|
||||
import org.jetbrains.kotlin.fir.declarations.FirAnnotatedDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirConstructor
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
@@ -31,3 +32,4 @@ typealias FirRegularClassChecker = FirDeclarationChecker<FirRegularClass>
|
||||
typealias FirConstructorChecker = FirDeclarationChecker<FirConstructor>
|
||||
typealias FirFileChecker = FirDeclarationChecker<FirFile>
|
||||
typealias FirTypeParameterChecker = FirDeclarationChecker<FirTypeParameter>
|
||||
typealias FirAnnotatedDeclarationChecker = FirDeclarationChecker<FirAnnotatedDeclaration>
|
||||
|
||||
@@ -165,6 +165,7 @@ object FirErrors {
|
||||
val NEWER_VERSION_IN_SINCE_KOTLIN by warning1<KtExpression, String>()
|
||||
val DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS by error0<PsiElement>()
|
||||
val DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS by error0<PsiElement>()
|
||||
val DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED by error0<PsiElement>()
|
||||
|
||||
// Exposed visibility
|
||||
val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3<KtNamedDeclaration, EffectiveVisibility, FirMemberDeclaration, EffectiveVisibility>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.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.FirAnnotatedDeclaration
|
||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
|
||||
import org.jetbrains.kotlin.fir.resolve.fqName
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
object FirAnnotationChecker : FirAnnotatedDeclarationChecker() {
|
||||
private val deprecatedClassId = FqName("kotlin.Deprecated")
|
||||
private val deprecatedSinceKotlinClassId = FqName("kotlin.DeprecatedSinceKotlin")
|
||||
|
||||
override fun check(
|
||||
declaration: FirAnnotatedDeclaration,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
var deprecatedCall: FirAnnotationCall? = null
|
||||
var deprecatedSinceKotlinCall: FirAnnotationCall? = null
|
||||
for (annotation in declaration.annotations) {
|
||||
val fqName = annotation.fqName(context.session)
|
||||
if (fqName == deprecatedClassId) {
|
||||
deprecatedCall = annotation
|
||||
} else if (fqName == deprecatedSinceKotlinClassId) {
|
||||
deprecatedSinceKotlinCall = annotation
|
||||
}
|
||||
}
|
||||
|
||||
if (deprecatedSinceKotlinCall != null && deprecatedCall == null) {
|
||||
reporter.reportOn(deprecatedSinceKotlinCall.source, FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-19
@@ -18,32 +18,31 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.languageVersionSettings
|
||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.fir.resolve.fqName
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.RequireKotlinConstants
|
||||
|
||||
object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
|
||||
private val deprecatedSinceKotlinClassId = ClassId.fromString("kotlin/DeprecatedSinceKotlin")
|
||||
private val sinceKotlinClassId = ClassId.fromString("kotlin/SinceKotlin")
|
||||
private val deprecatedSinceKotlinFqName = FqName("kotlin.DeprecatedSinceKotlin")
|
||||
private val sinceKotlinFqName = FqName("kotlin.SinceKotlin")
|
||||
|
||||
private val annotationClassIdsWithVersion = setOf(
|
||||
ClassId.fromString("kotlin/internal/RequireKotlin"),
|
||||
sinceKotlinClassId,
|
||||
deprecatedSinceKotlinClassId
|
||||
private val annotationFqNamesWithVersion = setOf(
|
||||
FqName("kotlin.internal.RequireKotlin"),
|
||||
sinceKotlinFqName,
|
||||
deprecatedSinceKotlinFqName
|
||||
)
|
||||
|
||||
override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val argumentMapping = expression.argumentMapping ?: return
|
||||
val classId = ((expression.annotationTypeRef as? FirResolvedTypeRef)?.type as? ConeClassLikeType)?.lookupTag?.classId
|
||||
val fqName = expression.fqName(context.session)
|
||||
for ((arg, _) in argumentMapping) {
|
||||
val argExpression = (arg as? FirNamedArgumentExpression)?.expression ?: arg
|
||||
checkAnnotationArgumentWithSubElements(argExpression, classId, context.session, reporter, context)
|
||||
checkAnnotationArgumentWithSubElements(argExpression, fqName, context.session, reporter, context)
|
||||
?.let { reporter.reportOn(argExpression.source, it, context) }
|
||||
}
|
||||
|
||||
checkDeprecatedSinceKotlin(expression.source, classId, argumentMapping, context, reporter)
|
||||
checkDeprecatedSinceKotlin(expression.source, fqName, argumentMapping, context, reporter)
|
||||
|
||||
val args = expression.argumentList.arguments
|
||||
for (arg in args) {
|
||||
@@ -55,7 +54,7 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
|
||||
|
||||
private fun checkAnnotationArgumentWithSubElements(
|
||||
expression: FirExpression,
|
||||
classId: ClassId?,
|
||||
fqName: FqName?,
|
||||
session: FirSession,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext
|
||||
@@ -67,7 +66,7 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
|
||||
for (arg in expression.argumentList.arguments) {
|
||||
val sourceForReport = arg.source
|
||||
|
||||
when (val err = checkAnnotationArgumentWithSubElements(arg, classId, session, reporter, context)) {
|
||||
when (val err = checkAnnotationArgumentWithSubElements(arg, fqName, session, reporter, context)) {
|
||||
null -> {
|
||||
//DO NOTHING
|
||||
}
|
||||
@@ -82,7 +81,7 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
|
||||
}
|
||||
is FirVarargArgumentsExpression -> {
|
||||
for (arg in expression.arguments)
|
||||
checkAnnotationArgumentWithSubElements(arg, classId, session, reporter, context)
|
||||
checkAnnotationArgumentWithSubElements(arg, fqName, session, reporter, context)
|
||||
?.let { reporter.reportOn(arg.source, it, context) }
|
||||
}
|
||||
else -> {
|
||||
@@ -96,14 +95,14 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
|
||||
}
|
||||
if (error != null) {
|
||||
return error
|
||||
} else if (classId != null && annotationClassIdsWithVersion.contains(classId)) {
|
||||
} else if (annotationFqNamesWithVersion.contains(fqName)) {
|
||||
val argSource = expression.source
|
||||
if (argSource != null) {
|
||||
val stringValue = (expression as? FirConstExpression<*>)?.value as? String
|
||||
if (stringValue != null) {
|
||||
if (!stringValue.matches(RequireKotlinConstants.VERSION_REGEX)) {
|
||||
reporter.reportOn(argSource, FirErrors.ILLEGAL_KOTLIN_VERSION_STRING_VALUE, context)
|
||||
} else if (classId == sinceKotlinClassId) {
|
||||
} else if (fqName == sinceKotlinFqName) {
|
||||
val version = ApiVersion.parse(stringValue)
|
||||
val specified = context.session.languageVersionSettings.apiVersion
|
||||
if (version != null && version > specified) {
|
||||
@@ -123,12 +122,12 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
|
||||
|
||||
private fun checkDeprecatedSinceKotlin(
|
||||
source: FirSourceElement?,
|
||||
classId: ClassId?,
|
||||
fqName: FqName?,
|
||||
argumentMapping: LinkedHashMap<FirExpression, FirValueParameter>,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
if (classId != deprecatedSinceKotlinClassId)
|
||||
if (fqName != deprecatedSinceKotlinFqName)
|
||||
return
|
||||
|
||||
if (argumentMapping.size == 0) {
|
||||
|
||||
+5
@@ -87,6 +87,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DELEGATION_IN_INT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DELEGATION_SUPER_CALL_IN_ENUM_CONSTRUCTOR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_MODIFIER_PAIR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DESERIALIZATION_ERROR
|
||||
@@ -438,6 +439,10 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS,
|
||||
"DeprecatedSinceKotlin annotation should have at least one argument"
|
||||
)
|
||||
map.put(
|
||||
DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED,
|
||||
" DeprecatedSinceKotlin annotation can be used only together with Deprecated annotation"
|
||||
)
|
||||
|
||||
// Exposed visibility group // #
|
||||
map.put(
|
||||
|
||||
+5
@@ -120,4 +120,9 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||
FirReifiedTypeParameterChecker,
|
||||
FirTypeParameterSyntaxChecker,
|
||||
)
|
||||
|
||||
override val annotatedDeclarationCheckers: Set<FirAnnotatedDeclarationChecker>
|
||||
get() = setOf(
|
||||
FirAnnotationChecker,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package kotlin.sub
|
||||
@DeprecatedSinceKotlin(warningSince = "1.0", errorSince = "1.1", hiddenSince = "1.2")
|
||||
fun good() {}
|
||||
|
||||
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS!>@DeprecatedSinceKotlin()<!>
|
||||
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS, DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED!>@DeprecatedSinceKotlin()<!>
|
||||
class Clazz
|
||||
|
||||
@Deprecated("", level = DeprecationLevel.WARNING)
|
||||
|
||||
+6
@@ -539,6 +539,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED) { firDiagnostic ->
|
||||
DeprecatedSinceKotlinWithoutDeprecatedImpl(
|
||||
firDiagnostic as FirPsiDiagnostic<*>,
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.EXPOSED_TYPEALIAS_EXPANDED_TYPE) { firDiagnostic ->
|
||||
ExposedTypealiasExpandedTypeImpl(
|
||||
firDiagnostic.a,
|
||||
|
||||
+4
@@ -388,6 +388,10 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
override val diagnosticClass get() = DeprecatedSinceKotlinWithoutArguments::class
|
||||
}
|
||||
|
||||
abstract class DeprecatedSinceKotlinWithoutDeprecated : KtFirDiagnostic<PsiElement>() {
|
||||
override val diagnosticClass get() = DeprecatedSinceKotlinWithoutDeprecated::class
|
||||
}
|
||||
|
||||
abstract class ExposedTypealiasExpandedType : KtFirDiagnostic<KtNamedDeclaration>() {
|
||||
override val diagnosticClass get() = ExposedTypealiasExpandedType::class
|
||||
abstract val elementVisibility: EffectiveVisibility
|
||||
|
||||
+7
@@ -626,6 +626,13 @@ internal class DeprecatedSinceKotlinWithoutArgumentsImpl(
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class DeprecatedSinceKotlinWithoutDeprecatedImpl(
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.DeprecatedSinceKotlinWithoutDeprecated(), KtAbstractFirDiagnostic<PsiElement> {
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class ExposedTypealiasExpandedTypeImpl(
|
||||
override val elementVisibility: EffectiveVisibility,
|
||||
override val restrictingDeclaration: KtSymbol,
|
||||
|
||||
Reference in New Issue
Block a user