Show warning on usages of javaClass<T>() in annotations loaded from Java

This commit is contained in:
Denis Zharkov
2015-04-17 17:34:44 +03:00
parent 482b4e3688
commit f53baebf89
13 changed files with 272 additions and 13 deletions
@@ -16,15 +16,23 @@
package org.jetbrains.kotlin.load.kotlin
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.java.descriptors.JavaConstructorDescriptor
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.types.JetType
public class JavaAnnotationCallChecker : CallChecker {
override fun <F : CallableDescriptor?> check(resolvedCall: ResolvedCall<F>, context: BasicCallResolutionContext) {
@@ -32,14 +40,56 @@ public class JavaAnnotationCallChecker : CallChecker {
if (resultingDescriptor !is JavaConstructorDescriptor ||
resultingDescriptor.getContainingDeclaration().getKind() != ClassKind.ANNOTATION_CLASS) return
reportErrorsOnPositionedArguments(resolvedCall, context)
reportWarningOnJavaClassUsages(resolvedCall, context)
}
private fun reportWarningOnJavaClassUsages(
resolvedCall: ResolvedCall<*>,
context: BasicCallResolutionContext
) {
resolvedCall.getValueArguments().filter { it.getKey().getType().isJavaLangClassOrArray() }.forEach {
reportOnValueArgument(context, it, ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION)
}
}
private fun JetType.isJavaLangClassOrArray() = isJavaLangClass() ||
(KotlinBuiltIns.isArray(this) && getArguments().first().getType().isJavaLangClass())
private fun JetType.isJavaLangClass(): Boolean {
val classifier = getConstructor().getDeclarationDescriptor()
if (classifier !is ClassDescriptor) return false
return DescriptorUtils.isJavaLangClass(classifier)
}
private fun reportErrorsOnPositionedArguments(
resolvedCall: ResolvedCall<*>,
context: BasicCallResolutionContext
) {
resolvedCall.getValueArguments().filter {
p -> p.key.getName() != JvmAnnotationNames.DEFAULT_ANNOTATION_MEMBER_NAME &&
p.value is ExpressionValueArgument &&
!((p.value as ExpressionValueArgument).getValueArgument()?.isNamed() ?: true)
p ->
p.key.getName() != JvmAnnotationNames.DEFAULT_ANNOTATION_MEMBER_NAME &&
p.value is ExpressionValueArgument &&
!((p.value as ExpressionValueArgument).getValueArgument()?.isNamed() ?: true)
}.forEach {
context.trace.report(
ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION.on(
it.getValue().getArguments().first().getArgumentExpression()))
reportOnValueArgument(context, it, ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION)
}
}
private fun reportOnValueArgument(
context: BasicCallResolutionContext,
argument: Map.Entry<ValueParameterDescriptor, ResolvedValueArgument>,
diagnostic: DiagnosticFactory0<JetExpression>
) {
argument.getValue().getArguments().forEach {
if (it.getArgumentExpression() != null) {
context.trace.report(
diagnostic.on(
it.getArgumentExpression()
)
)
}
}
}
}
@@ -57,6 +57,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(ErrorsJvm.NATIVE_DECLARATION_CANNOT_BE_INLINED, "Members of traits can not be inlined");
MAP.put(ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION, "Only named arguments are available for Java annotations");
MAP.put(ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION, "Usage of `javaClass<T>()` in annotations is deprecated. Use T::class instead");
MAP.put(ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH, "Expression ''{0}'' uses reflection which is not found in compilation classpath. Make sure you have kotlin-reflect.jar in the classpath", Renderers.ELEMENT_TEXT);
@@ -52,6 +52,7 @@ public interface ErrorsJvm {
DiagnosticFactory0<JetDeclaration> NATIVE_DECLARATION_CANNOT_BE_INLINED = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<JetExpression> POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<JetExpression> JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION = DiagnosticFactory0.create(WARNING);
// TODO: make this a warning
DiagnosticFactory1<JetExpression, JetExpression> NO_REFLECTION_IN_CLASS_PATH = DiagnosticFactory1.create(ERROR);
@@ -306,7 +306,7 @@ public class AnnotationResolver {
if (descriptor != null && DescriptorUtils.isEnumClass(descriptor)) {
trace.report(Errors.ANNOTATION_PARAMETER_MUST_BE_ENUM_CONST.on(argumentExpression));
}
else if (descriptor instanceof ClassDescriptor && CompileTimeConstantUtils.isJavaLangClass((ClassDescriptor) descriptor)) {
else if (descriptor instanceof ClassDescriptor && DescriptorUtils.isJavaLangClass((ClassDescriptor) descriptor)) {
trace.report(Errors.ANNOTATION_PARAMETER_MUST_BE_CLASS_LITERAL.on(argumentExpression));
}
else if (descriptor instanceof ClassDescriptor && KotlinBuiltIns.isKClass((ClassDescriptor) descriptor)) {
@@ -78,7 +78,7 @@ public class CompileTimeConstantUtils {
KotlinBuiltIns builtIns = KotlinBuiltIns.getInstance();
if (isEnumClass(typeDescriptor) ||
isAnnotationClass(typeDescriptor) ||
isJavaLangClass(typeDescriptor) ||
DescriptorUtils.isJavaLangClass(typeDescriptor) ||
KotlinBuiltIns.isKClass(typeDescriptor) ||
KotlinBuiltIns.isPrimitiveArray(parameterType) ||
KotlinBuiltIns.isPrimitiveType(parameterType) ||
@@ -97,7 +97,7 @@ public class CompileTimeConstantUtils {
if (arrayTypeDescriptor != null) {
return isEnumClass(arrayTypeDescriptor) ||
isAnnotationClass(arrayTypeDescriptor) ||
isJavaLangClass(arrayTypeDescriptor) ||
DescriptorUtils.isJavaLangClass(arrayTypeDescriptor) ||
KotlinBuiltIns.isKClass(arrayTypeDescriptor) ||
builtIns.getStringType().equals(arrayType);
}
@@ -127,10 +127,6 @@ public class CompileTimeConstantUtils {
return "kotlin.javaClass.function".equals(getIntrinsicAnnotationArgument(resolvedCall.getResultingDescriptor().getOriginal()));
}
public static boolean isJavaLangClass(ClassDescriptor descriptor) {
return "java.lang.Class".equals(DescriptorUtils.getFqName(descriptor).asString());
}
public static boolean canBeReducedToBooleanConstant(
@Nullable JetExpression expression,
@NotNull BindingTrace trace,
@@ -0,0 +1,17 @@
// FILE: A.java
public @interface A {
Class<?>[] value();
}
// FILE: b.kt
val jClass = javaClass<String>()
A(
<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION, ANNOTATION_PARAMETER_MUST_BE_CLASS_LITERAL!>jClass<!>,
<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Int>()<!>
)
class MyClass1
A(
<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<<!UNRESOLVED_REFERENCE!>Err<!>>()<!>,
<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<String>()<!>
) class MyClass2
@@ -0,0 +1,26 @@
package
internal val jClass: java.lang.Class<kotlin.String>
public final annotation class A : kotlin.Annotation {
public /*synthesized*/ constructor A(/*0*/ vararg value: java.lang.Class<*> /*kotlin.Array<out java.lang.Class<*>>*/)
public constructor A(/*0*/ vararg value: kotlin.reflect.KClass<*> /*kotlin.Array<out kotlin.reflect.KClass<*>>*/)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public abstract fun value(): kotlin.Array<kotlin.reflect.KClass<*>>
}
A(value = {javaClass<kotlin.Int>()}: kotlin.Array<out java.lang.Class<*>>) internal final class MyClass1 {
public constructor MyClass1()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(value = {javaClass<[ERROR : Err]>(), javaClass<kotlin.String>()}: kotlin.Array<out java.lang.Class<*>>) internal final class MyClass2 {
public constructor MyClass2()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,26 @@
// FILE: A.java
public @interface A {
Class<?>[] value() default {Integer.class};
Class<?>[] arg() default {String.class};
}
// FILE: b.kt
A(<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Int>()<!>,
<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Any>()<!>,
arg = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>array(javaClass<String>(), javaClass<Double>())<!>)
class MyClass1
A(<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Int>()<!>, <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Any>()<!>)
class MyClass2
A(arg = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>array(javaClass<String>(), javaClass<Double>())<!>)
class MyClass3
A class MyClass4
A(value = *<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>array(javaClass<Int>(), javaClass<Any>())<!>,
arg = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>array(javaClass<String>(), javaClass<Double>())<!>)
class MyClass5
A(value = *<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>array(javaClass<Int>(), javaClass<Any>())<!>)
class MyClass6
@@ -0,0 +1,53 @@
package
public final annotation class A : kotlin.Annotation {
public /*synthesized*/ constructor A(/*0*/ vararg value: java.lang.Class<*> /*kotlin.Array<out java.lang.Class<*>>*/ = ..., /*1*/ arg: kotlin.Array<out java.lang.Class<*>> = ...)
public constructor A(/*0*/ vararg value: kotlin.reflect.KClass<*> /*kotlin.Array<out kotlin.reflect.KClass<*>>*/ = ..., /*1*/ arg: kotlin.Array<out kotlin.reflect.KClass<*>> = ...)
public abstract fun arg(): kotlin.Array<kotlin.reflect.KClass<*>>
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public abstract fun value(): kotlin.Array<kotlin.reflect.KClass<*>>
}
A(arg = {javaClass<kotlin.String>(), javaClass<kotlin.Double>()}: kotlin.Array<java.lang.Class<out kotlin.Comparable<out kotlin.Any?>>>, value = {javaClass<kotlin.Int>(), javaClass<kotlin.Any>()}: kotlin.Array<out java.lang.Class<*>>) internal final class MyClass1 {
public constructor MyClass1()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(value = {javaClass<kotlin.Int>(), javaClass<kotlin.Any>()}: kotlin.Array<out java.lang.Class<*>>) internal final class MyClass2 {
public constructor MyClass2()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(arg = {javaClass<kotlin.String>(), javaClass<kotlin.Double>()}: kotlin.Array<java.lang.Class<out kotlin.Comparable<out kotlin.Any?>>>) internal final class MyClass3 {
public constructor MyClass3()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A() internal final class MyClass4 {
public constructor MyClass4()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(arg = {javaClass<kotlin.String>(), javaClass<kotlin.Double>()}: kotlin.Array<java.lang.Class<out kotlin.Comparable<out kotlin.Any?>>>, value = {javaClass<kotlin.Int>(), javaClass<kotlin.Any>()}: kotlin.Array<java.lang.Class<out kotlin.Any>>) internal final class MyClass5 {
public constructor MyClass5()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(value = {javaClass<kotlin.Int>(), javaClass<kotlin.Any>()}: kotlin.Array<java.lang.Class<out kotlin.Any>>) internal final class MyClass6 {
public constructor MyClass6()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,14 @@
// FILE: A.java
public @interface A {
Class<?> value() default Integer.class;
Class<?> arg() default String.class;
}
// FILE: b.kt
A(<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Int>()<!>, arg = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<String>()<!>) class MyClass1
A(<!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Int>()<!>) class MyClass2
A(arg = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<String>()<!>) class MyClass3
A class MyClass4
A(value = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Int>()<!>, arg = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<String>()<!>) class MyClass5
A(value = <!JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION!>javaClass<Int>()<!>) class MyClass6
@@ -0,0 +1,53 @@
package
public final annotation class A : kotlin.Annotation {
public /*synthesized*/ constructor A(/*0*/ value: java.lang.Class<*> = ..., /*1*/ arg: java.lang.Class<*> = ...)
public constructor A(/*0*/ value: kotlin.reflect.KClass<*> = ..., /*1*/ arg: kotlin.reflect.KClass<*> = ...)
public abstract fun arg(): kotlin.reflect.KClass<*>
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public abstract fun value(): kotlin.reflect.KClass<*>
}
A(arg = javaClass<kotlin.String>(): java.lang.Class<kotlin.String>, value = javaClass<kotlin.Int>(): java.lang.Class<kotlin.Int>) internal final class MyClass1 {
public constructor MyClass1()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(value = javaClass<kotlin.Int>(): java.lang.Class<kotlin.Int>) internal final class MyClass2 {
public constructor MyClass2()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(arg = javaClass<kotlin.String>(): java.lang.Class<kotlin.String>) internal final class MyClass3 {
public constructor MyClass3()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A() internal final class MyClass4 {
public constructor MyClass4()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(arg = javaClass<kotlin.String>(): java.lang.Class<kotlin.String>, value = javaClass<kotlin.Int>(): java.lang.Class<kotlin.Int>) internal final class MyClass5 {
public constructor MyClass5()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
A(value = javaClass<kotlin.Int>(): java.lang.Class<kotlin.Int>) internal final class MyClass6 {
public constructor MyClass6()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -117,6 +117,24 @@ public class JetDiagnosticsTestWithStdLibGenerated extends AbstractJetDiagnostic
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationParameters"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("javaClassArgumentError.kt")
public void testJavaClassArgumentError() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationParameters/javaClassArgumentError.kt");
doTest(fileName);
}
@TestMetadata("javaClassArrayInAnnotations.kt")
public void testJavaClassArrayInAnnotations() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationParameters/javaClassArrayInAnnotations.kt");
doTest(fileName);
}
@TestMetadata("javaClassInAnnotations.kt")
public void testJavaClassInAnnotations() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationParameters/javaClassInAnnotations.kt");
doTest(fileName);
}
@TestMetadata("orderWithValue.kt")
public void testOrderWithValue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationParameters/orderWithValue.kt");
@@ -532,4 +532,8 @@ public class DescriptorUtils {
}
}
}
public static boolean isJavaLangClass(ClassDescriptor descriptor) {
return "java.lang.Class".equals(getFqName(descriptor).asString());
}
}