Restore and deprecate Function{n}/ExtensionFunction{n} classes for easier migration

Users' Java code will not break in common cases (when passing functions to
Kotlin), and deprecation warnings will be reported.

Provide an inspection with a quick fix which allows to replace deprecated
function class usages to the new classes. Include this fix to the "code
cleanup" action
This commit is contained in:
Alexander Udalov
2015-05-25 21:56:48 +03:00
parent 851007cc6c
commit d14e5b8a72
64 changed files with 1596 additions and 14 deletions
@@ -17,20 +17,24 @@
package org.jetbrains.kotlin.load.java.structure.impl
import com.intellij.psi.PsiPackage
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.load.java.lazy.DeprecatedFunctionClassFqNameParser
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.load.java.structure.impl.JavaElementCollectionFromPsiArrayUtil.classes
import org.jetbrains.kotlin.load.java.structure.impl.JavaElementCollectionFromPsiArrayUtil.packages
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import com.intellij.psi.search.GlobalSearchScope
public class JavaPackageImpl(psiPackage: PsiPackage, private val scope: GlobalSearchScope) : JavaElementImpl<PsiPackage>(psiPackage), JavaPackage {
override fun getClasses(nameFilter: (Name) -> Boolean): Collection<JavaClass> {
val psiClasses = getPsi().getClasses(scope).filter {
val name = it.getName()
name != null && nameFilter(Name.identifier(name))
name != null && nameFilter(Name.identifier(name)) && it.getQualifiedName()?.let {
// TODO: drop after M12
!DeprecatedFunctionClassFqNameParser.isDeprecatedFunctionClassFqName(it)
} ?: true
}
return classes(psiClasses)
}
@@ -21,8 +21,14 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.load.java.lazy.DeprecatedFunctionClassFqNameParser
import org.jetbrains.kotlin.load.java.lazy.types.isMarkedNotNull
import org.jetbrains.kotlin.load.java.lazy.types.isMarkedNullable
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaClassifierType
import org.jetbrains.kotlin.load.java.structure.JavaMethod
import org.jetbrains.kotlin.load.java.structure.JavaType
import org.jetbrains.kotlin.load.kotlin.nativeDeclarations.NativeFunChecker
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
@@ -34,8 +40,11 @@ import org.jetbrains.kotlin.resolve.annotations.hasInlineAnnotation
import org.jetbrains.kotlin.resolve.annotations.hasIntrinsicAnnotation
import org.jetbrains.kotlin.resolve.annotations.hasPlatformStaticAnnotation
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
@@ -51,6 +60,7 @@ import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker
import org.jetbrains.kotlin.types.flexibility
import org.jetbrains.kotlin.types.isFlexible
import java.util.ArrayList
public object KotlinJvmCheckerProvider : AdditionalCheckerProvider(
additionalDeclarationCheckers = listOf(PlatformStaticAnnotationChecker(),
@@ -60,7 +70,8 @@ public object KotlinJvmCheckerProvider : AdditionalCheckerProvider(
OverloadsAnnotationChecker()),
additionalCallCheckers = listOf(NeedSyntheticChecker(), JavaAnnotationCallChecker(),
JavaAnnotationMethodCallChecker(), TraitDefaultMethodCallChecker()),
JavaAnnotationMethodCallChecker(), TraitDefaultMethodCallChecker(),
DeprecatedFunctionClassChecker()),
additionalTypeCheckers = listOf(JavaNullabilityWarningsChecker()),
additionalSymbolUsageValidators = listOf()
@@ -322,3 +333,24 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker {
}
}
}
public class DeprecatedFunctionClassChecker : CallChecker {
override fun <F : CallableDescriptor> check(resolvedCall: ResolvedCall<F>, c: BasicCallResolutionContext) {
val javaMethod = (resolvedCall.getResultingDescriptor().getSource() as? JavaSourceElement)?.javaElement as? JavaMethod ?: return
val fqNames = ArrayList<Pair<String, String>>(0)
fun processType(type: JavaType) {
val javaClass = (type as? JavaClassifierType)?.getClassifier() as? JavaClass ?: return
val fqName = javaClass.getFqName()?.asString() ?: return
fqNames.add(DeprecatedFunctionClassFqNameParser.extractOldAndNewFqName(fqName) ?: return)
}
javaMethod.getValueParameters().forEach { processType(it.getType()) }
javaMethod.getReturnType()?.let { processType(it) }
for ((oldFqName, newFqName) in fqNames) {
c.trace.report(ErrorsJvm.JAVA_METHOD_USES_DEPRECATED_FUNCTION_CLASS.on(c.call.getCallElement(), oldFqName, newFqName))
}
}
}
@@ -60,7 +60,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION, "Usage of `javaClass<T>()` in annotations is deprecated. Use T::class instead");
MAP.put(ErrorsJvm.DEPRECATED_ANNOTATION_METHOD_CALL, "Annotation methods are deprecated. Use property 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);
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);
MAP.put(ErrorsJvm.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
"Expected type does not accept nulls in {0}, but the value may be null in {1}", Renderers.TO_STRING, Renderers.TO_STRING);
@@ -68,8 +69,11 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(ErrorsJvm.TRAIT_CANT_CALL_DEFAULT_METHOD_VIA_SUPER, "Interfaces can't call Java default methods via super");
MAP.put(ErrorsJvm.WHEN_ENUM_CAN_BE_NULL_IN_JAVA, "Enum argument ''{0}'' can be null in Java, but exhaustive when contains no null branch");
}
MAP.put(ErrorsJvm.JAVA_METHOD_USES_DEPRECATED_FUNCTION_CLASS,
"This Java method uses the deprecated {0} class, which will be removed soon. " +
"Please change the signature to use the new {1} class instead", Renderers.TO_STRING, Renderers.TO_STRING);
}
@NotNull
@Override
@@ -57,6 +57,8 @@ public interface ErrorsJvm {
DiagnosticFactory0<JetElement> TRAIT_CANT_CALL_DEFAULT_METHOD_VIA_SUPER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<JetElement, String, String> JAVA_METHOD_USES_DEPRECATED_FUNCTION_CLASS = DiagnosticFactory2.create(ERROR);
// TODO: make this a warning
DiagnosticFactory1<JetExpression, JetExpression> NO_REFLECTION_IN_CLASS_PATH = DiagnosticFactory1.create(ERROR);
@@ -0,0 +1,35 @@
// FILE: J.java
import kotlin.ExtensionFunction0;
import kotlin.ExtensionFunction1;
import kotlin.Function0;
import kotlin.Function1;
import kotlin.Unit;
public class J {
public static void f1(Function0<Unit> f) {
f.invoke();
}
public static void f2(Function1<String, String> f) {
f.invoke("");
}
public static void ef1(ExtensionFunction1<Integer, Integer, Integer> ef) {
ef.invoke(42, -42);
}
public static ExtensionFunction0<String, Unit> ef2() {
return null;
}
}
// FILE: K.kt
fun foo() = J.<!JAVA_METHOD_USES_DEPRECATED_FUNCTION_CLASS!>f1 { }<!>
fun bar() = J.<!JAVA_METHOD_USES_DEPRECATED_FUNCTION_CLASS!>f2 { it }<!>
fun baz() = J.<!JAVA_METHOD_USES_DEPRECATED_FUNCTION_CLASS!>ef1 <!EXPECTED_PARAMETERS_NUMBER_MISMATCH!>{<!> <!NO_THIS!>this<!> }<!>
fun quux() = J.<!JAVA_METHOD_USES_DEPRECATED_FUNCTION_CLASS!>ef2()<!>
@@ -0,0 +1,19 @@
package
internal fun bar(): kotlin.Unit
internal fun baz(): kotlin.Unit
internal fun foo(): kotlin.Unit
internal fun quux(): kotlin.ExtensionFunction0<kotlin.String!, kotlin.Unit!>!
public open class J {
public constructor J()
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
// Static members
public open fun ef1(/*0*/ ef: kotlin.ExtensionFunction1<kotlin.Int!, kotlin.Int!, kotlin.Int!>!): kotlin.Unit
public open fun ef2(): kotlin.ExtensionFunction0<kotlin.String!, kotlin.Unit!>!
public open fun f1(/*0*/ f: (() -> kotlin.Unit!)!): kotlin.Unit
public open fun f2(/*0*/ f: ((kotlin.String!) -> kotlin.String!)!): kotlin.Unit
}
@@ -35,6 +35,12 @@ public class JetDiagnosticsTestWithStdLibGenerated extends AbstractJetDiagnostic
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("DeprecatedFunctionClasses.kt")
public void testDeprecatedFunctionClasses() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/DeprecatedFunctionClasses.kt");
doTest(fileName);
}
@TestMetadata("instar.kt")
public void testInstar() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/instar.kt");