From ff1adf596df8091c450176e85b9b5883f33d5828 Mon Sep 17 00:00:00 2001 From: Rick Clephas Date: Fri, 19 Aug 2022 20:34:17 +0200 Subject: [PATCH] [FIR] Implement ObjCName checkers --- .../diagnostics/FirNativeDiagnosticsList.kt | 15 ++ .../diagnostics/native/FirNativeErrors.kt | 8 ++ .../native/FirNativeErrorsDefaultMessages.kt | 16 +++ .../checkers/FirNativeObjCNameChecker.kt | 129 ++++++++++++++++++ .../FirNativeObjCNameOverridesChecker.kt | 90 ++++++++++++ .../checkers/NativeDeclarationCheckers.kt | 6 +- .../diagnostics/nativeTests/objCName.fir.kt | 69 ++++++---- .../diagnostics/nativeTests/objCName.kt | 11 +- .../diagnostics/nativeTests/objCName.txt | 6 +- 9 files changed, 316 insertions(+), 34 deletions(-) create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt index 14e26bd1807..1a2ddb96068 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt @@ -47,5 +47,20 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") { parameter>("containingClasses") } val INVALID_OBJC_REFINEMENT_TARGETS by error() + val INAPPLICABLE_OBJC_NAME by error() + val INVALID_OBJC_NAME by error() + val INVALID_OBJC_NAME_CHARS by error { + parameter("characters") + } + val INVALID_OBJC_NAME_FIRST_CHAR by error { + parameter("characters") + } + val INCOMPATIBLE_OBJC_NAME_OVERRIDE by error { + parameter>("declaration") + parameter>("containingClasses") + } + val INAPPLICABLE_EXACT_OBJC_NAME by error() + val MISSING_EXACT_OBJC_NAME by error() + val NON_LITERAL_OBJC_NAME_ARG by error() } } diff --git a/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt b/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt index df882cf9086..0250f82993c 100644 --- a/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt +++ b/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt @@ -36,6 +36,14 @@ object FirNativeErrors { val REDUNDANT_SWIFT_REFINEMENT by error0() val INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE by error2, Collection>() val INVALID_OBJC_REFINEMENT_TARGETS by error0() + val INAPPLICABLE_OBJC_NAME by error0() + val INVALID_OBJC_NAME by error0() + val INVALID_OBJC_NAME_CHARS by error1() + val INVALID_OBJC_NAME_FIRST_CHAR by error1() + val INCOMPATIBLE_OBJC_NAME_OVERRIDE by error2, Collection>() + val INAPPLICABLE_EXACT_OBJC_NAME by error0() + val MISSING_EXACT_OBJC_NAME by error0() + val NON_LITERAL_OBJC_NAME_ARG by error0() init { RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages) diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt index f87d5185ff3..d3684cbf58e 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt @@ -11,17 +11,25 @@ import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS import org.jetbrains.kotlin.fir.analysis.diagnostics.checkMissingMessages +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_EXACT_OBJC_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_OBJC_NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_THREAD_LOCAL import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_OBJC_NAME_OVERRIDE import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_INHERITED import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_OVERRIDE import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_CHARACTERS_NATIVE +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_CHARS +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_FIRST_CHAR import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_REFINEMENT_TARGETS +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXACT_OBJC_NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.REDUNDANT_SWIFT_REFINEMENT +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.NON_LITERAL_OBJC_NAME_ARG import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.THROWS_LIST_EMPTY object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() { @@ -55,6 +63,14 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() { INVALID_OBJC_REFINEMENT_TARGETS, "Refines annotations are only applicable to annotations with targets FUNCTION and/or PROPERTY" ) + map.put(INAPPLICABLE_OBJC_NAME, "@ObjCName is not applicable on overrides") + map.put(INVALID_OBJC_NAME, "@ObjCName should have a name and/or swiftName") + map.put(INVALID_OBJC_NAME_CHARS, "@ObjCName contains illegal characters: {0}", TO_STRING) + map.put(INVALID_OBJC_NAME_FIRST_CHAR, "@ObjCName contains illegal first characters: {0}", TO_STRING) + map.put(INCOMPATIBLE_OBJC_NAME_OVERRIDE, "Member \"{0}\" inherits inconsistent @ObjCName from {1}", SYMBOL, SYMBOLS) + map.put(INAPPLICABLE_EXACT_OBJC_NAME, "Exact @ObjCName is only applicable to classes, objects and interfaces") + map.put(MISSING_EXACT_OBJC_NAME, "Exact @ObjCName is required to have an ObjC name") + map.put(NON_LITERAL_OBJC_NAME_ARG, "@ObjCName accepts only literal string and boolean values") map.checkMissingMessages(FirNativeErrors) } diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt new file mode 100644 index 00000000000..df280d1173f --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2010-2022 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.native.checkers + +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.FirAnnotationContainer +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker +import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_EXACT_OBJC_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_OBJC_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_CHARS +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_FIRST_CHAR +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXACT_OBJC_NAME +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.NON_LITERAL_OBJC_NAME_ARG +import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameOverridesChecker.check +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.isOverride +import org.jetbrains.kotlin.fir.expressions.FirAnnotation +import org.jetbrains.kotlin.fir.expressions.FirConstExpression +import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +object FirNativeObjCNameChecker : FirBasicDeclarationChecker() { + + private val objCNameClassId = ClassId.topLevel(FqName("kotlin.native.ObjCName")) + private val nameName = Name.identifier("name") + private val swiftNameName = Name.identifier("swiftName") + private val exactName = Name.identifier("exact") + + override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { + checkDeclaration(declaration, context, reporter) + if (declaration is FirCallableDeclaration && (declaration is FirSimpleFunction || declaration is FirProperty)) { + val containingClass = context.containingDeclarations.lastOrNull() as? FirClass + if (containingClass != null) { + val firTypeScope = containingClass.unsubstitutedScope(context) + check(firTypeScope, declaration.symbol, declaration, context, reporter) + } + } + } + + private fun checkDeclaration(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { + if (declaration is FirValueParameter) return // those are checked with the FirFunction + val objCNames = declaration.symbol.getObjCNames().filterNotNull() + if (objCNames.isEmpty()) return + if (declaration is FirCallableDeclaration && declaration.isOverride) { + for (objCName in objCNames) { + reporter.reportOn(objCName.annotation.source, INAPPLICABLE_OBJC_NAME, context) + } + } + objCNames.forEach { checkObjCName(it, declaration, context, reporter) } + } + + // We only allow valid ObjC identifiers (even for Swift names) + private val validFirstChars = ('A'..'Z').toSet() + ('a'..'z').toSet() + '_' + private val validChars = validFirstChars + ('0'..'9').toSet() + + private fun checkObjCName(objCName: ObjCName, declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { + val annotationSource = objCName.annotation.source + for ((_, argument) in objCName.annotation.argumentMapping.mapping) { + if (argument is FirConstExpression<*>) continue + reporter.reportOn(argument.source, NON_LITERAL_OBJC_NAME_ARG, context) + } + if (objCName.name == null && objCName.swiftName == null) { + reporter.reportOn(annotationSource, INVALID_OBJC_NAME, context) + } + val invalidNameFirstChar = objCName.name?.firstOrNull()?.takeUnless(validFirstChars::contains) + val invalidSwiftNameFirstChar = objCName.swiftName?.firstOrNull()?.takeUnless(validFirstChars::contains) + val invalidFirstChars = setOfNotNull(invalidNameFirstChar, invalidSwiftNameFirstChar) + if (invalidFirstChars.isNotEmpty()) { + reporter.reportOn(annotationSource, INVALID_OBJC_NAME_FIRST_CHAR, invalidFirstChars.joinToString(""), context) + } + val invalidNameChars = objCName.name?.toSet()?.subtract(validChars) ?: emptySet() + val invalidSwiftNameChars = objCName.swiftName?.toSet()?.subtract(validChars) ?: emptySet() + val invalidChars = invalidNameChars + invalidSwiftNameChars + if (invalidChars.isNotEmpty()) { + reporter.reportOn(annotationSource, INVALID_OBJC_NAME_CHARS, invalidFirstChars.joinToString(""), context) + } + if (objCName.exact && (declaration !is FirClass || declaration.classKind == ClassKind.ENUM_ENTRY)) { + reporter.reportOn(annotationSource, INAPPLICABLE_EXACT_OBJC_NAME, context) + } + if (objCName.exact && objCName.name == null) { + reporter.reportOn(annotationSource, MISSING_EXACT_OBJC_NAME, context) + } + } + + class ObjCName( + val annotation: FirAnnotation + ) { + val name: String? = annotation.getStringArgument(nameName)?.takeIf { it.isNotBlank() } + val swiftName: String? = annotation.getStringArgument(swiftNameName)?.takeIf { it.isNotBlank() } + val exact: Boolean = annotation.getBooleanArgument(exactName) ?: false + + override fun equals(other: Any?): Boolean = + other is ObjCName && name == other.name && swiftName == other.swiftName && exact == other.exact + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + swiftName.hashCode() + result = 31 * result + exact.hashCode() + return result + } + } + + private fun FirAnnotationContainer.getObjCName(): ObjCName? = + getAnnotationByClassId(objCNameClassId)?.let(::ObjCName) + + private fun FirBasedSymbol<*>.getObjCName(): ObjCName? = + getAnnotationByClassId(objCNameClassId)?.let(::ObjCName) + + fun FirBasedSymbol<*>.getObjCNames(): List = when (this) { + is FirFunctionSymbol<*> -> buildList { + add((this@getObjCNames as FirBasedSymbol<*>).getObjCName()) + add(resolvedReceiverTypeRef?.getObjCName()) + valueParameterSymbols.forEach { add(it.getObjCName()) } + } + + else -> listOf(getObjCName()) + } +} diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt new file mode 100644 index 00000000000..55030ce16a5 --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2022 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.native.checkers + +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker +import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_OBJC_NAME_OVERRIDE +import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameChecker.getObjCNames +import org.jetbrains.kotlin.fir.containingClass +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.isIntersectionOverride +import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol +import org.jetbrains.kotlin.fir.scopes.* +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol + +object FirNativeObjCNameOverridesChecker : FirClassChecker() { + + override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) { + // We just need to check intersection overrides, all other declarations are checked by FirNativeObjCNameChecker + val firTypeScope = declaration.unsubstitutedScope(context) + firTypeScope.processAllFunctions { symbol -> + if (!symbol.isIntersectionOverride) return@processAllFunctions + check(firTypeScope, symbol, declaration, context, reporter) + } + firTypeScope.processAllProperties { symbol -> + if (!symbol.isIntersectionOverride) return@processAllProperties + check(firTypeScope, symbol, declaration, context, reporter) + } + } + + fun check( + firTypeScope: FirTypeScope, + memberSymbol: FirCallableSymbol<*>, + declarationToReport: FirDeclaration, + context: CheckerContext, + reporter: DiagnosticReporter + ) { + val overriddenSymbols = firTypeScope.getDirectOverriddenSymbols(memberSymbol) + if (overriddenSymbols.isEmpty()) return + val objCNames = overriddenSymbols.map { it.getFirstBaseSymbol(firTypeScope).getObjCNames() } + if (!objCNames.allNamesEquals()) { + val containingDeclarations = overriddenSymbols.mapNotNull { it.containingClass()?.toFirRegularClassSymbol(context.session) } + reporter.reportOn( + declarationToReport.source, + INCOMPATIBLE_OBJC_NAME_OVERRIDE, + declarationToReport.symbol, + containingDeclarations, + context + ) + } + } + + private fun FirTypeScope.getDirectOverriddenSymbols(memberSymbol: FirCallableSymbol<*>): List> { + return when (memberSymbol) { + is FirNamedFunctionSymbol -> { + processFunctionsByName(memberSymbol.name) {} + getDirectOverriddenFunctions(memberSymbol) + } + + is FirPropertySymbol -> { + processPropertiesByName(memberSymbol.name) {} + getDirectOverriddenProperties(memberSymbol) + } + + else -> error("unexpected member kind $memberSymbol") + } + } + + private fun FirCallableSymbol<*>.getFirstBaseSymbol(firTypeScope: FirTypeScope): FirCallableSymbol<*> { + val overriddenMemberSymbols = firTypeScope.getDirectOverriddenSymbols(this) + return if (overriddenMemberSymbols.isEmpty()) this else overriddenMemberSymbols.first().getFirstBaseSymbol(firTypeScope) + } + + private fun List>.allNamesEquals(): Boolean { + val first = this[0] + for (i in 1 until size) { + if (first != this[i]) return false + } + return true + } +} diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt index b756d562e65..cd7be4992a4 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt @@ -13,7 +13,8 @@ object NativeDeclarationCheckers : DeclarationCheckers() { FirNativeThrowsChecker, FirNativeSharedImmutableChecker, FirNativeThreadLocalChecker, - FirNativeIdentifierChecker + FirNativeIdentifierChecker, + FirNativeObjCNameChecker ) override val callableDeclarationCheckers: Set @@ -23,7 +24,8 @@ object NativeDeclarationCheckers : DeclarationCheckers() { override val classCheckers: Set get() = setOf( - FirNativeObjCRefinementOverridesChecker + FirNativeObjCRefinementOverridesChecker, + FirNativeObjCNameOverridesChecker ) override val regularClassCheckers: Set diff --git a/compiler/testData/diagnostics/nativeTests/objCName.fir.kt b/compiler/testData/diagnostics/nativeTests/objCName.fir.kt index f9ba2193997..a51f1c1f787 100644 --- a/compiler/testData/diagnostics/nativeTests/objCName.fir.kt +++ b/compiler/testData/diagnostics/nativeTests/objCName.fir.kt @@ -24,30 +24,39 @@ open class KotlinClass { @ObjCName("ObjCSubClass", "SwiftSubClass") class KotlinSubClass: KotlinClass() { - @ObjCName("objCProperty") + @ObjCName("objCProperty") override var kotlinProperty: Int = 1 - @ObjCName(swiftName = "swiftFunction") - override fun @receiver:ObjCName("objCReceiver") Int.kotlinFunction( - @ObjCName("objCParam") kotlinParam: Int + @ObjCName(swiftName = "swiftFunction") + override fun @receiver:ObjCName("objCReceiver") Int.kotlinFunction( + @ObjCName("objCParam") kotlinParam: Int ): Int = this + kotlinParam * 2 } -@ObjCName() -val invalidObjCName: Int = 0 +@ObjCName() +val invalidObjCNameA: Int = 0 -@ObjCName("validName", "invalid.name") +@ObjCName("", "") +val invalidObjCNameB: Int = 0 + +@ObjCName("validName", "") +val validBlankObjCNameA: Int = 0 + +@ObjCName("", "validName") +val validBlankObjCNameB: Int = 0 + +@ObjCName("validName", "invalid.name") val invalidCharactersObjCNameA: Int = 0 -@ObjCName("invalid.name", "validName") +@ObjCName("invalid.name", "validName") val invalidCharactersObjCNameB: Int = 0 -@ObjCName("validName1", "1validName") +@ObjCName("validName1", "1validName") val invalidFirstCharacterObjCNameA: Int = 0 -@ObjCName("1validName", "validName1") +@ObjCName("1validName", "validName1") val invalidFirstCharacterObjCNameB: Int = 0 -@ObjCName(swiftName = "SwiftMissingExactName", exact = true) +@ObjCName(swiftName = "SwiftMissingExactName", exact = true) class MissingExactName interface KotlinInterfaceA { @@ -118,32 +127,32 @@ interface KotlinInterfaceB { class KotlinOverrideClass: KotlinInterfaceA, KotlinInterfaceB { override var kotlinPropertyA: Int = 0 - override var kotlinPropertyB: Int = 0 - override var kotlinPropertyC: Int = 0 - override var kotlinPropertyD: Int = 0 - override var kotlinPropertyE: Int = 0 + override var kotlinPropertyB: Int = 0 + override var kotlinPropertyC: Int = 0 + override var kotlinPropertyD: Int = 0 + override var kotlinPropertyE: Int = 0 override var kotlinPropertyF: Int = 0 override fun Int.kotlinFunctionA(kotlinParam: Int): Int = this + kotlinParam - override fun Int.kotlinFunctionB(kotlinParam: Int): Int = this + kotlinParam - override fun Int.kotlinFunctionC(kotlinParam: Int): Int = this + kotlinParam - override fun Int.kotlinFunctionD(kotlinParam: Int): Int = this + kotlinParam - override fun Int.kotlinFunctionE(kotlinParam: Int): Int = this + kotlinParam + override fun Int.kotlinFunctionB(kotlinParam: Int): Int = this + kotlinParam + override fun Int.kotlinFunctionC(kotlinParam: Int): Int = this + kotlinParam + override fun Int.kotlinFunctionD(kotlinParam: Int): Int = this + kotlinParam + override fun Int.kotlinFunctionE(kotlinParam: Int): Int = this + kotlinParam } @ObjCName("ObjCExactChecks", exact = true) class ExactChecks { - @ObjCName("objCProperty", exact = true) + @ObjCName("objCProperty", exact = true) var property: Int = 0 - @ObjCName("objCFunction", exact = true) - fun @receiver:ObjCName("objCReceiver", exact = true) Int.function( - @ObjCName("objCParam", exact = true) param: Int + @ObjCName("objCFunction", exact = true) + fun @receiver:ObjCName("objCReceiver", exact = true) Int.function( + @ObjCName("objCParam", exact = true) param: Int ): Int = this * param } @ObjCName("ObjCEnumExactChecks", exact = true) enum class EnumExactChecks { - @ObjCName("objCEntryOne", exact = true) + @ObjCName("objCEntryOne", exact = true) ENTRY_ONE, @ObjCName("objCEntryTwo") ENTRY_TWO @@ -159,7 +168,7 @@ interface I { fun foo() } -open class Derived : Base(), I +open class Derived : Base(), I open class Derived2 : Derived() { override fun foo() {} @@ -168,11 +177,11 @@ open class Derived2 : Derived() { private const val exact = false private const val objcName = "nonLiteralArgsObjC" -@ObjCName( - objcName, - "nonLiteralArgs" + "Swift", - exact -) +@ObjCName( + objcName, + "nonLiteralArgs" + "Swift", + exact +) val nonLiteralArgs: Int = 0 @ObjCName("invalidArgsObjC", false, "not a boolean") diff --git a/compiler/testData/diagnostics/nativeTests/objCName.kt b/compiler/testData/diagnostics/nativeTests/objCName.kt index 99fc9cf5cc9..ac3d308954a 100644 --- a/compiler/testData/diagnostics/nativeTests/objCName.kt +++ b/compiler/testData/diagnostics/nativeTests/objCName.kt @@ -33,7 +33,16 @@ class KotlinSubClass: KotlinClass() { } @ObjCName() -val invalidObjCName: Int = 0 +val invalidObjCNameA: Int = 0 + +@ObjCName("", "") +val invalidObjCNameB: Int = 0 + +@ObjCName("validName", "") +val validBlankObjCNameA: Int = 0 + +@ObjCName("", "validName") +val validBlankObjCNameB: Int = 0 @ObjCName("validName", "invalid.name") val invalidCharactersObjCNameA: Int = 0 diff --git a/compiler/testData/diagnostics/nativeTests/objCName.txt b/compiler/testData/diagnostics/nativeTests/objCName.txt index cb06e56ea0c..b669f60b883 100644 --- a/compiler/testData/diagnostics/nativeTests/objCName.txt +++ b/compiler/testData/diagnostics/nativeTests/objCName.txt @@ -6,9 +6,12 @@ private const val exact: kotlin.Boolean = false @kotlin.native.ObjCName(name = "invalid.name", swiftName = "validName") public val invalidCharactersObjCNameB: kotlin.Int = 0 @kotlin.native.ObjCName(name = "validName1", swiftName = "1validName") public val invalidFirstCharacterObjCNameA: kotlin.Int = 0 @kotlin.native.ObjCName(name = "1validName", swiftName = "validName1") public val invalidFirstCharacterObjCNameB: kotlin.Int = 0 -@kotlin.native.ObjCName public val invalidObjCName: kotlin.Int = 0 +@kotlin.native.ObjCName public val invalidObjCNameA: kotlin.Int = 0 +@kotlin.native.ObjCName(name = "", swiftName = "") public val invalidObjCNameB: kotlin.Int = 0 @kotlin.native.ObjCName(exact = false, name = "nonLiteralArgsObjC", swiftName = "nonLiteralArgsSwift") public val nonLiteralArgs: kotlin.Int = 0 private const val objcName: kotlin.String = "nonLiteralArgsObjC" +@kotlin.native.ObjCName(name = "validName", swiftName = "") public val validBlankObjCNameA: kotlin.Int = 0 +@kotlin.native.ObjCName(name = "", swiftName = "validName") public val validBlankObjCNameB: kotlin.Int = 0 public open class Base { public constructor Base() @@ -161,3 +164,4 @@ package kotlin { } } } +