[FE 1.0] Add NativeObjCRefinement checkers

This commit is contained in:
Rick Clephas
2022-08-19 16:46:15 +02:00
committed by SvyatoslavScherbina
parent 6ae517a2df
commit 203af3afdd
9 changed files with 545 additions and 4 deletions
@@ -54,6 +54,17 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
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")
put(ErrorsNative.REDUNDANT_SWIFT_REFINEMENT, "An ObjC refined declaration can't also be refined in Swift")
put(
ErrorsNative.INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE,
"Refined declaration \"{0}\" overrides declarations with different or no refinement from {1}",
Renderers.NAME,
CommonRenderers.commaSeparated(Renderers.NAME)
)
put(
ErrorsNative.INVALID_OBJC_REFINEMENT_TARGETS,
"Refines annotations are only applicable to annotations with targets FUNCTION and/or PROPERTY"
)
}
}
@@ -52,8 +52,14 @@ object ErrorsNative {
val MISSING_EXACT_OBJC_NAME = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
@JvmField
val NON_LITERAL_OBJC_NAME_ARG = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
@JvmField
val REDUNDANT_SWIFT_REFINEMENT = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
@JvmField
val INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE = DiagnosticFactory2.create<KtElement, DeclarationDescriptor, Collection<DeclarationDescriptor>>(Severity.ERROR)
@JvmField
val INVALID_OBJC_REFINEMENT_TARGETS = DiagnosticFactory0.create<KtElement>(Severity.ERROR)
init {
Errors.Initializer.initializeFactoryNames(ErrorsNative::class.java)
}
}
}
@@ -0,0 +1,62 @@
/*
* 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.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.AnnotationChecker
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeObjCRefinementChecker.hidesFromObjCFqName
import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeObjCRefinementChecker.refinesInSwiftFqName
object NativeObjCRefinementAnnotationChecker : DeclarationChecker {
private val supportedTargets = arrayOf(KotlinTarget.FUNCTION, KotlinTarget.PROPERTY)
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (descriptor !is ClassDescriptor || descriptor.kind != ClassKind.ANNOTATION_CLASS) return
val (objCAnnotation, swiftAnnotation) = descriptor.findRefinesAnnotations()
if (objCAnnotation == null && swiftAnnotation == null) return
if (objCAnnotation != null && swiftAnnotation != null) {
val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(swiftAnnotation) ?: declaration
context.trace.report(ErrorsNative.REDUNDANT_SWIFT_REFINEMENT.on(reportLocation))
}
val targets = AnnotationChecker.applicableTargetSet(descriptor)
val unsupportedTargets = targets - supportedTargets
if (unsupportedTargets.isNotEmpty()) {
objCAnnotation?.let { context.trace.reportInvalidAnnotationTargets(declaration, it) }
swiftAnnotation?.let { context.trace.reportInvalidAnnotationTargets(declaration, it) }
}
}
private fun DeclarationDescriptor.findRefinesAnnotations(): Pair<AnnotationDescriptor?, AnnotationDescriptor?> {
var objCAnnotation: AnnotationDescriptor? = null
var swiftAnnotation: AnnotationDescriptor? = null
for (annotation in annotations) {
when (annotation.fqName) {
hidesFromObjCFqName -> objCAnnotation = annotation
refinesInSwiftFqName -> swiftAnnotation = annotation
}
if (objCAnnotation != null && swiftAnnotation != null) break
}
return objCAnnotation to swiftAnnotation
}
private fun BindingTrace.reportInvalidAnnotationTargets(
declaration: KtDeclaration,
annotation: AnnotationDescriptor
) {
val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(annotation) ?: declaration
report(ErrorsNative.INVALID_OBJC_REFINEMENT_TARGETS.on(reportLocation))
}
}
@@ -0,0 +1,57 @@
/*
* 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.KtDeclaration
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeObjCRefinementOverridesChecker.check
object NativeObjCRefinementChecker : DeclarationChecker {
val hidesFromObjCFqName = FqName("kotlin.native.HidesFromObjC")
val refinesInSwiftFqName = FqName("kotlin.native.RefinesInSwift")
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (descriptor !is CallableMemberDescriptor) return
if (descriptor !is FunctionDescriptor && descriptor !is PropertyDescriptor) return
val (objCAnnotations, swiftAnnotations) = descriptor.findRefinedAnnotations()
if (objCAnnotations.isNotEmpty() && swiftAnnotations.isNotEmpty()) {
swiftAnnotations.forEach {
val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(it) ?: declaration
context.trace.report(ErrorsNative.REDUNDANT_SWIFT_REFINEMENT.on(reportLocation))
}
}
check(declaration, descriptor, context, objCAnnotations, swiftAnnotations)
}
private fun DeclarationDescriptor.findRefinedAnnotations(): Pair<List<AnnotationDescriptor>, List<AnnotationDescriptor>> {
val objCAnnotations = mutableListOf<AnnotationDescriptor>()
val swiftAnnotations = mutableListOf<AnnotationDescriptor>()
for (annotation in annotations) {
val annotations = annotation.annotationClass?.annotations ?: continue
for (metaAnnotation in annotations) {
when (metaAnnotation.fqName) {
hidesFromObjCFqName -> {
objCAnnotations.add(annotation)
break
}
refinesInSwiftFqName -> {
swiftAnnotations.add(annotation)
break
}
}
}
}
return objCAnnotations to swiftAnnotations
}
}
@@ -0,0 +1,108 @@
/*
* 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.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeObjCRefinementChecker.hidesFromObjCFqName
import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeObjCRefinementChecker.refinesInSwiftFqName
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
object NativeObjCRefinementOverridesChecker : DeclarationChecker {
override fun check(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
check(declaration, it, context, emptyList(), emptyList())
}
}
fun check(
declarationToReport: KtDeclaration,
descriptor: CallableMemberDescriptor,
context: DeclarationCheckerContext,
objCAnnotations: List<AnnotationDescriptor>,
swiftAnnotations: List<AnnotationDescriptor>
) {
if (descriptor.overriddenDescriptors.isEmpty()) return
var isHiddenFromObjC = objCAnnotations.isNotEmpty()
var isRefinedInSwift = swiftAnnotations.isNotEmpty()
val supersNotHiddenFromObjC = mutableListOf<CallableMemberDescriptor>()
val supersNotRefinedInSwift = mutableListOf<CallableMemberDescriptor>()
for (overriddenDescriptor in descriptor.overriddenDescriptors) {
val (superIsHiddenFromObjC, superIsRefinedInSwift) = overriddenDescriptor.inheritsRefinedAnnotations()
if (superIsHiddenFromObjC) isHiddenFromObjC = true else supersNotHiddenFromObjC.add(overriddenDescriptor)
if (superIsRefinedInSwift) isRefinedInSwift = true else supersNotRefinedInSwift.add(overriddenDescriptor)
}
if (isHiddenFromObjC && supersNotHiddenFromObjC.isNotEmpty()) {
context.trace.reportIncompatibleOverride(declarationToReport, descriptor, objCAnnotations, supersNotHiddenFromObjC)
}
if (isRefinedInSwift && supersNotRefinedInSwift.isNotEmpty()) {
context.trace.reportIncompatibleOverride(declarationToReport, descriptor, swiftAnnotations, supersNotRefinedInSwift)
}
}
private fun CallableMemberDescriptor.inheritsRefinedAnnotations(): Pair<Boolean, Boolean> {
val (hasObjC, hasSwift) = hasRefinedAnnotations()
if (hasObjC && hasSwift) return true to true
if (overriddenDescriptors.isEmpty()) return hasObjC to hasSwift
// Note: `checkOverrides` requires all overridden descriptors to be either refined or not refined.
val (inheritsObjC, inheritsSwift) = overriddenDescriptors.first().inheritsRefinedAnnotations()
return (hasObjC || inheritsObjC) to (hasSwift || inheritsSwift)
}
private fun CallableMemberDescriptor.hasRefinedAnnotations(): Pair<Boolean, Boolean> {
var hasObjC = false
var hasSwift = false
for (annotation in annotations) {
val annotations = annotation.annotationClass?.annotations ?: continue
for (metaAnnotation in annotations) {
when (metaAnnotation.fqName) {
hidesFromObjCFqName -> {
hasObjC = true
break
}
refinesInSwiftFqName -> {
hasSwift = true
break
}
}
}
if (hasObjC && hasSwift) return true to true
}
return hasObjC to hasSwift
}
private fun BindingTrace.reportIncompatibleOverride(
declaration: KtDeclaration,
descriptor: CallableMemberDescriptor,
annotations: List<AnnotationDescriptor>,
notRefinedSupers: List<CallableMemberDescriptor>
) {
val containingDeclarations = notRefinedSupers.map { it.containingDeclaration }
if (annotations.isEmpty()) {
report(ErrorsNative.INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE.on(declaration, descriptor, containingDeclarations))
} else {
annotations.forEach {
val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(it) ?: declaration
report(ErrorsNative.INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE.on(reportLocation, descriptor, containingDeclarations))
}
}
}
}
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.resolve.konan.platform
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useImpl
import org.jetbrains.kotlin.container.useInstance
@@ -13,7 +12,6 @@ import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.PlatformConfiguratorBase
import org.jetbrains.kotlin.resolve.calls.checkers.TypeOfChecker
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
import org.jetbrains.kotlin.resolve.inline.ReasonableInlineRule
import org.jetbrains.kotlin.resolve.jvm.checkers.SuperCallWithDefaultArgumentsChecker
@@ -26,7 +24,8 @@ object NativePlatformConfigurator : PlatformConfiguratorBase(
additionalDeclarationCheckers = listOf(
NativeThrowsChecker, NativeSharedImmutableChecker,
NativeTopLevelSingletonChecker, NativeThreadLocalChecker,
NativeObjCNameChecker
NativeObjCNameChecker, NativeObjCRefinementChecker,
NativeObjCRefinementAnnotationChecker, NativeObjCRefinementOverridesChecker
)
) {
override fun configureModuleComponents(container: StorageComponentContainer) {