From d5fdfcb797ff594247264bb41695e6bebb51e803 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 24 Nov 2014 18:00:20 +0300 Subject: [PATCH] Resolve `dynamic.extensionForDynamic()` statically --- .../resolve/calls/tasks/TaskPrioritizer.java | 38 +++++++++--- .../lang/resolve/calls/tasks/dynamicCalls.kt | 15 +++++ .../tests/dynamicTypes/extensionVals.kt | 13 +++- .../tests/dynamicTypes/extensionVals.txt | 2 + .../tests/dynamicTypes/extensionVars.kt | 4 +- .../tests/dynamicTypes/extensions.kt | 4 +- .../tests/dynamicTypes/extensionsToDynamic.kt | 61 +++++++++++++++++++ .../dynamicTypes/extensionsToDynamic.txt | 18 ++++++ .../dynamicTypes/overloadingAmbiguity.kt | 14 +++++ .../dynamicTypes/overloadingAmbiguity.txt | 5 ++ .../checkers/JetDiagnosticsTestGenerated.java | 12 ++++ spec-docs/dynamic-types.md | 8 +-- 12 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.kt create mode 100644 compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.txt create mode 100644 compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.kt create mode 100644 compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.txt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java index 10f7dc83c53..667f73fce09 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java @@ -137,15 +137,24 @@ public class TaskPrioritizer { } private static void addCandidatesForExplicitReceiver( + @NotNull ReceiverValue explicitReceiver, + @NotNull Collection implicitReceivers, + @NotNull TaskPrioritizerContext c, + boolean isExplicit + ) { + addMembers(explicitReceiver, c, /*static=*/false, isExplicit); + + addCandidatesForDynamicReceiver(explicitReceiver, implicitReceivers, c, isExplicit); + + addExtensionCandidates(explicitReceiver, implicitReceivers, c, isExplicit); + } + + private static void addExtensionCandidates( @NotNull final ReceiverValue explicitReceiver, @NotNull Collection implicitReceivers, @NotNull final TaskPrioritizerContext c, final boolean isExplicit ) { - addMembers(explicitReceiver, c, /*static=*/false, isExplicit); - - addCandidatesForDynamicReceiver(explicitReceiver, c); - for (final CallableDescriptorCollector callableDescriptorCollector : c.callableDescriptorCollectors) { //member extensions for (ReceiverValue implicitReceiver : implicitReceivers) { @@ -191,14 +200,21 @@ public class TaskPrioritizer { } private static void addCandidatesForDynamicReceiver( - @NotNull final ReceiverValue receiver, - @NotNull final TaskPrioritizerContext c + @NotNull final ReceiverValue explicitReceiver, + @NotNull Collection implicitReceivers, + @NotNull final TaskPrioritizerContext c, + boolean isExplicit ) { + if (!TypesPackage.isDynamic(explicitReceiver.getType())) return; + + TaskPrioritizerContext onlyDynamicReceivers = c.replaceCollectors(TasksPackage.onlyDynamicReceivers(c.callableDescriptorCollectors)); + addExtensionCandidates(explicitReceiver, implicitReceivers, onlyDynamicReceivers, isExplicit); + + c.result.addCandidates( new Function0>>() { @Override public Collection> invoke() { - if (!TypesPackage.isDynamic(receiver.getType())) return Collections.emptyList(); //noinspection unchecked D dynamicDescriptor = (D) DynamicCallableDescriptors.createCallableDescriptorForDynamicCall( @@ -211,7 +227,7 @@ public class TaskPrioritizer { c.context.call, dynamicDescriptor ); - dynamicCandidate.setDispatchReceiver(receiver); + dynamicCandidate.setDispatchReceiver(explicitReceiver); dynamicCandidate.setExplicitReceiverKind(DISPATCH_RECEIVER); return Collections.singletonList(dynamicCandidate); @@ -473,5 +489,11 @@ public class TaskPrioritizer { private TaskPrioritizerContext replaceScope(JetScope newScope) { return new TaskPrioritizerContext(name, result, context, newScope, callableDescriptorCollectors); } + + private TaskPrioritizerContext replaceCollectors(CallableDescriptorCollectors newCollectors) { + return new TaskPrioritizerContext(name, result, context, scope, newCollectors); + } + + } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt index 006f3fa07b9..6c3507c6102 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt @@ -39,6 +39,10 @@ import org.jetbrains.jet.lang.types.JetType import kotlin.platform.platformStatic import org.jetbrains.jet.lang.resolve.scopes.receivers.TransientReceiver import org.jetbrains.jet.lang.types.isDynamic +import org.jetbrains.jet.lang.resolve.calls.tasks.collectors.CallableDescriptorCollector +import org.jetbrains.jet.lang.resolve.scopes.JetScope +import org.jetbrains.jet.lang.resolve.BindingTrace +import org.jetbrains.jet.lang.resolve.calls.tasks.collectors.CallableDescriptorCollectors object DynamicCallableDescriptors { @@ -132,3 +136,14 @@ fun DeclarationDescriptor.isDynamic(): Boolean { return dispatchReceiverParameter != null && dispatchReceiverParameter.getType().isDynamic() } +class CollectorForDynamicReceivers(val delegate: CallableDescriptorCollector) : CallableDescriptorCollector by delegate { + override fun getExtensionsByName(scope: JetScope, name: Name, bindingTrace: BindingTrace): Collection { + return delegate.getExtensionsByName(scope, name, bindingTrace).filter { + it.getExtensionReceiverParameter()?.getType()?.isDynamic() ?: false + } + } +} + +fun CallableDescriptorCollectors.onlyDynamicReceivers(): CallableDescriptorCollectors { + return CallableDescriptorCollectors(* this.map { CollectorForDynamicReceivers(it) }.copyToArray()) +} diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.kt b/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.kt index e59b97c98a1..2e63d78900b 100644 --- a/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.kt +++ b/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.kt @@ -17,15 +17,19 @@ fun test(d: dynamic) { d.onStringVal = 1 - d.onDynamicVal = 1 - - d?.onDynamicVal = 1 + d.onDynamicVal = 1 (d: String).onStringVal (d: Any).onAnyVal (d: Any?).onNullableAnyVal (d: Any).onDynamicVal +} +fun testReassignmentWithSafeCall(d: dynamic) { + d?.onDynamicVal = 1 +} + +fun testReassignmentWithStaticCalls(d: dynamic) { (d: String).onStringVal = 1 (d: Any).onAnyVal = 1 (d: Any?).onNullableAnyVal = 1 @@ -40,7 +44,10 @@ val dynamic.onDynamicVal: Int get() = 1 class C { fun test(d: dynamic) { d.memberVal + d.memberVal = 1 + d.memberExtensionVal + d.memberExtensionVal = 1 } val memberVal = 1 diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.txt b/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.txt index d3562145463..5c7a5140492 100644 --- a/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.txt +++ b/compiler/testData/diagnostics/tests/dynamicTypes/extensionVals.txt @@ -5,6 +5,8 @@ internal val dynamic.onDynamicVal: kotlin.Int internal val kotlin.Any?.onNullableAnyVal: kotlin.Int internal val kotlin.String.onStringVal: kotlin.Int internal fun test(/*0*/ d: dynamic): kotlin.Unit +internal fun testReassignmentWithSafeCall(/*0*/ d: dynamic): kotlin.Unit +internal fun testReassignmentWithStaticCalls(/*0*/ d: dynamic): kotlin.Unit internal final class C { public constructor C() diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/extensionVars.kt b/compiler/testData/diagnostics/tests/dynamicTypes/extensionVars.kt index c3bb179b07e..c7b499a984b 100644 --- a/compiler/testData/diagnostics/tests/dynamicTypes/extensionVars.kt +++ b/compiler/testData/diagnostics/tests/dynamicTypes/extensionVars.kt @@ -17,9 +17,9 @@ fun test(d: dynamic) { d.onStringVar = 1 - d.onDynamicVar = 1 + d.onDynamicVar = 1 - d?.onDynamicVar = 1 + d?.onDynamicVar = 1 (d: String).onStringVar (d: Any).onAnyVar diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/extensions.kt b/compiler/testData/diagnostics/tests/dynamicTypes/extensions.kt index 797f2f65d42..79e1cdf8513 100644 --- a/compiler/testData/diagnostics/tests/dynamicTypes/extensions.kt +++ b/compiler/testData/diagnostics/tests/dynamicTypes/extensions.kt @@ -13,8 +13,8 @@ fun test(d: dynamic) { d.onNullableAny() d.onString() - d.onDynamic() - d?.onDynamic() + d.onDynamic() + d?.onDynamic() (d: String).onString() (d: Any).onAny() diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.kt b/compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.kt new file mode 100644 index 00000000000..687c32bd724 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.kt @@ -0,0 +1,61 @@ +// !MARK_DYNAMIC_CALLS + +// MODULE[js]: m1 +// FILE: k.kt + +fun test(d: dynamic) { + d.onDynamic() + d.onNullableDynamic() + + d.valOnDynamic + d.valOnDynamic = 1 + + d.varOnDynamic + d.varOnDynamic = 1 +} + +fun dynamic.extTest() { + onDynamic() + onNullableDynamic() + + valOnDynamic + valOnDynamic = 1 + + varOnDynamic + varOnDynamic = 1 + + this.onDynamic() + this.onNullableDynamic() + + this.valOnDynamic + this.valOnDynamic = 1 + + this.varOnDynamic + this.varOnDynamic = 1 + +} + +fun dynamic.onDynamic() {} +fun dynamic?.onNullableDynamic() {} + +val dynamic.valOnDynamic: Int get() = 1 + +var dynamic.varOnDynamic: Int + get() = 1 + set(v) {} + + +class ForMemberExtensions { + fun test(d: dynamic) { + d.memberExtensionVar + d.memberExtensionVar = 1 + + d.memberExtensionVal + d.memberExtensionVal = 1 + } + + val dynamic.memberExtensionVal: Int get() = 1 + var dynamic.memberExtensionVar: Int + get() = 1 + set(v) {} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.txt b/compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.txt new file mode 100644 index 00000000000..4a6fe53beca --- /dev/null +++ b/compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.txt @@ -0,0 +1,18 @@ +package + +internal val dynamic.valOnDynamic: kotlin.Int +internal var dynamic.varOnDynamic: kotlin.Int +internal fun test(/*0*/ d: dynamic): kotlin.Unit +internal fun dynamic.extTest(): kotlin.Unit +internal fun dynamic.onDynamic(): kotlin.Unit +internal fun dynamic.onNullableDynamic(): kotlin.Unit + +internal final class ForMemberExtensions { + public constructor ForMemberExtensions() + internal final val dynamic.memberExtensionVal: kotlin.Int + internal final var dynamic.memberExtensionVar: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + internal final fun test(/*0*/ d: dynamic): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.kt b/compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.kt new file mode 100644 index 00000000000..983e596a56e --- /dev/null +++ b/compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.kt @@ -0,0 +1,14 @@ +// !MARK_DYNAMIC_CALLS +// !DIAGNOSTICS: -UNUSED_PARAMETER + +// MODULE[js]: m1 +// FILE: k.kt + +fun dynamic.foo(s: String, a: Any) {} +fun dynamic.foo(s: Any, a: String) {} + +fun test(d: dynamic) { + d.foo(1, "") + d.foo("", "") + d.foo(1, 1) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.txt b/compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.txt new file mode 100644 index 00000000000..f4a0e84fa88 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.txt @@ -0,0 +1,5 @@ +package + +internal fun test(/*0*/ d: dynamic): kotlin.Unit +internal fun dynamic.foo(/*0*/ s: kotlin.Any, /*1*/ a: kotlin.String): kotlin.Unit +internal fun dynamic.foo(/*0*/ s: kotlin.String, /*1*/ a: kotlin.Any): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index e4c02d6be48..4c6661c1687 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -3782,6 +3782,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("extensionsToDynamic.kt") + public void testExtensionsToDynamic() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dynamicTypes/extensionsToDynamic.kt"); + doTest(fileName); + } + @TestMetadata("implicitDynamicReceiver.kt") public void testImplicitDynamicReceiver() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dynamicTypes/implicitDynamicReceiver.kt"); @@ -3818,6 +3824,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("overloadingAmbiguity.kt") + public void testOverloadingAmbiguity() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dynamicTypes/overloadingAmbiguity.kt"); + doTest(fileName); + } + @TestMetadata("overrides.kt") public void testOverrides() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dynamicTypes/overrides.kt"); diff --git a/spec-docs/dynamic-types.md b/spec-docs/dynamic-types.md index 7044971b073..12caa5e3604 100644 --- a/spec-docs/dynamic-types.md +++ b/spec-docs/dynamic-types.md @@ -63,13 +63,13 @@ Internally, `dynamic` is represented as a flexible type `Nothing..Any?`, with th ## Resolution rules -- If a receiver is `dynamic` a call is resolved as dynamic if no members match the signature (these are members of `Any`, unless we implement bounded `dynamic`) +- If a receiver is `dynamic` a call is resolved as dynamic if no members (these are members of `Any`, unless we implement bounded `dynamic`) + and no extensions with dynamic receivers match the signature. - Motivation: otherwise, **any** extension to **any** type that simply happens to be in scope and match the name and arguments will be bound for a call with a `dynamic` receiver, i.e. there's no way to force a call to be dynamic, and in the case of a `*`-import the code may change its semantics just because somebody added some extension in another file. - - This means that an extension **can not** be called on a `dynamic` receiver. If needed, one can force a call to an extension by casting - teh receiver to a static type: `(d as Foo).bar()` - - This also means that an extension whose receiver type is `dynamic` can not be called on a `dynamic` variable without a cast + - This means that an extension to a normal, non-dynamic type **can not** be called on a `dynamic` receiver. + If needed, one can force a call to an extension by casting the receiver to a static type: `(d as Foo).bar()` ## Type Argument Inference