From 04c190231ad844858d4e072036a082fef99f35a0 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 30 May 2016 12:34:43 +0300 Subject: [PATCH] Support bound references to objects and companion objects Tweak member extension detection a little bit to prohibit extensions imported from objects (they don't have one of the receiver parameters) --- .../jetbrains/kotlin/diagnostics/Errors.java | 1 - .../rendering/DefaultErrorMessages.java | 3 +- .../CallableReferencesResolutionUtils.kt | 19 +++++---- .../DoubleColonExpressionResolver.kt | 30 +++++++++----- .../bound/companionObject.kt | 39 +++++++++++++++++++ .../bound/companionObject.txt | 22 +++++++++++ .../tests/callableReference/bound/object.kt | 11 ++++++ .../tests/callableReference/bound/object.txt | 12 ++++++ ...xtensionsImportedFromObjectsUnsupported.kt | 26 +++++++++++++ ...ensionsImportedFromObjectsUnsupported.txt} | 4 -- .../objectMembersUnsupported.kt | 31 --------------- .../checkers/DiagnosticsTestGenerated.java | 27 +++++++++++-- 12 files changed, 167 insertions(+), 58 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/callableReference/bound/companionObject.kt create mode 100644 compiler/testData/diagnostics/tests/callableReference/bound/companionObject.txt create mode 100644 compiler/testData/diagnostics/tests/callableReference/bound/object.kt create mode 100644 compiler/testData/diagnostics/tests/callableReference/bound/object.txt create mode 100644 compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.kt rename compiler/testData/diagnostics/tests/callableReference/{objectMembersUnsupported.txt => memberExtensionsImportedFromObjectsUnsupported.txt} (86%) delete mode 100644 compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 0e6e044bb3f..7d76fa3eefa 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -549,7 +549,6 @@ public interface Errors { DiagnosticFactory1 EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 CALLABLE_REFERENCE_LHS_NOT_A_CLASS = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CALLABLE_REFERENCE_TO_MEMBER_OR_EXTENSION_WITH_EMPTY_LHS = DiagnosticFactory0.create(ERROR); - DiagnosticFactory0 CALLABLE_REFERENCE_TO_OBJECT_MEMBER = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CLASS_LITERAL_LHS_NOT_A_CLASS = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 32ae168ddc0..6a4bdec2da9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -40,7 +40,7 @@ import java.util.List; import static org.jetbrains.kotlin.diagnostics.Errors.*; import static org.jetbrains.kotlin.diagnostics.rendering.Renderers.*; -import static org.jetbrains.kotlin.diagnostics.rendering.RenderingContext.*; +import static org.jetbrains.kotlin.diagnostics.rendering.RenderingContext.of; public class DefaultErrorMessages { @@ -733,7 +733,6 @@ public class DefaultErrorMessages { MAP.put(CALLABLE_REFERENCE_TO_MEMBER_OR_EXTENSION_WITH_EMPTY_LHS, "Left-hand side of a callable reference with a receiver parameter cannot be empty. " + "Please specify the type of the receiver before '::' explicitly"); - MAP.put(CALLABLE_REFERENCE_TO_OBJECT_MEMBER, "Callable references to object members are not supported"); MAP.put(CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR, "Annotation class cannot be instantiated"); MAP.put(CLASS_LITERAL_LHS_NOT_A_CLASS, "Only classes are allowed on the left hand side of a class literal"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/callableReferences/CallableReferencesResolutionUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/callableReferences/CallableReferencesResolutionUtils.kt index 7f2ff2b4e41..9bbb6760067 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/callableReferences/CallableReferencesResolutionUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/callableReferences/CallableReferencesResolutionUtils.kt @@ -184,6 +184,13 @@ private fun createReflectionTypeForCallableDescriptor( reportOn: KtExpression?, ignoreReceiver: Boolean ): KotlinType? { + if (descriptor is CallableMemberDescriptor && isMemberExtension(descriptor)) { + if (reportOn != null) { + trace?.report(EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED.on(reportOn, descriptor)) + } + return null + } + val extensionReceiver = descriptor.extensionReceiverParameter val dispatchReceiver = descriptor.dispatchReceiverParameter?.let { dispatchReceiver -> // See CallableDescriptor#getOwnerForEffectiveDispatchReceiverParameter @@ -192,13 +199,6 @@ private fun createReflectionTypeForCallableDescriptor( else dispatchReceiver } - if (extensionReceiver != null && dispatchReceiver != null && descriptor is CallableMemberDescriptor) { - if (reportOn != null) { - trace?.report(EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED.on(reportOn, descriptor)) - } - return null - } - val receiverType = if ((extensionReceiver != null || dispatchReceiver != null) && !ignoreReceiver) lhsType ?: extensionReceiver?.type ?: dispatchReceiver?.type @@ -224,6 +224,11 @@ private fun createReflectionTypeForCallableDescriptor( } } +private fun isMemberExtension(descriptor: CallableMemberDescriptor): Boolean { + val original = (descriptor as? ImportedFromObjectCallableDescriptor<*>)?.callableFromObject ?: descriptor + return original.extensionReceiverParameter != null && original.dispatchReceiverParameter != null +} + fun getReflectionTypeForCandidateDescriptor( descriptor: CallableDescriptor, reflectionTypes: ReflectionTypes, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt index 2ce9d08b0ac..d4675bf8c0e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt @@ -27,14 +27,17 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.callableReferences.createReflectionTypeForResolvedCallableReference import org.jetbrains.kotlin.resolve.callableReferences.resolvePossiblyAmbiguousCallableReference import org.jetbrains.kotlin.resolve.calls.CallResolver import org.jetbrains.kotlin.resolve.calls.callResolverUtil.ResolveArgumentsMode +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil +import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinTypeImpl @@ -138,11 +141,22 @@ class DoubleColonExpressionResolver( val contextForExpr = c.replaceTraceAndCache(traceForExpr) val typeInfo = expressionTypingServices.getTypeInfo(expression, contextForExpr) val type = typeInfo.type - // TODO (!!!): it's wrong to only check type, should check that there's a companion qualifier - if (type != null && !DescriptorUtils.isCompanionObject(type.constructor.declarationDescriptor)) { - traceForExpr.commit() - return DoubleColonLHS.Expression(typeInfo).apply { - c.trace.record(BindingContext.DOUBLE_COLON_LHS, expression, this) + + if (type != null) { + val call = traceForExpr.trace.bindingContext[BindingContext.CALL, expression.getQualifiedElementSelector()] + val resolvedCall = call.getResolvedCall(traceForExpr.trace.bindingContext) + val implicitReferenceToCompanion = + resolvedCall != null && + resolvedCall.resultingDescriptor.let { result -> + result is FakeCallableDescriptorForObject && + result.classDescriptor.companionObjectDescriptor != null + } + + if (!implicitReferenceToCompanion) { + traceForExpr.commit() + return DoubleColonLHS.Expression(typeInfo).apply { + c.trace.record(BindingContext.DOUBLE_COLON_LHS, expression, this) + } } } } @@ -248,11 +262,7 @@ class DoubleColonExpressionResolver( context.trace.report(CALLABLE_REFERENCE_TO_MEMBER_OR_EXTENSION_WITH_EMPTY_LHS.on(reference)) } - val containingDeclaration = descriptor.containingDeclaration - if (DescriptorUtils.isObject(containingDeclaration)) { - context.trace.report(CALLABLE_REFERENCE_TO_OBJECT_MEMBER.on(reference)) - } - if (descriptor is ConstructorDescriptor && DescriptorUtils.isAnnotationClass(containingDeclaration)) { + if (descriptor is ConstructorDescriptor && DescriptorUtils.isAnnotationClass(descriptor.containingDeclaration)) { context.trace.report(CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR.on(reference)) } diff --git a/compiler/testData/diagnostics/tests/callableReference/bound/companionObject.kt b/compiler/testData/diagnostics/tests/callableReference/bound/companionObject.kt new file mode 100644 index 00000000000..d053ab877fa --- /dev/null +++ b/compiler/testData/diagnostics/tests/callableReference/bound/companionObject.kt @@ -0,0 +1,39 @@ +// !CHECK_TYPE +// !DIAGNOSTICS: -UNUSED_EXPRESSION + +package test + +class C { + companion object { + fun foo(): String = "companion" + fun bar() {} + } + + fun foo(): Int = 0 +} + +fun test() { + val r1 = C::foo + checkSubtype<(C) -> Int>(r1) + + val r2 = test.C::foo + checkSubtype<(C) -> Int>(r2) + + val r3 = C.Companion::foo + checkSubtype<() -> String>(r3) + + val r4 = test.C.Companion::foo + checkSubtype<() -> String>(r4) + + val r5 = (C)::foo + checkSubtype<() -> String>(r5) + + val r6 = (test.C)::foo + checkSubtype<() -> String>(r6) + + val c = C.Companion + val r7 = c::foo + checkSubtype<() -> String>(r7) + + C::bar +} diff --git a/compiler/testData/diagnostics/tests/callableReference/bound/companionObject.txt b/compiler/testData/diagnostics/tests/callableReference/bound/companionObject.txt new file mode 100644 index 00000000000..3998c835d04 --- /dev/null +++ b/compiler/testData/diagnostics/tests/callableReference/bound/companionObject.txt @@ -0,0 +1,22 @@ +package + +package test { + public fun test(): kotlin.Unit + + public final class C { + public constructor C() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public companion object Companion { + private constructor Companion() + public final fun bar(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } +} diff --git a/compiler/testData/diagnostics/tests/callableReference/bound/object.kt b/compiler/testData/diagnostics/tests/callableReference/bound/object.kt new file mode 100644 index 00000000000..d2b6f013005 --- /dev/null +++ b/compiler/testData/diagnostics/tests/callableReference/bound/object.kt @@ -0,0 +1,11 @@ +// !CHECK_TYPE + +object Obj { + fun foo() {} + val bar = 2 +} + +fun test() { + checkSubtype<() -> Unit>(Obj::foo) + checkSubtype<() -> Int>(Obj::bar) +} diff --git a/compiler/testData/diagnostics/tests/callableReference/bound/object.txt b/compiler/testData/diagnostics/tests/callableReference/bound/object.txt new file mode 100644 index 00000000000..b0b8ba71666 --- /dev/null +++ b/compiler/testData/diagnostics/tests/callableReference/bound/object.txt @@ -0,0 +1,12 @@ +package + +public fun test(): kotlin.Unit + +public object Obj { + private constructor Obj() + public final val bar: kotlin.Int = 2 + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.kt b/compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.kt new file mode 100644 index 00000000000..86ff3db9f44 --- /dev/null +++ b/compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.kt @@ -0,0 +1,26 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION + +import Obj.ext +import A.Companion.ext2 + +object Obj { + val String.ext: String get() = this +} + +class A { + companion object { + val String.ext2: String get() = this + } +} + +fun test() { + String::ext + Obj::ext + + String::ext2 + A.Companion::ext2 + A::ext2 + + A::foo + A::bar +} diff --git a/compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.txt b/compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.txt similarity index 86% rename from compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.txt rename to compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.txt index e36b5ced850..c3593f43d2c 100644 --- a/compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.txt +++ b/compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.txt @@ -10,10 +10,8 @@ public final class A { public companion object Companion { private constructor Companion() - public final val bar: kotlin.Int = 2 public final val kotlin.String.ext2: kotlin.String public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean - public final fun foo(): kotlin.Unit public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } @@ -21,10 +19,8 @@ public final class A { public object Obj { private constructor Obj() - public final val bar: kotlin.Int = 2 public final val kotlin.String.ext: kotlin.String public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean - public final fun foo(): kotlin.Unit public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } diff --git a/compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.kt b/compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.kt deleted file mode 100644 index 4899c73d929..00000000000 --- a/compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.kt +++ /dev/null @@ -1,31 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_EXPRESSION - -import Obj.ext -import A.Companion.ext2 - -object Obj { - fun foo() {} - val bar = 2 - val String.ext: String get() = this -} - -class A { - companion object { - fun foo() {} - val bar = 2 - val String.ext2: String get() = this - } -} - -fun test() { - Obj::foo - Obj::bar - String::ext - - A.Companion::foo - A.Companion::bar - String::ext2 - - A::foo - A::bar -} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 2657360b612..294052440d3 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -1683,9 +1683,9 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } - @TestMetadata("objectMembersUnsupported.kt") - public void testObjectMembersUnsupported() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/objectMembersUnsupported.kt"); + @TestMetadata("memberExtensionsImportedFromObjectsUnsupported.kt") + public void testMemberExtensionsImportedFromObjectsUnsupported() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/memberExtensionsImportedFromObjectsUnsupported.kt"); doTest(fileName); } @@ -1713,6 +1713,27 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("compiler/testData/diagnostics/tests/callableReference/bound") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Bound extends AbstractDiagnosticsTest { + public void testAllFilesPresentInBound() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/callableReference/bound"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("companionObject.kt") + public void testCompanionObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/bound/companionObject.kt"); + doTest(fileName); + } + + @TestMetadata("object.kt") + public void testObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/callableReference/bound/object.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/callableReference/function") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)