Add custom diagnostic checker for @JvmDefault annotation

This commit is contained in:
Mikhael Bogdanov
2018-03-29 16:41:47 +02:00
parent 89f22e69b4
commit 23e8adb793
21 changed files with 205 additions and 47 deletions
@@ -228,6 +228,9 @@ class K2JVMCompilerArguments : CommonCompilerArguments() {
)
var noExceptionOnExplicitEqualsForBoxedNull by FreezableVar(false)
@Argument(value = "-Xenable-jvm-default", description = "Allow to use '@JvmDefault' for JVM default method support")
var enableJvmDefault: Boolean by FreezableVar(false)
// Paths to output directories for friend modules.
var friendPaths: Array<String>? by FreezableVar(null)
@@ -237,6 +240,7 @@ class K2JVMCompilerArguments : CommonCompilerArguments() {
jsr305,
supportCompatqualCheckerFrameworkAnnotations
)
result[AnalysisFlag.enableJvmDefault] = enableJvmDefault
return result
}
@@ -129,4 +129,7 @@ public class JVMConfigurationKeys {
public static final CompilerConfigurationKey<List<String>> ADDITIONAL_JAVA_MODULES =
CompilerConfigurationKey.create("additional Java modules");
public static final CompilerConfigurationKey<Boolean> ENABLE_JVM_DEFAULT =
CompilerConfigurationKey.create("Allow to use '@JvmDefault'");
}
@@ -5,26 +5,28 @@
package org.jetbrains.kotlin.resolve.jvm.checkers
import org.jetbrains.kotlin.config.AnalysisFlag
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmDefaultAnnotation
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
class JvmDefaultChecker(val jvmTarget: JvmTarget) : DeclarationChecker {
companion object {
val JVM_DEFAULT_FQ_NAME = FqName("kotlin.jvm.JvmDefault")
}
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
val enableJvmDefault = context.languageVersionSettings.getFlag(AnalysisFlag.enableJvmDefault)
descriptor.annotations.findAnnotation(JVM_DEFAULT_FQ_NAME)?.let { annotationDescriptor ->
val reportOn = DescriptorToSourceUtils.getSourceFromAnnotation(annotationDescriptor) ?: declaration
@@ -32,10 +34,23 @@ class JvmDefaultChecker(val jvmTarget: JvmTarget) : DeclarationChecker {
context.trace.report(ErrorsJvm.JVM_DEFAULT_NOT_IN_INTERFACE.on(reportOn))
} else if (jvmTarget == JvmTarget.JVM_1_6) {
context.trace.report(ErrorsJvm.JVM_DEFAULT_IN_JVM6_TARGET.on(reportOn))
} else if (!enableJvmDefault) {
context.trace.report(ErrorsJvm.JVM_DEFAULT_IN_DECLARATION.on(declaration))
}
return@check
}
if (descriptor is ClassDescriptor) {
val hasDeclaredJvmDefaults =
descriptor.unsubstitutedMemberScope.getContributedDescriptors().filterIsInstance<CallableMemberDescriptor>().any {
it.kind.isReal && it.hasJvmDefaultAnnotation()
}
if (!hasDeclaredJvmDefaults && !checkJvmDefaultThroughInheritance(descriptor, enableJvmDefault)) {
context.trace.report(ErrorsJvm.JVM_DEFAULT_THROUGH_INHERITANCE.on(declaration))
}
}
if (!DescriptorUtils.isInterface(descriptor.containingDeclaration)) return
val memberDescriptor = descriptor as? CallableMemberDescriptor ?: return
if (descriptor is PropertyAccessorDescriptor) return
@@ -44,4 +59,23 @@ class JvmDefaultChecker(val jvmTarget: JvmTarget) : DeclarationChecker {
context.trace.report(ErrorsJvm.JVM_DEFAULT_REQUIRED_FOR_OVERRIDE.on(declaration))
}
}
private fun checkJvmDefaultThroughInheritance(descriptor: DeclarationDescriptor, enableJvmDefault: Boolean): Boolean {
if (enableJvmDefault) return true
if (descriptor !is ClassDescriptor) return true
if (!DescriptorUtils.isInterface(descriptor) &&
!DescriptorUtils.isAnnotationClass(descriptor)
) {
return descriptor.getSuperInterfaces().all {
checkJvmDefaultThroughInheritance(it, enableJvmDefault)
}
}
return descriptor.unsubstitutedMemberScope.getContributedDescriptors().filterIsInstance<CallableMemberDescriptor>().all {
!it.hasJvmDefaultAnnotation()
}
}
}
@@ -137,6 +137,9 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(JVM_DEFAULT_NOT_IN_INTERFACE,"'@JvmDefault' is only supported on interface members");
MAP.put(JVM_DEFAULT_IN_JVM6_TARGET,"'@JvmDefault' is only supported since JVM target 1.8. Recompile with '-jvm-target 1.8'");
MAP.put(JVM_DEFAULT_REQUIRED_FOR_OVERRIDE, "'@JvmDefault' is required for an override of a '@JvmDefault' member");
MAP.put(JVM_DEFAULT_IN_DECLARATION, "Usage of '@JvmDefault' is only allowed if the flag -Xenable-jvm-default is enabled");
MAP.put(JVM_DEFAULT_THROUGH_INHERITANCE, "Inheritance from an interface with '@JvmDefault' members is only allowed if the flag -Xenable-jvm-default is enabled");
MAP.put(USAGE_OF_JVM_THROUGH_SUPER_CALL, "Super calls of '@JvmDefault' members are only allowed if the flag -Xenable-jvm-default is enabled");
}
@NotNull
@@ -117,6 +117,9 @@ public interface ErrorsJvm {
DiagnosticFactory0<PsiElement> JVM_DEFAULT_NOT_IN_INTERFACE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> JVM_DEFAULT_IN_JVM6_TARGET = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtDeclaration> JVM_DEFAULT_REQUIRED_FOR_OVERRIDE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<KtDeclaration> JVM_DEFAULT_IN_DECLARATION = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<KtDeclaration> JVM_DEFAULT_THROUGH_INHERITANCE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<KtDeclaration> USAGE_OF_JVM_THROUGH_SUPER_CALL = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
enum NullabilityInformationSource {
KOTLIN {
@@ -1,6 +1,7 @@
// !DIAGNOSTICS: -EXPERIMENTAL_API_USAGE
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
// !ENABLE_JVM_DEFAULT
interface A<T> {
@JvmDefault
fun test(p: T) {
@@ -0,0 +1,19 @@
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
interface A {
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
fun test()<!> {
}
}
interface <!JVM_DEFAULT_THROUGH_INHERITANCE!>B<!> : A {
}
open class <!JVM_DEFAULT_THROUGH_INHERITANCE!>Foo<!> : B
open class <!JVM_DEFAULT_THROUGH_INHERITANCE!>Foo2<!> : B, A
class Bar : Foo()
class <!JVM_DEFAULT_THROUGH_INHERITANCE!>Bar2<!> : Foo(), A
class <!JVM_DEFAULT_THROUGH_INHERITANCE!>Bar3<!> : Foo(), B
@@ -0,0 +1,55 @@
package
public interface A {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public interface B : A {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open override /*1*/ /*fake_override*/ fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class Bar : Foo {
public constructor Bar()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open override /*1*/ /*fake_override*/ fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class Bar2 : Foo, A {
public constructor Bar2()
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open override /*2*/ /*fake_override*/ fun test(): kotlin.Unit
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class Bar3 : Foo, B {
public constructor Bar3()
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open override /*2*/ /*fake_override*/ fun test(): kotlin.Unit
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
}
public open class Foo : B {
public constructor Foo()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open override /*1*/ /*fake_override*/ fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public open class Foo2 : B, A {
public constructor Foo2()
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open override /*2*/ /*fake_override*/ fun test(): kotlin.Unit
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,30 @@
// !DIAGNOSTICS: -EXPERIMENTAL_API_USAGE
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
interface B {
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
fun test()<!> {}
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
abstract fun test2(s: String = "")<!>
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
abstract fun test3()<!>
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
abstract val prop: String<!>
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
abstract val prop2: String<!>
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
val prop3: String<!>
get() = ""
<!JVM_DEFAULT_IN_DECLARATION!>@JvmDefault
var prop4: String<!>
get() = ""
set(value) {}
}
@@ -0,0 +1,14 @@
package
public interface B {
@kotlin.jvm.JvmDefault public abstract val prop: kotlin.String
@kotlin.jvm.JvmDefault public abstract val prop2: kotlin.String
@kotlin.jvm.JvmDefault public open val prop3: kotlin.String
@kotlin.jvm.JvmDefault public open var prop4: kotlin.String
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open fun test(): kotlin.Unit
@kotlin.jvm.JvmDefault public abstract fun test2(/*0*/ s: kotlin.String = ...): kotlin.Unit
@kotlin.jvm.JvmDefault public abstract fun test3(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -1,6 +1,7 @@
// !DIAGNOSTICS: -EXPERIMENTAL_API_USAGE
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
// !ENABLE_JVM_DEFAULT
interface B {
@JvmDefault
@@ -1,6 +1,7 @@
// !DIAGNOSTICS: -EXPERIMENTAL_API_USAGE
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
// !ENABLE_JVM_DEFAULT
interface A {
@JvmDefault
fun test() {}
@@ -1,28 +0,0 @@
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
@JvmDefaultFeature
interface A {
@JvmDefault
fun test() {
}
}
interface Abstract : <!EXPERIMENTAL_API_USAGE!>A<!> {
<!JVM_DEFAULT_REQUIRED_FOR_OVERRIDE!>override fun <!EXPERIMENTAL_OVERRIDE!>test<!>()<!>
}
interface ANonDefault {
fun test() {}
}
interface B : <!EXPERIMENTAL_API_USAGE!>A<!> {
<!JVM_DEFAULT_REQUIRED_FOR_OVERRIDE!>override fun <!EXPERIMENTAL_OVERRIDE!>test<!>()<!> {}
}
interface C : ANonDefault, <!EXPERIMENTAL_API_USAGE!>A<!> {
<!JVM_DEFAULT_REQUIRED_FOR_OVERRIDE!>override fun <!EXPERIMENTAL_OVERRIDE!>test<!>()<!> {}
}
interface D : <!EXPERIMENTAL_API_USAGE!>A<!>, ANonDefault {
<!JVM_DEFAULT_REQUIRED_FOR_OVERRIDE!>override fun <!EXPERIMENTAL_OVERRIDE!>test<!>()<!> {}
}
@@ -1,6 +1,6 @@
package
@kotlin.jvm.JvmDefaultFeature public interface A {
public interface A {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
@kotlin.jvm.JvmDefault public open fun test(): kotlin.Unit
@@ -1,6 +1,7 @@
// !DIAGNOSTICS: -EXPERIMENTAL_API_USAGE
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
// !ENABLE_JVM_DEFAULT
interface A {
@JvmDefault
val test: String
@@ -13,7 +13,6 @@ interface B {
abstract fun test3()
<!JVM_DEFAULT_IN_JVM6_TARGET!>@JvmDefault<!>
abstract val prop: String
@@ -1,6 +1,7 @@
// !DIAGNOSTICS: -EXPERIMENTAL_API_USAGE
// !API_VERSION: 1.3
// !JVM_TARGET: 1.8
// !ENABLE_JVM_DEFAULT
interface B {
@JvmDefault
@@ -28,6 +28,7 @@ const val API_VERSION_DIRECTIVE = "API_VERSION"
const val EXPERIMENTAL_DIRECTIVE = "EXPERIMENTAL"
const val USE_EXPERIMENTAL_DIRECTIVE = "USE_EXPERIMENTAL"
const val ENABLE_JVM_DEFAULT = "ENABLE_JVM_DEFAULT"
data class CompilerTestLanguageVersionSettings(
private val initialLanguageFeatures: Map<LanguageFeature, LanguageFeature.State>,
@@ -57,6 +58,7 @@ fun parseLanguageVersionSettings(directiveMap: Map<String, String>): LanguageVer
val languageFeaturesString = directiveMap[LANGUAGE_DIRECTIVE]
val experimental = directiveMap[EXPERIMENTAL_DIRECTIVE]?.split(' ')?.let { AnalysisFlag.experimental to it }
val useExperimental = directiveMap[USE_EXPERIMENTAL_DIRECTIVE]?.split(' ')?.let { AnalysisFlag.useExperimental to it }
val enableJvmDefault = AnalysisFlag.enableJvmDefault to directiveMap.containsKey(ENABLE_JVM_DEFAULT)
if (apiVersionString == null && languageFeaturesString == null && experimental == null && useExperimental == null) return null
@@ -69,7 +71,7 @@ fun parseLanguageVersionSettings(directiveMap: Map<String, String>): LanguageVer
return CompilerTestLanguageVersionSettings(
languageFeatures, apiVersion, languageVersion,
mapOf(*listOfNotNull(experimental, useExperimental).toTypedArray())
mapOf(*listOfNotNull(experimental, useExperimental, enableJvmDefault).toTypedArray())
)
}
@@ -482,6 +482,18 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
doTest(fileName);
}
@TestMetadata("jvmDefaultInInheritance.kt")
public void testJvmDefaultInInheritance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/jvmDefaultInInheritance.kt");
doTest(fileName);
}
@TestMetadata("noJvmDefaultFlag.kt")
public void testNoJvmDefaultFlag() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/noJvmDefaultFlag.kt");
doTest(fileName);
}
@TestMetadata("notInterface.kt")
public void testNotInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/notInterface.kt");
@@ -500,12 +512,6 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
doTest(fileName);
}
@TestMetadata("simpleOverrideWithFeature.kt")
public void testSimpleOverrideWithFeature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/simpleOverrideWithFeature.kt");
doTest(fileName);
}
@TestMetadata("simplePropertyOverride.kt")
public void testSimplePropertyOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/simplePropertyOverride.kt");
@@ -482,6 +482,18 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
doTest(fileName);
}
@TestMetadata("jvmDefaultInInheritance.kt")
public void testJvmDefaultInInheritance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/jvmDefaultInInheritance.kt");
doTest(fileName);
}
@TestMetadata("noJvmDefaultFlag.kt")
public void testNoJvmDefaultFlag() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/noJvmDefaultFlag.kt");
doTest(fileName);
}
@TestMetadata("notInterface.kt")
public void testNotInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/notInterface.kt");
@@ -500,12 +512,6 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
doTest(fileName);
}
@TestMetadata("simpleOverrideWithFeature.kt")
public void testSimpleOverrideWithFeature() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/simpleOverrideWithFeature.kt");
doTest(fileName);
}
@TestMetadata("simplePropertyOverride.kt")
public void testSimplePropertyOverride() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/simplePropertyOverride.kt");
@@ -69,5 +69,8 @@ class AnalysisFlag<out T> internal constructor(
@JvmStatic
val explicitApiVersion by Flag.Boolean
@JvmStatic
val enableJvmDefault by Flag.Boolean
}
}