diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 49f3f209d48..b35e0ff5d5a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -392,9 +392,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { } } else { - if (supertypes.size() > 1) { + if (UnqualifiedSuperPackage.isPossiblyAmbiguousUnqualifiedSuper(expression, supertypes)) { Collection supertypesResolvedFromContext = - UnqualifiedSuperPackage.resolveUnqualifiedSuperFromExpressionContext(expression, supertypes); + UnqualifiedSuperPackage.resolveUnqualifiedSuperFromExpressionContext( + expression, supertypes, components.builtIns.getAnyType()); if (supertypesResolvedFromContext.size() == 1) { JetType singleResolvedType = supertypesResolvedFromContext.iterator().next(); result = substitutor.substitute(singleResolvedType, Variance.INVARIANT); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/unqualifiedSuper/unqualifiedSuper.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/unqualifiedSuper/unqualifiedSuper.kt index 8863ff2d1a9..420f2f8732e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/unqualifiedSuper/unqualifiedSuper.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/unqualifiedSuper/unqualifiedSuper.kt @@ -17,33 +17,47 @@ package org.jetbrains.kotlin.types.expressions.unqualifiedSuper import com.intellij.util.SmartList +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.MemberDescriptor import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.JetCallExpression import org.jetbrains.kotlin.psi.JetDotQualifiedExpression import org.jetbrains.kotlin.psi.JetSimpleNameExpression import org.jetbrains.kotlin.psi.JetSuperExpression import org.jetbrains.kotlin.types.JetType +import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.utils.addToStdlib.singletonList -import java.util.* -public fun resolveUnqualifiedSuperFromExpressionContext(superExpression: JetSuperExpression, supertypes: Collection): Collection { - val parentElement = superExpression.getParent() + +public fun resolveUnqualifiedSuperFromExpressionContext( + superExpression: JetSuperExpression, + supertypes: Collection, + anyType: JetType +): Collection { + val parentElement = superExpression.parent if (parentElement is JetDotQualifiedExpression) { - val selectorExpression = parentElement.getSelectorExpression() + val selectorExpression = parentElement.selectorExpression when (selectorExpression) { is JetCallExpression -> { // super.foo(...): foo can be a function or a property of a callable type - val calleeExpression = selectorExpression.getCalleeExpression() + val calleeExpression = selectorExpression.calleeExpression if (calleeExpression is JetSimpleNameExpression) { - return resolveSupertypesByCalleeName(supertypes, calleeExpression.getReferencedNameAsName()) + val calleeName = calleeExpression.getReferencedNameAsName() + if (isCallingMethodOfAny(selectorExpression, calleeName)) { + return resolveSupertypesForMethodOfAny(supertypes, calleeName, anyType) + } + else { + return resolveSupertypesByCalleeName(supertypes, calleeName) + } } } is JetSimpleNameExpression -> { // super.x: x can be a property only + // NB there are no properties in kotlin.Any return resolveSupertypesByPropertyName(supertypes, selectorExpression.getReferencedNameAsName()) } } @@ -52,37 +66,92 @@ public fun resolveUnqualifiedSuperFromExpressionContext(superExpression: JetSupe return emptyList() } -private fun resolveSupertypesByCalleeName(supertypes: Collection, calleeName: Name): Collection = - resolveSupertypesByMembers(supertypes) { getFunctionMembers(it, calleeName) + getPropertyMembers(it, calleeName) } +private val ARITY_OF_METHODS_OF_ANY = hashMapOf("hashCode" to 0, "equals" to 1, "toString" to 0) -private fun resolveSupertypesByPropertyName(supertypes: Collection, propertyName: Name): Collection = - resolveSupertypesByMembers(supertypes) { getPropertyMembers(it, propertyName) } +private fun isCallingMethodOfAny(callExpression: JetCallExpression, calleeName: Name): Boolean = + ARITY_OF_METHODS_OF_ANY.getOrElse(calleeName.asString(), { -1 }) == callExpression.valueArguments.size() -private inline fun resolveSupertypesByMembers( - supertypes: Collection, - getMembers: (JetType) -> Collection -): Collection { - val withConcreteMembers = SmartList() - val withAnyMembers = SmartList() +public fun isPossiblyAmbiguousUnqualifiedSuper(superExpression: JetSuperExpression, supertypes: Collection): Boolean = + supertypes.size() > 1 || + (supertypes.size() == 1 && supertypes.single().isInterface() && isCallingMethodOfAnyWithSuper(superExpression)) - for (supertype in supertypes) { - val members = getMembers(supertype) - if (members.isNotEmpty()) { - withAnyMembers.add(supertype) - if (members any ::isConcreteMember) { - withConcreteMembers.add(supertype) +private fun isCallingMethodOfAnyWithSuper(superExpression: JetSuperExpression): Boolean { + val parent = superExpression.parent + if (parent is JetDotQualifiedExpression) { + val selectorExpression = parent.selectorExpression + if (selectorExpression is JetCallExpression) { + val calleeExpression = selectorExpression.calleeExpression + if (calleeExpression is JetSimpleNameExpression) { + val calleeName = calleeExpression.getReferencedNameAsName() + return isCallingMethodOfAny(selectorExpression, calleeName) } } } - return if (withConcreteMembers.isNotEmpty()) withConcreteMembers else withAnyMembers + return false +} + +private fun JetType.isInterface(): Boolean = + TypeUtils.getClassDescriptor(this)?.kind == ClassKind.INTERFACE + +private fun resolveSupertypesForMethodOfAny(supertypes: Collection, calleeName: Name, anyType: JetType): Collection { + val typesWithConcreteOverride = resolveSupertypesByMembers(supertypes, false) { getFunctionMembers(it, calleeName) } + return if (typesWithConcreteOverride.isNotEmpty()) + typesWithConcreteOverride + else + anyType.singletonList() +} + +private fun resolveSupertypesByCalleeName(supertypes: Collection, calleeName: Name): Collection = + resolveSupertypesByMembers(supertypes, true) { getFunctionMembers(it, calleeName) + getPropertyMembers(it, calleeName) } + +private fun resolveSupertypesByPropertyName(supertypes: Collection, propertyName: Name): Collection = + resolveSupertypesByMembers(supertypes, true) { getPropertyMembers(it, propertyName) } + +private inline fun resolveSupertypesByMembers( + supertypes: Collection, + allowArbitraryMembers: Boolean, + getMembers: (JetType) -> Collection +): Collection { + val typesWithConcreteMembers = SmartList() + val typesWithArbitraryMembers = SmartList() + + for (supertype in supertypes) { + val members = getMembers(supertype) + if (members.isNotEmpty()) { + typesWithArbitraryMembers.add(supertype) + if (members.any { isConcreteMember(supertype, it) }) { + typesWithConcreteMembers.add(supertype) + } + } + } + + return if (typesWithConcreteMembers.isNotEmpty()) typesWithConcreteMembers + else if (allowArbitraryMembers) typesWithArbitraryMembers + else emptyList() } private fun getFunctionMembers(type: JetType, name: Name): Collection = - type.getMemberScope().getFunctions(name) + type.memberScope.getFunctions(name) private fun getPropertyMembers(type: JetType, name: Name): Collection = - type.getMemberScope().getProperties(name).filterIsInstanceTo(SmartList()) + type.memberScope.getProperties(name).filterIsInstanceTo(SmartList()) -private fun isConcreteMember(memberDescriptor: MemberDescriptor): Boolean = - memberDescriptor.getModality() != Modality.ABSTRACT +private fun isConcreteMember(supertype: JetType, memberDescriptor: MemberDescriptor): Boolean { + // "Concrete member" is a function or a property that is not abstract, + // and is not an implicit fake override for a method of Any on an interface. + + if (memberDescriptor.modality == Modality.ABSTRACT) + return false + + val classDescriptorForSupertype = TypeUtils.getClassDescriptor(supertype) + val memberKind = (memberDescriptor as CallableMemberDescriptor).kind + if (classDescriptorForSupertype?.kind == ClassKind.INTERFACE && memberKind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { + // We have a fake override on interface. It should have a dispatch receiver, which should not be Any. + val dispatchReceiverType = memberDescriptor.dispatchReceiverParameter?.type ?: return false + val dispatchReceiverClass = TypeUtils.getClassDescriptor(dispatchReceiverType) ?: return false + return !KotlinBuiltIns.isAny(dispatchReceiverClass) + } + + return true +} diff --git a/compiler/testData/codegen/box/super/unqualifiedSuperWithMethodsOfAny.kt b/compiler/testData/codegen/box/super/unqualifiedSuperWithMethodsOfAny.kt new file mode 100644 index 00000000000..90faa3a9a69 --- /dev/null +++ b/compiler/testData/codegen/box/super/unqualifiedSuperWithMethodsOfAny.kt @@ -0,0 +1,34 @@ +interface ISomething + +open class ClassWithToString { + override fun toString(): String = "C" +} + +interface IWithToString { + override fun toString(): String +} + +interface IWithDefaultToString { + override fun toString(): String = "I" +} + +class C1 : ClassWithToString(), ISomething { + override fun toString(): String = super.toString() +} + +class C2 : ClassWithToString(), IWithToString, ISomething { + override fun toString(): String = super.toString() +} + +class C3 : IWithDefaultToString, ISomething { + override fun toString(): String = super.toString() +} + +fun box(): String { + return when { + C1().toString() != "C" -> "Failed #1" + C2().toString() != "C" -> "Failed #2" + C3().toString() != "I" -> "Failed #3" + else -> "OK" + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withAmbiguousMethodOfAny.kt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withAmbiguousMethodOfAny.kt new file mode 100644 index 00000000000..e4c511db505 --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withAmbiguousMethodOfAny.kt @@ -0,0 +1,12 @@ +interface IA { + override fun toString(): String = "IA" +} + +interface IB { + override fun toString(): String = "IB" +} + +class C : IA, IB { + override fun toString(): String = + super.toString() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withAmbiguousMethodOfAny.txt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withAmbiguousMethodOfAny.txt new file mode 100644 index 00000000000..3ad1e1a8fa6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withAmbiguousMethodOfAny.txt @@ -0,0 +1,20 @@ +package + +internal final class C : IA, IB { + public constructor C() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ fun toString(): kotlin.String +} + +internal interface IA { + 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*/ fun toString(): kotlin.String +} + +internal interface IB { + 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*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyImplementedInSuperInterface.kt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyImplementedInSuperInterface.kt new file mode 100644 index 00000000000..84ef5bbc1ed --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyImplementedInSuperInterface.kt @@ -0,0 +1,12 @@ +interface A { + override fun toString(): String { + return "OK" + } +} + +interface B : A + +class C : B { + override fun toString(): String = + super.toString() +} diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyImplementedInSuperInterface.txt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyImplementedInSuperInterface.txt new file mode 100644 index 00000000000..3290bc32641 --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyImplementedInSuperInterface.txt @@ -0,0 +1,20 @@ +package + +internal 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 + public open override /*1*/ fun toString(): kotlin.String +} + +internal 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 + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal final class C : B { + public constructor C() + 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*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyOverridenInInterface.kt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyOverridenInInterface.kt new file mode 100644 index 00000000000..bb9f295dae7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyOverridenInInterface.kt @@ -0,0 +1,16 @@ +interface IWithToString { + override fun toString(): String +} + +class A : IWithToString { + // Should be Any#toString(), even though IWithToString defines an abstract toString. + override fun toString(): String = super.toString() +} + +interface IWithImplementedToString { + override fun toString(): String = "Heh" +} + +class B : IWithImplementedToString { + override fun toString(): String = super.toString() +} diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyOverridenInInterface.txt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyOverridenInInterface.txt new file mode 100644 index 00000000000..fc5af255788 --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyOverridenInInterface.txt @@ -0,0 +1,27 @@ +package + +internal final class A : IWithToString { + public constructor 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 + public open override /*1*/ fun toString(): kotlin.String +} + +internal final class B : IWithImplementedToString { + public constructor B() + 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*/ fun toString(): kotlin.String +} + +internal interface IWithImplementedToString { + 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*/ fun toString(): kotlin.String +} + +internal interface IWithToString { + 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 abstract override /*1*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodsOfAny.kt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodsOfAny.kt new file mode 100644 index 00000000000..e61f5a4f19a --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodsOfAny.kt @@ -0,0 +1,10 @@ +interface IFoo + +interface IBar + +class A : IFoo, IBar { + // Unqualified 'super' should be resolved to 'Any'. + override fun equals(other: Any?): Boolean = super.equals(other) + override fun hashCode(): Int = super.hashCode() + override fun toString(): String = super.toString() +} diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodsOfAny.txt b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodsOfAny.txt new file mode 100644 index 00000000000..14a5324197b --- /dev/null +++ b/compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodsOfAny.txt @@ -0,0 +1,20 @@ +package + +internal final class A : IFoo, IBar { + public constructor A() + public open override /*2*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ fun hashCode(): kotlin.Int + public open override /*2*/ fun toString(): kotlin.String +} + +internal interface IBar { + 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 +} + +internal interface IFoo { + 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 +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 5785310daf2..59e90d2a301 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -14584,6 +14584,30 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/unqualifiedSuperWithUnresolvedBase.kt"); doTest(fileName); } + + @TestMetadata("withAmbiguousMethodOfAny.kt") + public void testWithAmbiguousMethodOfAny() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withAmbiguousMethodOfAny.kt"); + doTest(fileName); + } + + @TestMetadata("withMethodOfAnyImplementedInSuperInterface.kt") + public void testWithMethodOfAnyImplementedInSuperInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyImplementedInSuperInterface.kt"); + doTest(fileName); + } + + @TestMetadata("withMethodOfAnyOverridenInInterface.kt") + public void testWithMethodOfAnyOverridenInInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodOfAnyOverridenInInterface.kt"); + doTest(fileName); + } + + @TestMetadata("withMethodsOfAny.kt") + public void testWithMethodsOfAny() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/thisAndSuper/unqualifiedSuper/withMethodsOfAny.kt"); + doTest(fileName); + } } } diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java index c0279473869..04c4ed5eec1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -7185,6 +7185,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/super/unqualifiedSuperWithLocalClass.kt"); doTest(fileName); } + + @TestMetadata("unqualifiedSuperWithMethodsOfAny.kt") + public void testUnqualifiedSuperWithMethodsOfAny() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/super/unqualifiedSuperWithMethodsOfAny.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/superConstructorCall")