From 4f701285b1b817c3fe321c83d9f6a8ccad28a4f1 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Thu, 30 Mar 2017 17:44:19 +0300 Subject: [PATCH] Light class builder: do not generate methods delegating to DefaultImpls in kotlin classes Class APIs from java point of view stays the same so we can avoid generating those methods Otherwise we have to calculate all supertypes when getMethods() is called, which imposes severe performance penalties We have to pretend these methods are not 'abstract' (also we consider them 'default' for safety) so java highlighting does not report "class should be abstract" for all inheritors We have to manually report "class should be abstract" on some of the java inheritors, specifically those that are implementing interfaces directly as opposed to extending kotlin classes implementing those interfaces --- .../codegen/ImplementationBodyCodegen.java | 12 +-- .../codegen/InterfaceImplBodyCodegen.kt | 7 +- .../SignatureCollectingClassBuilderFactory.kt | 8 +- ...FactoryForDuplicateSignatureDiagnostics.kt | 7 +- .../kotlin/codegen/state/GenerationState.kt | 11 ++- .../jvm/diagnostics/JvmDeclarationOrigin.kt | 5 +- .../kotlin/asJava/elements/KtLightElements.kt | 5 +- .../asJava/elements/KtLightMemberImpl.kt | 23 ++++-- ...DataClassWithCustomImplementedMembers.java | 70 +++++++++++++++++ .../ExtendingInterfaceWithDefaultImpls.java | 12 +++ .../ExtendingInterfaceWithDefaultImpls.kt | 10 +++ ...eritingInterfaceDefaultImpls.compiled.java | 13 ++++ .../InheritingInterfaceDefaultImpls.java | 7 ++ .../InheritingInterfaceDefaultImpls.kt | 26 +++++++ .../kotlin/asJava/LightClassTestCommon.kt | 3 +- .../AbstractCompilerLightClassTest.java | 3 +- .../CompilerLightClassTestGenerated.java | 12 +++ ...plementedKotlinInterfaceMemberAnnotator.kt | 77 +++++++++++++++++++ idea/src/META-INF/plugin.xml | 2 + .../FakeOverrideToStringInTrait.kt | 2 +- .../FakeOverridesForTraitFunWithImpl.kt | 8 +- .../OverrideIconForOverloadMethodBug.kt | 3 +- .../OverridingTooltipOnDefaultTraitImpl.kt | 2 +- ...ClassWithDefaultImplementationComplex.java | 39 ++++++++++ ...ndClassWithDefaultImplementationComplex.kt | 26 +++++++ ...endClassWithDefaultImplementation_1_6.java | 14 ++++ ...xtendClassWithDefaultImplementation_1_6.kt | 20 +++++ ...tendClassWithDefaultImplementation_1_6.txt | 1 + ...endClassWithDefaultImplementation_1_8.java | 14 ++++ ...xtendClassWithDefaultImplementation_1_8.kt | 20 +++++ ...tendClassWithDefaultImplementation_1_8.txt | 1 + ...nstKotlinBinariesCheckerTestGenerated.java | 18 +++++ ...ainstKotlinSourceCheckerTestGenerated.java | 18 +++++ .../resolve/AbstractIdeLightClassTest.kt | 28 +++++-- .../IdeCompiledLightClassTestGenerated.java | 12 +++ .../resolve/IdeLightClassTestGenerated.java | 12 +++ .../wrappers/kotlinWrappers/nestedClasses.txt | 2 +- .../testData/DefaultImpls.render.txt | 2 +- 38 files changed, 510 insertions(+), 45 deletions(-) create mode 100644 compiler/testData/asJava/lightClasses/DataClassWithCustomImplementedMembers.java create mode 100644 compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.java create mode 100644 compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.kt create mode 100644 compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.compiled.java create mode 100644 compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.java create mode 100644 compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.kt create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/java/UnimplementedKotlinInterfaceMemberAnnotator.kt create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.java create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.kt create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.java create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.kt create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.txt create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.java create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.kt create mode 100644 idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.txt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java index f9fd8200c2d..c5415ab5758 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java @@ -83,6 +83,7 @@ import static org.jetbrains.kotlin.resolve.DescriptorUtils.*; import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_STRING_TYPE; import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE; import static org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.NO_ORIGIN; +import static org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind.CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL; import static org.jetbrains.kotlin.types.Variance.INVARIANT; import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isLocalFunction; import static org.jetbrains.org.objectweb.asm.Opcodes.*; @@ -1282,14 +1283,15 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { CodegenUtilKt.reportTarget6InheritanceErrorIfNeeded(descriptor, myClass.getPsiOrParent(), restrictedInheritance, state); } - private void generateDelegationToDefaultImpl(@NotNull FunctionDescriptor traitFun, @NotNull FunctionDescriptor inheritedFun) { + private void generateDelegationToDefaultImpl(@NotNull FunctionDescriptor interfaceFun, @NotNull FunctionDescriptor inheritedFun) { + functionCodegen.generateMethod( - JvmDeclarationOriginKt.DelegationToDefaultImpls(descriptorToDeclaration(traitFun), traitFun), + new JvmDeclarationOrigin(CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL, descriptorToDeclaration(interfaceFun), interfaceFun), inheritedFun, new FunctionGenerationStrategy.CodegenBased(state) { @Override public void doGenerateBody(@NotNull ExpressionCodegen codegen, @NotNull JvmMethodSignature signature) { - DeclarationDescriptor containingDeclaration = traitFun.getContainingDeclaration(); + DeclarationDescriptor containingDeclaration = interfaceFun.getContainingDeclaration(); if (!DescriptorUtils.isInterface(containingDeclaration)) return; DeclarationDescriptor declarationInheritedFun = inheritedFun.getContainingDeclaration(); @@ -1301,12 +1303,12 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { ClassDescriptor containingTrait = (ClassDescriptor) containingDeclaration; Type traitImplType = typeMapper.mapDefaultImpls(containingTrait); - Method traitMethod = typeMapper.mapAsmMethod(traitFun.getOriginal(), OwnerKind.DEFAULT_IMPLS); + Method traitMethod = typeMapper.mapAsmMethod(interfaceFun.getOriginal(), OwnerKind.DEFAULT_IMPLS); Type[] argTypes = signature.getAsmMethod().getArgumentTypes(); Type[] originalArgTypes = traitMethod.getArgumentTypes(); assert originalArgTypes.length == argTypes.length + 1 : - "Invalid trait implementation signature: " + signature + " vs " + traitMethod + " for " + traitFun; + "Invalid trait implementation signature: " + signature + " vs " + traitMethod + " for " + interfaceFun; InstructionAdapter iv = codegen.v; iv.load(0, OBJECT_TYPE); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/InterfaceImplBodyCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/InterfaceImplBodyCodegen.kt index a211fbcb342..72bd18a722c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/InterfaceImplBodyCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/InterfaceImplBodyCodegen.kt @@ -29,8 +29,8 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtPureClassOrObject import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.jvm.diagnostics.DelegationToDefaultImpls import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.org.objectweb.asm.MethodVisitor @@ -113,7 +113,10 @@ class InterfaceImplBodyCodegen( if (delegateTo is JavaMethodDescriptor) return functionCodegen.generateMethod( - DelegationToDefaultImpls(DescriptorToSourceUtils.descriptorToDeclaration(descriptor), descriptor), + JvmDeclarationOrigin( + JvmDeclarationOriginKind.DEFAULT_IMPL_DELEGATION_TO_SUPERINTERFACE_DEFAULT_IMPL, + DescriptorToSourceUtils.descriptorToDeclaration(descriptor), descriptor + ), descriptor, object : FunctionGenerationStrategy.CodegenBased(state) { override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/SignatureCollectingClassBuilderFactory.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/SignatureCollectingClassBuilderFactory.kt index ed1e231a41c..a4e54e9eddf 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/SignatureCollectingClassBuilderFactory.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/SignatureCollectingClassBuilderFactory.kt @@ -27,7 +27,7 @@ import org.jetbrains.org.objectweb.asm.FieldVisitor import org.jetbrains.org.objectweb.asm.MethodVisitor abstract class SignatureCollectingClassBuilderFactory( - delegate: ClassBuilderFactory + delegate: ClassBuilderFactory, val shouldGenerate: (JvmDeclarationOrigin) -> Boolean ) : DelegatingClassBuilderFactory(delegate) { protected abstract fun handleClashingSignatures(data: ConflictingJvmDeclarationsData) @@ -57,11 +57,17 @@ abstract class SignatureCollectingClassBuilderFactory( override fun newField(origin: JvmDeclarationOrigin, access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor { signatures.putValue(RawSignature(name, desc, MemberKind.FIELD), origin) + if (!shouldGenerate(origin)) { + return AbstractClassBuilder.EMPTY_FIELD_VISITOR + } return super.newField(origin, access, name, desc, signature, value) } override fun newMethod(origin: JvmDeclarationOrigin, access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor { signatures.putValue(RawSignature(name, desc, MemberKind.METHOD), origin) + if (!shouldGenerate(origin)) { + return AbstractClassBuilder.EMPTY_METHOD_VISITOR + } return super.newMethod(origin, access, name, desc, signature, exceptions) } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt index dae5c87c61e..12180b19ac3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/BuilderFactoryForDuplicateSignatureDiagnostics.kt @@ -37,7 +37,7 @@ import org.jetbrains.kotlin.utils.addIfNotNull import java.util.* private val EXTERNAL_SOURCES_KINDS = arrayOf( - JvmDeclarationOriginKind.DELEGATION_TO_DEFAULT_IMPLS, + JvmDeclarationOriginKind.CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL, JvmDeclarationOriginKind.DELEGATION, JvmDeclarationOriginKind.BRIDGE ) @@ -58,8 +58,9 @@ class BuilderFactoryForDuplicateSignatureDiagnostics( bindingContext: BindingContext, private val diagnostics: DiagnosticSink, fileClassesProvider: JvmFileClassesProvider, - moduleName: String -) : SignatureCollectingClassBuilderFactory(builderFactory) { + moduleName: String, + shouldGenerate: (JvmDeclarationOrigin) -> Boolean +) : SignatureCollectingClassBuilderFactory(builderFactory, shouldGenerate) { // Avoid errors when some classes are not loaded for some reason private val typeMapper = KotlinTypeMapper( diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index a1192ba6820..94369b0bf1f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -44,6 +44,8 @@ import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind.CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration import java.io.File @@ -177,8 +179,10 @@ class GenerationState @JvmOverloads constructor( { OptimizationClassBuilderFactory(it, configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false)) }, ::CoroutineTransformerClassBuilderFactory, { BuilderFactoryForDuplicateSignatureDiagnostics( - it, this.bindingContext, diagnostics, fileClassesProvider, this.moduleName - ).apply { duplicateSignatureFactory = this } }, + it, this.bindingContext, diagnostics, + fileClassesProvider, this.moduleName, + shouldGenerate = { !shouldOnlyCollectSignatures(it) } + ).apply { duplicateSignatureFactory = this } }, { BuilderFactoryForDuplicateClassNameDiagnostics(it, diagnostics) }, { configuration.get(JVMConfigurationKeys.DECLARATIONS_JSON_PATH) ?.let { destination -> SignatureDumpingBuilderFactory(it, File(destination)) } ?: it } @@ -209,6 +213,9 @@ class GenerationState @JvmOverloads constructor( fun destroy() { interceptedBuilderFactory.close() } + + private fun shouldOnlyCollectSignatures(origin: JvmDeclarationOrigin) + = classBuilderMode == ClassBuilderMode.LIGHT_CLASSES && origin.originKind == CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL } private class LazyJvmDiagnostics(compute: () -> Diagnostics): Diagnostics { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt index 4951c574a52..0407f0d8832 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt @@ -32,7 +32,8 @@ enum class JvmDeclarationOriginKind { OTHER, PACKAGE_PART, INTERFACE_DEFAULT_IMPL, - DELEGATION_TO_DEFAULT_IMPLS, + CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL, + DEFAULT_IMPL_DELEGATION_TO_SUPERINTERFACE_DEFAULT_IMPL, DELEGATION, SAM_DELEGATION, BRIDGE, @@ -84,8 +85,6 @@ fun MultifileClassPart(file: KtFile, descriptor: PackageFragmentDescriptor): Jvm JvmDeclarationOrigin(MULTIFILE_CLASS_PART, file, descriptor) fun DefaultImpls(element: PsiElement?, descriptor: ClassDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(INTERFACE_DEFAULT_IMPL, element, descriptor) -fun DelegationToDefaultImpls(element: PsiElement?, descriptor: FunctionDescriptor): JvmDeclarationOrigin = - JvmDeclarationOrigin(DELEGATION_TO_DEFAULT_IMPLS, element, descriptor) fun Delegation(element: PsiElement?, descriptor: FunctionDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(DELEGATION, element, descriptor) diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightElements.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightElements.kt index 42de4ee5f19..b2eb7e82a6b 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightElements.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightElements.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.asJava.elements import com.intellij.psi.* import com.intellij.psi.impl.PsiVariableEx import org.jetbrains.kotlin.asJava.builder.LightMemberOrigin +import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind @@ -33,6 +34,8 @@ interface KtLightDeclaration : KtLigh interface KtLightMember : PsiMember, KtLightDeclaration, PsiNameIdentifierOwner, PsiDocCommentOwner { val lightMemberOrigin: LightMemberOrigin? + + override fun getContainingClass(): KtLightClass } interface KtLightField : PsiField, KtLightMember, PsiVariableEx @@ -40,5 +43,5 @@ interface KtLightField : PsiField, KtLightMember, PsiVariableEx interface KtLightMethod : PsiAnnotationMethod, KtLightMember { val isDelegated: Boolean get() = lightMemberOrigin?.originKind == JvmDeclarationOriginKind.DELEGATION - || lightMemberOrigin?.originKind == JvmDeclarationOriginKind.DELEGATION_TO_DEFAULT_IMPLS + || lightMemberOrigin?.originKind == JvmDeclarationOriginKind.CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL } \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightMemberImpl.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightMemberImpl.kt index c30d421521b..90df98878a3 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightMemberImpl.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightMemberImpl.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.psiUtil.hasBody abstract class KtLightMemberImpl( computeRealDelegate: () -> D, @@ -104,18 +105,24 @@ class KtLightModifierList( private val clsDelegate by lazyPub { owner.clsDelegate.modifierList!! } private val _annotations by lazyPub { computeAnnotations(this, clsDelegate) } - override fun hasModifierProperty(name: String): Boolean { - if (dummyDelegate != null) { - if (name in visibilityModifiers) { - if (owner.kotlinOrigin?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false) { - return clsDelegate.hasModifierProperty(name) - } + override fun hasModifierProperty(name: String) = when { + name == PsiModifier.ABSTRACT && isImplementationInInterface() -> false + name == PsiModifier.DEFAULT && isImplementationInInterface() -> true + dummyDelegate != null -> { + when { + name in visibilityModifiers && isOverride() -> + clsDelegate.hasModifierProperty(name) + else -> dummyDelegate.hasModifierProperty(name) } - return dummyDelegate.hasModifierProperty(name) } - return clsDelegate.hasModifierProperty(name) + else -> clsDelegate.hasModifierProperty(name) } + private fun isOverride() = owner.kotlinOrigin?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false + + private fun isImplementationInInterface() + = owner.containingClass.isInterface && owner is KtLightMethod && owner.kotlinOrigin?.hasBody() ?: false + override fun hasExplicitModifier(name: String) = hasModifierProperty(name) override fun setModifierProperty(name: String, value: Boolean) = clsDelegate.setModifierProperty(name, value) diff --git a/compiler/testData/asJava/lightClasses/DataClassWithCustomImplementedMembers.java b/compiler/testData/asJava/lightClasses/DataClassWithCustomImplementedMembers.java new file mode 100644 index 00000000000..1988b04c7eb --- /dev/null +++ b/compiler/testData/asJava/lightClasses/DataClassWithCustomImplementedMembers.java @@ -0,0 +1,70 @@ +public final class Wrapper { + public Wrapper() { /* compiled code */ } + + public static final class Equals { + @org.jetbrains.annotations.NotNull + private final p.G code; + + public boolean equals(@org.jetbrains.annotations.Nullable java.lang.Object other) { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.G getCode() { /* compiled code */ } + + public Equals(@org.jetbrains.annotations.NotNull p.G code) { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.G component1() { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.Wrapper.Equals copy(@org.jetbrains.annotations.NotNull p.G code) { /* compiled code */ } + + public java.lang.String toString() { /* compiled code */ } + + public int hashCode() { /* compiled code */ } + } + + public static final class HashCode { + @org.jetbrains.annotations.NotNull + private final p.G code; + + public int hashCode() { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.G getCode() { /* compiled code */ } + + public HashCode(@org.jetbrains.annotations.NotNull p.G code) { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.G component1() { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.Wrapper.HashCode copy(@org.jetbrains.annotations.NotNull p.G code) { /* compiled code */ } + + public java.lang.String toString() { /* compiled code */ } + + public boolean equals(java.lang.Object p) { /* compiled code */ } + } + + public static final class ToString { + @org.jetbrains.annotations.NotNull + private final p.G code; + + @org.jetbrains.annotations.NotNull + public java.lang.String toString() { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.G getCode() { /* compiled code */ } + + public ToString(@org.jetbrains.annotations.NotNull p.G code) { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.G component1() { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public final p.Wrapper.ToString copy(@org.jetbrains.annotations.NotNull p.G code) { /* compiled code */ } + + public int hashCode() { /* compiled code */ } + + public boolean equals(java.lang.Object p) { /* compiled code */ } + } +} \ No newline at end of file diff --git a/compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.java b/compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.java new file mode 100644 index 00000000000..43f385c121a --- /dev/null +++ b/compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.java @@ -0,0 +1,12 @@ +public interface B extends p.A { + @org.jetbrains.annotations.NotNull + java.lang.String b(); + + final class DefaultImpls { + @org.jetbrains.annotations.NotNull + public static java.lang.String b(p.B $this) { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public static java.lang.String a(p.B $this) { /* compiled code */ } + } +} diff --git a/compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.kt b/compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.kt new file mode 100644 index 00000000000..c97b33adca5 --- /dev/null +++ b/compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.kt @@ -0,0 +1,10 @@ +// p.B +package p + +interface A { + fun a() = "a" +} + +interface B: A { + fun b() = "b" +} \ No newline at end of file diff --git a/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.compiled.java b/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.compiled.java new file mode 100644 index 00000000000..59a42a2bf07 --- /dev/null +++ b/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.compiled.java @@ -0,0 +1,13 @@ +public final class Inheritor implements p.I, p.I2 { + public final void f() { /* compiled code */ } + + public void g() { /* compiled code */ } + + public Inheritor() { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public java.lang.String foo() { /* compiled code */ } + + @org.jetbrains.annotations.NotNull + public java.lang.String bar() { /* compiled code */ } +} \ No newline at end of file diff --git a/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.java b/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.java new file mode 100644 index 00000000000..4e30aafdcd9 --- /dev/null +++ b/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.java @@ -0,0 +1,7 @@ +public final class Inheritor implements p.I, p.I2 { + public final void f() { /* compiled code */ } + + public void g() { /* compiled code */ } + + public Inheritor() { /* compiled code */ } +} \ No newline at end of file diff --git a/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.kt b/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.kt new file mode 100644 index 00000000000..2b78bb02333 --- /dev/null +++ b/compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.kt @@ -0,0 +1,26 @@ +// p.Inheritor +package p + +class Inheritor: I, I2 { + + fun f() { + + } + + override fun g() { + } +} + +interface I : I1 { + fun g() +} + +interface I1 { + fun foo() = "foo" +} + +interface I2 { + fun bar() = "bar" +} + +// LAZINESS:NoLaziness \ No newline at end of file diff --git a/compiler/tests-common/org/jetbrains/kotlin/asJava/LightClassTestCommon.kt b/compiler/tests-common/org/jetbrains/kotlin/asJava/LightClassTestCommon.kt index e2007729af4..dfd2e523f45 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/asJava/LightClassTestCommon.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/asJava/LightClassTestCommon.kt @@ -30,6 +30,7 @@ object LightClassTestCommon { @JvmOverloads fun testLightClass( + expectedFile: File, testDataFile: File, findLightClass: (String) -> PsiClass?, normalizeText: (String) -> String = { it } @@ -42,7 +43,7 @@ object LightClassTestCommon { val lightClass = findLightClass(fqName) val actual = actualText(fqName, lightClass, normalizeText) - KotlinTestUtils.assertEqualsToFile(KotlinTestUtils.replaceExtension(testDataFile, "java"), actual) + KotlinTestUtils.assertEqualsToFile(expectedFile, actual) } private fun actualText(fqName: String?, lightClass: PsiClass?, normalizeText: (String) -> String): String { diff --git a/compiler/tests/org/jetbrains/kotlin/asJava/AbstractCompilerLightClassTest.java b/compiler/tests/org/jetbrains/kotlin/asJava/AbstractCompilerLightClassTest.java index 59b8b6d1dbd..d89b6d4f1d1 100644 --- a/compiler/tests/org/jetbrains/kotlin/asJava/AbstractCompilerLightClassTest.java +++ b/compiler/tests/org/jetbrains/kotlin/asJava/AbstractCompilerLightClassTest.java @@ -52,7 +52,8 @@ public abstract class AbstractCompilerLightClassTest extends KotlinMultiFileTest @Override protected void doMultiFileTest(File file, Map modules, List files) throws IOException { - LightClassTestCommon.INSTANCE.testLightClass(file, s -> { + File expectedFile = KotlinTestUtils.replaceExtension(file, "java"); + LightClassTestCommon.INSTANCE.testLightClass(expectedFile, file, s -> { try { return createFinder(getEnvironment()).findClass(s, GlobalSearchScope.allScope(getEnvironment().getProject())); } diff --git a/compiler/tests/org/jetbrains/kotlin/asJava/CompilerLightClassTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/asJava/CompilerLightClassTestGenerated.java index 50a15907750..fb200b945e5 100644 --- a/compiler/tests/org/jetbrains/kotlin/asJava/CompilerLightClassTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/asJava/CompilerLightClassTestGenerated.java @@ -66,12 +66,24 @@ public class CompilerLightClassTestGenerated extends AbstractCompilerLightClassT doTest(fileName); } + @TestMetadata("ExtendingInterfaceWithDefaultImpls.kt") + public void testExtendingInterfaceWithDefaultImpls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.kt"); + doTest(fileName); + } + @TestMetadata("HiddenDeprecated.kt") public void testHiddenDeprecated() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/HiddenDeprecated.kt"); doTest(fileName); } + @TestMetadata("InheritingInterfaceDefaultImpls.kt") + public void testInheritingInterfaceDefaultImpls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.kt"); + doTest(fileName); + } + @TestMetadata("JvmNameOnMember.kt") public void testJvmNameOnMember() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/JvmNameOnMember.kt"); diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/java/UnimplementedKotlinInterfaceMemberAnnotator.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/java/UnimplementedKotlinInterfaceMemberAnnotator.kt new file mode 100644 index 00000000000..24a15f3c7a7 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/java/UnimplementedKotlinInterfaceMemberAnnotator.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.java + +import com.intellij.codeInsight.ClassUtil.getAnyMethodToImplement +import com.intellij.codeInsight.daemon.JavaErrorMessages +import com.intellij.codeInsight.daemon.impl.analysis.HighlightNamesUtil.getClassDeclarationTextRange +import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil +import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil +import com.intellij.codeInsight.intention.QuickFixFactory +import com.intellij.lang.annotation.Annotation +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.lang.annotation.Annotator +import com.intellij.psi.* +import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.utils.ifEmpty + +class UnimplementedKotlinInterfaceMemberAnnotator : Annotator { + override fun annotate(element: PsiElement, holder: AnnotationHolder) { + if (element !is PsiClass || element.language == KotlinLanguage.INSTANCE) return + + if (element.isInterface || element.hasModifierProperty(PsiModifier.ABSTRACT)) return + + if (getAnyMethodToImplement(element) != null) return // reported by java default annotator + + findUnimplementedMethod(element)?.let { + report(it, holder, element) + } + + } + + private fun findUnimplementedMethod(psiClass: PsiClass): KtLightMethod? { + val signaturesFromKotlinInterfaces = psiClass.visibleSignatures.filter { signature -> + signature.method.let { it is KtLightMethod && it.hasModifierProperty(PsiModifier.DEFAULT) } + }.ifEmpty { return null } + + val kotlinSuperClass = generateSequence(psiClass) { it.superClass }.firstOrNull { it is KtLightClassForSourceDeclaration } + ?: return signaturesFromKotlinInterfaces.first().method as? KtLightMethod + + val signaturesVisibleThroughKotlinSuperClass = kotlinSuperClass.visibleSignatures + return signaturesFromKotlinInterfaces.firstOrNull { it !in signaturesVisibleThroughKotlinSuperClass }?.method as? KtLightMethod + } + + private fun report(method: KtLightMethod, holder: AnnotationHolder, psiClass: PsiClass) { + val key = if (psiClass is PsiEnumConstantInitializer) "enum.constant.should.implement.method" else "class.must.be.abstract" + val message = JavaErrorMessages.message(key, HighlightUtil.formatClass(psiClass, false), JavaHighlightUtil.formatMethod(method), + HighlightUtil.formatClass(method.containingClass, false)) + val errorAnnotation = holder.createErrorAnnotation(getClassDeclarationTextRange(psiClass), message) + registerFixes(errorAnnotation, psiClass) + } + + private fun registerFixes(errorAnnotation: Annotation, psiClass: PsiClass) { + val quickFixFactory = QuickFixFactory.getInstance() + // this code is untested + // see com.intellij.codeInsight.daemon.impl.analysis.HighlightClassUtil.checkClassWithAbstractMethods + errorAnnotation.registerFix(quickFixFactory.createImplementMethodsFix(psiClass)) + if (psiClass !is PsiAnonymousClass && !(psiClass.modifierList?.hasExplicitModifier(PsiModifier.FINAL) ?: false)) { + errorAnnotation.registerFix(quickFixFactory.createModifierListFix(psiClass, PsiModifier.ABSTRACT, true, false)) + } + } +} \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 5a2e5562969..7c6c492fa53 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -516,6 +516,8 @@ + + diff --git a/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverrideToStringInTrait.kt b/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverrideToStringInTrait.kt index 38caac214df..00e46872938 100644 --- a/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverrideToStringInTrait.kt +++ b/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverrideToStringInTrait.kt @@ -1,5 +1,5 @@ interface A { - override fun toString() = "A" + override fun toString() = "A" } abstract class B : A diff --git a/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverridesForTraitFunWithImpl.kt b/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverridesForTraitFunWithImpl.kt index cb48b5d7041..a98a4db45cf 100644 --- a/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverridesForTraitFunWithImpl.kt +++ b/idea/testData/codeInsight/lineMarker/overrideImplement/FakeOverridesForTraitFunWithImpl.kt @@ -1,11 +1,9 @@ interface A { - fun foo(): String = "A" + fun foo(): String = "A" - // TODO: B shoudn't be mentioned - val some: String? get() = null + val some: String? get() = null - // TODO: B shoudn't be mentioned - var other: String? + var other: String? get() = null set(value) {} } diff --git a/idea/testData/codeInsight/lineMarker/overrideImplement/OverrideIconForOverloadMethodBug.kt b/idea/testData/codeInsight/lineMarker/overrideImplement/OverrideIconForOverloadMethodBug.kt index a5f80817c1e..5f33a73a3fb 100644 --- a/idea/testData/codeInsight/lineMarker/overrideImplement/OverrideIconForOverloadMethodBug.kt +++ b/idea/testData/codeInsight/lineMarker/overrideImplement/OverrideIconForOverloadMethodBug.kt @@ -4,8 +4,7 @@ interface SkipSupport { } public interface SkipSupportWithDefaults : SkipSupport { - // TODO: should be "Is overriden in SkipSupportImpl" - override fun skip(why: String) {} + override fun skip(why: String) {} override fun skip() { skip("not given") diff --git a/idea/testData/codeInsight/lineMarker/overrideImplement/OverridingTooltipOnDefaultTraitImpl.kt b/idea/testData/codeInsight/lineMarker/overrideImplement/OverridingTooltipOnDefaultTraitImpl.kt index e50b324e27e..2a50fe34143 100644 --- a/idea/testData/codeInsight/lineMarker/overrideImplement/OverridingTooltipOnDefaultTraitImpl.kt +++ b/idea/testData/codeInsight/lineMarker/overrideImplement/OverridingTooltipOnDefaultTraitImpl.kt @@ -3,7 +3,7 @@ interface SkipSupport { } public interface SkipSupportWithDefaults : SkipSupport { - override fun skip() {} + override fun skip() {} } public interface SkipSupportImpl1 : SkipSupportWithDefaults { diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.java b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.java new file mode 100644 index 00000000000..6a7a3c103ec --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.java @@ -0,0 +1,39 @@ +package test; + +public class ExtendClassWithDefaultImplementationComplext { + public static class Test1 implements A { + + } + + public static class Test2 extends AI implements A { + + } + + public static class Test3 extends AI implements B { + + } + + public static class Test4 extends BI implements B { + + } + + public static class Test5 extends BI implements C { + + } + + public static class Test6 extends BI implements D { + + } + + public static class Test7 extends BI implements S { + + } + + public static interface Test8 extends A { + + } + + public static abstract class Test9 implements A { + + } +} diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.kt b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.kt new file mode 100644 index 00000000000..ba4dff26cae --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.kt @@ -0,0 +1,26 @@ +package test + +interface A { + fun a() = Unit +} + +open class AI : A + +interface B : A { + fun b() = Unit +} + +open class BI: B + +interface C: B { + fun c() = Unit +} + +interface D { + fun d() = Unit +} + +interface S { + fun a() = Unit + fun b() = Unit +} \ No newline at end of file diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.java b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.java new file mode 100644 index 00000000000..42d3d4b29d3 --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.java @@ -0,0 +1,14 @@ +package test; + +public class ExtendClassWithDefaultImplementation_1_6 { + public static class ExtendClass extends KotlinClass { + + } + + public static class ImplementInterface implements KotlinInterface { + @Override + public void f() { + + } + } +} diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.kt b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.kt new file mode 100644 index 00000000000..c1fc147d957 --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.kt @@ -0,0 +1,20 @@ +package test + +interface KotlinInterface { + fun foo() { + + } + + fun bar() { + + } + + fun f() +} + + +abstract class KotlinClass : KotlinInterface { + override fun f() { + + } +} \ No newline at end of file diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.txt b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.txt new file mode 100644 index 00000000000..9b64cffb2d1 --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.txt @@ -0,0 +1 @@ +// LANGUAGE_LEVEL 1.6 \ No newline at end of file diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.java b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.java new file mode 100644 index 00000000000..1d048adb61f --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.java @@ -0,0 +1,14 @@ +package test; + +public class ExtendClassWithDefaultImplementation_1_8 { + public static class ExtendClass extends KotlinClass { + + } + + public static class ImplementInterface implements KotlinInterface { + @Override + public void f() { + + } + } +} diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.kt b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.kt new file mode 100644 index 00000000000..c1fc147d957 --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.kt @@ -0,0 +1,20 @@ +package test + +interface KotlinInterface { + fun foo() { + + } + + fun bar() { + + } + + fun f() +} + + +abstract class KotlinClass : KotlinInterface { + override fun f() { + + } +} \ No newline at end of file diff --git a/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.txt b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.txt new file mode 100644 index 00000000000..16770180714 --- /dev/null +++ b/idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.txt @@ -0,0 +1 @@ +// LANGUAGE_LEVEL 1.8 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinBinariesCheckerTestGenerated.java b/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinBinariesCheckerTestGenerated.java index d32cd9361a8..ee2e1db92a5 100644 --- a/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinBinariesCheckerTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinBinariesCheckerTestGenerated.java @@ -72,6 +72,24 @@ public class JavaAgainstKotlinBinariesCheckerTestGenerated extends AbstractJavaA doTest(fileName); } + @TestMetadata("ExtendClassWithDefaultImplementationComplex.kt") + public void testExtendClassWithDefaultImplementationComplex() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.kt"); + doTest(fileName); + } + + @TestMetadata("ExtendClassWithDefaultImplementation_1_6.kt") + public void testExtendClassWithDefaultImplementation_1_6() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.kt"); + doTest(fileName); + } + + @TestMetadata("ExtendClassWithDefaultImplementation_1_8.kt") + public void testExtendClassWithDefaultImplementation_1_8() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.kt"); + doTest(fileName); + } + @TestMetadata("FunctionInNestedClassInDataFlowInspection.kt") public void testFunctionInNestedClassInDataFlowInspection() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/FunctionInNestedClassInDataFlowInspection.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinSourceCheckerTestGenerated.java b/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinSourceCheckerTestGenerated.java index bbb792dcc22..c4784fba6d3 100644 --- a/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinSourceCheckerTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/checkers/JavaAgainstKotlinSourceCheckerTestGenerated.java @@ -74,6 +74,24 @@ public class JavaAgainstKotlinSourceCheckerTestGenerated extends AbstractJavaAga doTest(fileName); } + @TestMetadata("ExtendClassWithDefaultImplementationComplex.kt") + public void testExtendClassWithDefaultImplementationComplex() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementationComplex.kt"); + doTest(fileName); + } + + @TestMetadata("ExtendClassWithDefaultImplementation_1_6.kt") + public void testExtendClassWithDefaultImplementation_1_6() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_6.kt"); + doTest(fileName); + } + + @TestMetadata("ExtendClassWithDefaultImplementation_1_8.kt") + public void testExtendClassWithDefaultImplementation_1_8() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/ExtendClassWithDefaultImplementation_1_8.kt"); + doTest(fileName); + } + @TestMetadata("FunctionInNestedClassInDataFlowInspection.kt") public void testFunctionInNestedClassInDataFlowInspection() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kotlinAndJavaChecker/javaAgainstKotlin/FunctionInNestedClassInDataFlowInspection.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractIdeLightClassTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractIdeLightClassTest.kt index 3852cc3e9f6..312b1e6693a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractIdeLightClassTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractIdeLightClassTest.kt @@ -56,7 +56,8 @@ abstract class AbstractIdeLightClassTest : KotlinLightCodeInsightFixtureTestCase myFixture.configureByFiles(*testFiles.toTypedArray()) val ktFile = myFixture.file as KtFile - testLightClass(testDataPath, { LightClassTestCommon.removeEmptyDefaultImpls(it) }) { fqName -> + val testData = File(testDataPath) + testLightClass(KotlinTestUtils.replaceExtension(testData, "java"), testData, { LightClassTestCommon.removeEmptyDefaultImpls(it) }, { fqName -> val tracker = LightClassLazinessChecker.Tracker(fqName) project.withServiceRegistered(tracker) { findClass(fqName, ktFile, project)?.apply { @@ -65,7 +66,7 @@ abstract class AbstractIdeLightClassTest : KotlinLightCodeInsightFixtureTestCase PsiElementChecker.checkPsiElementStructure(this) } } - } + }) } private fun lazinessModeByFileText(testDataPath: String): LightClassLazinessChecker.Mode { @@ -97,17 +98,22 @@ abstract class AbstractIdeCompiledLightClassTest : KotlinDaemonAnalyzerTestCase( private fun libName() = "libFor" + getTestName(false) fun doTest(testDataPath: String) { - testLightClass(testDataPath, { it }) { + val testDataFile = File(testDataPath) + val expectedFile = KotlinTestUtils.replaceExtension( + testDataFile, "compiled.java" + ).let { if (it.exists()) it else KotlinTestUtils.replaceExtension(testDataFile, "java") } + testLightClass(expectedFile, testDataFile, { it }, { findClass(it, null, project)?.apply { PsiElementChecker.checkPsiElementStructure(this) } - } + }) } } -private fun testLightClass(testDataPath: String, normalize: (String) -> String, findLightClass: (String) -> PsiClass?) { +private fun testLightClass(expected: File, testData: File, normalize: (String) -> String, findLightClass: (String) -> PsiClass?) { LightClassTestCommon.testLightClass( - File(testDataPath), + expected, + testData, findLightClass, normalizeText = { text -> //NOTE: ide and compiler differ in names generated for parameters with unspecified names @@ -116,6 +122,7 @@ private fun testLightClass(testDataPath: String, normalize: (String) -> String, .replace("java.lang.String s)", "java.lang.String p)") .replace("java.lang.String s1", "java.lang.String p1") .replace("java.lang.String s2", "java.lang.String p2") + .replace("java.lang.Object o)", "java.lang.Object p)") .removeLinesStartingWith("@" + JvmAnnotationNames.METADATA_FQ_NAME.asString()) .run(normalize) } @@ -243,11 +250,18 @@ object LightClassLazinessChecker { private fun methodInfo(method: PsiMethod) = with(method) { MethodInfo( - name, PsiModifier.MODIFIERS.asList().filter { modifierList.hasModifierProperty(it) }, + name, relevantModifiers(), isConstructor, method.parameterList.parametersCount, isVarArgs ) } + private fun PsiMethod.relevantModifiers() + = when { containingClass!!.isInterface -> + PsiModifier.MODIFIERS.filter { it != PsiModifier.ABSTRACT && it != PsiModifier.DEFAULT } + else -> PsiModifier.MODIFIERS.asList() + }.filter { modifierList.hasModifierProperty(it) } + + private fun Array.names() = mapTo(LinkedHashSet()) { it.name!! } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeCompiledLightClassTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeCompiledLightClassTestGenerated.java index 35372a59fcd..9de9c49d2a2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeCompiledLightClassTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeCompiledLightClassTestGenerated.java @@ -66,12 +66,24 @@ public class IdeCompiledLightClassTestGenerated extends AbstractIdeCompiledLight doTest(fileName); } + @TestMetadata("ExtendingInterfaceWithDefaultImpls.kt") + public void testExtendingInterfaceWithDefaultImpls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.kt"); + doTest(fileName); + } + @TestMetadata("HiddenDeprecated.kt") public void testHiddenDeprecated() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/HiddenDeprecated.kt"); doTest(fileName); } + @TestMetadata("InheritingInterfaceDefaultImpls.kt") + public void testInheritingInterfaceDefaultImpls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.kt"); + doTest(fileName); + } + @TestMetadata("JvmNameOnMember.kt") public void testJvmNameOnMember() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/JvmNameOnMember.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeLightClassTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeLightClassTestGenerated.java index aa1e62c284b..d5900f4ca6a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeLightClassTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeLightClassTestGenerated.java @@ -66,12 +66,24 @@ public class IdeLightClassTestGenerated extends AbstractIdeLightClassTest { doTest(fileName); } + @TestMetadata("ExtendingInterfaceWithDefaultImpls.kt") + public void testExtendingInterfaceWithDefaultImpls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/ExtendingInterfaceWithDefaultImpls.kt"); + doTest(fileName); + } + @TestMetadata("HiddenDeprecated.kt") public void testHiddenDeprecated() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/HiddenDeprecated.kt"); doTest(fileName); } + @TestMetadata("InheritingInterfaceDefaultImpls.kt") + public void testInheritingInterfaceDefaultImpls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/InheritingInterfaceDefaultImpls.kt"); + doTest(fileName); + } + @TestMetadata("JvmNameOnMember.kt") public void testJvmNameOnMember() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/JvmNameOnMember.kt"); diff --git a/plugins/annotation-processing/testData/wrappers/kotlinWrappers/nestedClasses.txt b/plugins/annotation-processing/testData/wrappers/kotlinWrappers/nestedClasses.txt index eec19262b77..65daf606ae5 100644 --- a/plugins/annotation-processing/testData/wrappers/kotlinWrappers/nestedClasses.txt +++ b/plugins/annotation-processing/testData/wrappers/kotlinWrappers/nestedClasses.txt @@ -44,7 +44,7 @@ public final class MyClass { public static abstract interface InnerInterface { public abstract void innerInterfaceFun() - public abstract int innerInterfaceFunWithImpl() + public default int innerInterfaceFunWithImpl() public static final class DefaultImpls { public static int innerInterfaceFunWithImpl(MyClass.InnerInterface $this) diff --git a/plugins/uast-kotlin/testData/DefaultImpls.render.txt b/plugins/uast-kotlin/testData/DefaultImpls.render.txt index 1e912f7b081..347816e5e07 100644 --- a/plugins/uast-kotlin/testData/DefaultImpls.render.txt +++ b/plugins/uast-kotlin/testData/DefaultImpls.render.txt @@ -1,5 +1,5 @@ public abstract interface Foo { - public abstract fun bar() : java.lang.String = "Hello!" + public default fun bar() : java.lang.String = "Hello!" } public final class Baz : Foo {