[K/N] Add ObjCName annotation (#4815)
This commit is contained in:
+16
-1
@@ -38,7 +38,22 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
|
||||
"@ThreadLocal is applicable only to property with backing field, to property with delegation or to objects"
|
||||
)
|
||||
put(ErrorsNative.INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL, "@ThreadLocal is applicable only to top level declarations")
|
||||
put(ErrorsNative.INVALID_CHARACTERS_NATIVE, "Name {0}", CommonRenderers.STRING);
|
||||
put(ErrorsNative.INVALID_CHARACTERS_NATIVE, "Name {0}", CommonRenderers.STRING)
|
||||
put(ErrorsNative.INAPPLICABLE_OBJC_NAME, "@ObjCName is not applicable on overrides")
|
||||
put(ErrorsNative.INVALID_OBJC_NAME, "@ObjCName should have a name and/or swiftName")
|
||||
put(ErrorsNative.INVALID_OBJC_NAME_CHARS, "@ObjCName contains illegal characters: {0}", CommonRenderers.STRING)
|
||||
put(
|
||||
ErrorsNative.INVALID_OBJC_NAME_FIRST_CHAR, "@ObjCName contains illegal first characters: {0}",
|
||||
CommonRenderers.STRING
|
||||
)
|
||||
put(
|
||||
ErrorsNative.INCOMPATIBLE_OBJC_NAME_OVERRIDE, "Member \"{0}\" inherits inconsistent @ObjCName from {1}",
|
||||
Renderers.NAME,
|
||||
CommonRenderers.commaSeparated(Renderers.NAME)
|
||||
)
|
||||
put(ErrorsNative.INAPPLICABLE_EXACT_OBJC_NAME, "Exact @ObjCName is only applicable to classes, objects and interfaces")
|
||||
put(ErrorsNative.MISSING_EXACT_OBJC_NAME, "Exact @ObjCName is required to have an ObjC name")
|
||||
put(ErrorsNative.NON_LITERAL_OBJC_NAME_ARG, "@ObjCName accepts only literal string and boolean values")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,22 @@ object ErrorsNative {
|
||||
val VARIABLE_IN_ENUM = DiagnosticFactory0.create<KtElement>(Severity.INFO)
|
||||
@JvmField
|
||||
val INVALID_CHARACTERS_NATIVE = DiagnosticFactoryForDeprecation1.create<PsiElement, String>(LanguageFeature.ProhibitInvalidCharsInNativeIdentifiers)
|
||||
@JvmField
|
||||
val INAPPLICABLE_OBJC_NAME = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
|
||||
@JvmField
|
||||
val INVALID_OBJC_NAME = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
|
||||
@JvmField
|
||||
val INVALID_OBJC_NAME_CHARS = DiagnosticFactory1.create<KtElement, String>(Severity.ERROR)
|
||||
@JvmField
|
||||
val INVALID_OBJC_NAME_FIRST_CHAR = DiagnosticFactory1.create<KtElement, String>(Severity.ERROR)
|
||||
@JvmField
|
||||
val INCOMPATIBLE_OBJC_NAME_OVERRIDE = DiagnosticFactory2.create<KtElement, DeclarationDescriptor, Collection<DeclarationDescriptor>>(Severity.ERROR)
|
||||
@JvmField
|
||||
val INAPPLICABLE_EXACT_OBJC_NAME = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
|
||||
@JvmField
|
||||
val MISSING_EXACT_OBJC_NAME = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
|
||||
@JvmField
|
||||
val NON_LITERAL_OBJC_NAME_ARG = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
|
||||
|
||||
init {
|
||||
Errors.Initializer.initializeFactoryNames(ErrorsNative::class.java)
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.resolve.konan.diagnostics
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtConstantExpression
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
|
||||
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
object NativeObjCNameChecker : DeclarationChecker {
|
||||
private val objCNameFqName = FqName("kotlin.native.ObjCName")
|
||||
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
checkDeclaration(declaration, descriptor, context)
|
||||
checkOverrides(declaration, descriptor, context)
|
||||
checkFakeOverrides(declaration, descriptor, context)
|
||||
}
|
||||
|
||||
private fun checkDeclaration(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
val objCNames = descriptor.getObjCNames().filterNotNull()
|
||||
if (objCNames.isEmpty()) return
|
||||
if (descriptor is CallableMemberDescriptor && descriptor.overriddenDescriptors.isNotEmpty()) {
|
||||
objCNames.forEach {
|
||||
val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(it.annotation) ?: declaration
|
||||
context.trace.report(ErrorsNative.INAPPLICABLE_OBJC_NAME.on(reportLocation))
|
||||
}
|
||||
}
|
||||
objCNames.forEach { checkObjCName(it, declaration, descriptor, context) }
|
||||
}
|
||||
|
||||
// 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: KtDeclaration,
|
||||
descriptor: DeclarationDescriptor,
|
||||
context: DeclarationCheckerContext
|
||||
) {
|
||||
val annotationSource = DescriptorToSourceUtils.getSourceFromAnnotation(objCName.annotation)
|
||||
annotationSource?.valueArguments?.forEach {
|
||||
// We don't support constant references since that would require resolution in ObjCExportLazy
|
||||
val expression = it.getArgumentExpression() ?: return@forEach
|
||||
if (expression is KtConstantExpression || expression is KtStringTemplateExpression ||
|
||||
(it is KtValueArgument && it.stringTemplateExpression != null)
|
||||
) return@forEach
|
||||
context.trace.report(ErrorsNative.NON_LITERAL_OBJC_NAME_ARG.on(expression))
|
||||
}
|
||||
val reportLocation = annotationSource ?: declaration
|
||||
if (objCName.name == null && objCName.swiftName == null) {
|
||||
context.trace.report(ErrorsNative.INVALID_OBJC_NAME.on(reportLocation))
|
||||
}
|
||||
val invalidNameFirstChar = objCName.name?.firstOrNull()?.takeUnless(validFirstChars::contains)
|
||||
val invalidSwiftNameFirstChar = objCName.swiftName?.firstOrNull()?.takeUnless(validFirstChars::contains)
|
||||
val invalidFirstChars = setOfNotNull(invalidNameFirstChar, invalidSwiftNameFirstChar)
|
||||
if (invalidFirstChars.isNotEmpty()) {
|
||||
context.trace.report(ErrorsNative.INVALID_OBJC_NAME_FIRST_CHAR.on(reportLocation, invalidFirstChars.joinToString("")))
|
||||
}
|
||||
val invalidNameChars = objCName.name?.toSet()?.subtract(validChars) ?: emptySet()
|
||||
val invalidSwiftNameChars = objCName.swiftName?.toSet()?.subtract(validChars) ?: emptySet()
|
||||
val invalidChars = invalidNameChars + invalidSwiftNameChars
|
||||
if (invalidChars.isNotEmpty()) {
|
||||
context.trace.report(ErrorsNative.INVALID_OBJC_NAME_CHARS.on(reportLocation, invalidChars.joinToString("")))
|
||||
}
|
||||
if (objCName.exact && (descriptor !is ClassDescriptor || descriptor.kind == ClassKind.ENUM_ENTRY)) {
|
||||
context.trace.report(ErrorsNative.INAPPLICABLE_EXACT_OBJC_NAME.on(reportLocation))
|
||||
}
|
||||
if (objCName.exact && objCName.name == null) {
|
||||
context.trace.report(ErrorsNative.MISSING_EXACT_OBJC_NAME.on(reportLocation))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkOverrides(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
if (descriptor !is CallableMemberDescriptor || descriptor.overriddenDescriptors.isEmpty()) return
|
||||
val objCNames = descriptor.overriddenDescriptors.map { it.getFirstBaseDescriptor().getObjCNames() }
|
||||
if (!objCNames.allNamesEquals()) {
|
||||
val containingDeclarations = descriptor.overriddenDescriptors.map { it.containingDeclaration }
|
||||
context.trace.report(ErrorsNative.INCOMPATIBLE_OBJC_NAME_OVERRIDE.on(declaration, descriptor, containingDeclarations))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFakeOverrides(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
if (descriptor !is ClassDescriptor) return
|
||||
descriptor.defaultType.memberScope
|
||||
.getContributedDescriptors(DescriptorKindFilter.ALL, MemberScope.Companion.ALL_NAME_FILTER)
|
||||
.forEach {
|
||||
if (it !is CallableMemberDescriptor || it.kind.isReal) return@forEach
|
||||
checkOverrides(declaration, it, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CallableMemberDescriptor.getFirstBaseDescriptor(): CallableMemberDescriptor =
|
||||
if (overriddenDescriptors.isEmpty()) this else overriddenDescriptors.first().getFirstBaseDescriptor()
|
||||
|
||||
private class ObjCName(
|
||||
val annotation: AnnotationDescriptor
|
||||
) {
|
||||
val name: String? = annotation.argumentValue("name")?.value?.safeAs<String>()?.takeIf { it.isNotBlank() }
|
||||
val swiftName: String? = annotation.argumentValue("swiftName")?.value?.safeAs<String>()?.takeIf { it.isNotBlank() }
|
||||
val exact: Boolean = annotation.argumentValue("exact")?.value?.safeAs<Boolean>() ?: 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 DeclarationDescriptor.getObjCName(): ObjCName? = annotations.findAnnotation(objCNameFqName)?.let(::ObjCName)
|
||||
|
||||
private fun DeclarationDescriptor.getObjCNames(): List<ObjCName?> = when (this) {
|
||||
is FunctionDescriptor -> buildList {
|
||||
add(getObjCName())
|
||||
add(extensionReceiverParameter?.getObjCName())
|
||||
valueParameters.forEach { add(it.getObjCName()) }
|
||||
}
|
||||
else -> listOf(getObjCName())
|
||||
}
|
||||
|
||||
private fun List<List<ObjCName?>>.allNamesEquals(): Boolean {
|
||||
val first = this[0]
|
||||
for (i in 1 until size) {
|
||||
if (first != this[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -25,7 +25,8 @@ object NativePlatformConfigurator : PlatformConfiguratorBase(
|
||||
),
|
||||
additionalDeclarationCheckers = listOf(
|
||||
NativeThrowsChecker, NativeSharedImmutableChecker,
|
||||
NativeTopLevelSingletonChecker, NativeThreadLocalChecker
|
||||
NativeTopLevelSingletonChecker, NativeThreadLocalChecker,
|
||||
NativeObjCNameChecker
|
||||
)
|
||||
) {
|
||||
override fun configureModuleComponents(container: StorageComponentContainer) {
|
||||
|
||||
Reference in New Issue
Block a user