From fcbff72f6f0509588c74a1ee88d5dc90fdfbb42e Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Thu, 19 May 2016 11:35:06 +0300 Subject: [PATCH] Replace expected type for coroutine-lambda Use 'handleResult' method of controller to determine what lambda should return If original declaration was fun builder(coroutine c: Controller.() -> Continuation) = 1 and there is 'fun handleResult(x: X, c: Continuation)', then expected type for lambda is 'Controller.() -> X' --- .../kotlin/coroutines/coroutineUtil.kt | 41 ++++++++++++ .../kotlin/resolve/calls/CallResolverUtil.kt | 30 ++++++++- .../tests/coroutines/lambdaExpectedType.kt | 64 +++++++++++++++++++ .../tests/coroutines/lambdaExpectedType.txt | 40 ++++++++++++ .../checkers/DiagnosticsTestGenerated.java | 15 +++++ 5 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/coroutines/coroutineUtil.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/coroutines/coroutineUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/coroutines/coroutineUtil.kt new file mode 100644 index 00000000000..f04f9e3a417 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/coroutines/coroutineUtil.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2016 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.coroutines + +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.types.KotlinType + +@JvmField +val CONTINUATION_INTERFACE_FQ_NAME = FqName("kotlin.coroutines.Continuation") + +@JvmField +val HANDLE_RESULT_NAME = Name.identifier("handleResult") + +/** + * @returns type of first value parameter if function is 'operator handleResult' in coroutine controller + */ +fun SimpleFunctionDescriptor.getExpectedTypeForCoroutineControllerHandleResult(): KotlinType? { + if (name != HANDLE_RESULT_NAME) return null + if (valueParameters.size != 2) return null + + if (valueParameters[1].type.constructor.declarationDescriptor?.fqNameUnsafe != CONTINUATION_INTERFACE_FQ_NAME.toUnsafe()) return null + + return valueParameters[0].type +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolverUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolverUtil.kt index aea01f288c3..0c6b26fe69c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolverUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolverUtil.kt @@ -19,7 +19,11 @@ package org.jetbrains.kotlin.resolve.calls.callResolverUtil import com.google.common.collect.Lists import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.builtins.ReflectionTypes +import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType +import org.jetbrains.kotlin.builtins.isExtensionFunctionType +import org.jetbrains.kotlin.coroutines.getExpectedTypeForCoroutineControllerHandleResult import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.KotlinLookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.CallTransformer @@ -28,6 +32,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.Constrain import org.jetbrains.kotlin.resolve.calls.inference.getNestedTypeVariables import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate +import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue @@ -36,6 +41,7 @@ import org.jetbrains.kotlin.resolve.validation.InfixValidator import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.TypeUtils.DONT_CARE import org.jetbrains.kotlin.types.checker.KotlinTypeChecker +import org.jetbrains.kotlin.types.typeUtil.asTypeProjection import org.jetbrains.kotlin.types.typeUtil.contains import org.jetbrains.kotlin.util.OperatorNameConventions @@ -165,6 +171,28 @@ fun getEffectiveExpectedType(parameterDescriptor: ValueParameterDescriptor, argu return varargElementType } + if (parameterDescriptor.isCoroutine && + argument.getArgumentExpression() is KtLambdaExpression && + parameterDescriptor.type.isExtensionFunctionType + ) { + val receiverType = getReceiverTypeFromFunctionType(parameterDescriptor.type)!! + + val newExpectedLambdaReturnType = + receiverType.memberScope + .getContributedFunctions(HANDLE_RESULT_NAME, KotlinLookupLocation(argument.asElement())).mapNotNull { + it.getExpectedTypeForCoroutineControllerHandleResult() + }.singleOrNull() + // If no handleResult function found, then expected return type for lambda is Unit + ?: parameterDescriptor.module.builtIns.unitType + + // replace return type for lambda with the one we got from single 'handleResult' + val newFunctionTypeArguments = parameterDescriptor.type.arguments.toMutableList() + newFunctionTypeArguments[newFunctionTypeArguments.lastIndex] = newExpectedLambdaReturnType.asTypeProjection() + + return parameterDescriptor.type.replace( + newArguments = newFunctionTypeArguments) + } + return parameterDescriptor.type } @@ -198,4 +226,4 @@ fun createResolutionCandidatesForConstructors( } return constructors.map { ResolutionCandidate.create(call, it, dispatchReceiver, receiverKind, knownSubstitutor) } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.kt b/compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.kt new file mode 100644 index 00000000000..44e41bf06a1 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.kt @@ -0,0 +1,64 @@ +// !CHECK_TYPE +// !DIAGNOSTICS: -UNUSED_PARAMETER -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE -UNUSED_VARIABLE +class IntController { + fun handleResult(x: Int, c: Continuation) { } +} + +class GenericController { + fun handleResult(x: T, c: Continuation) { } +} + +class UnitController { + fun handleResult(x: Unit, c: Continuation) { } +} + +class EmptyController + +fun builder(coroutine c: IntController.() -> Continuation) = 1 +fun genericBuilder(coroutine c: GenericController.() -> Continuation): T = null!! +fun unitBuilder(coroutine c: UnitController.() -> Continuation) = 1 +fun emptyBuilder(coroutine c: EmptyController.() -> Continuation) = 1 + +fun manyArgumentsBuilder( + coroutine c1: UnitController.() -> Continuation, + coroutine c2: GenericController.() -> Continuation, + coroutine c3: IntController.() -> Continuation +):T = null!! + +fun severalParamsInLambda(coroutine c: UnitController.(String, Int) -> Continuation) {} + +fun foo() { + builder({ 1 }) + builder { 1 } + + val x = { 1 } + builder(x) + builder({1} as (IntController.() -> Continuation)) + + var i: Int = 1 + i = genericBuilder({ 1 }) + i = genericBuilder { 1 } + genericBuilder { 1 } + genericBuilder { 1 } + genericBuilder { "" } + + val y = { 1 } + genericBuilder(y) + + unitBuilder {} + unitBuilder { 1 } + unitBuilder({}) + unitBuilder({ 1 }) + + manyArgumentsBuilder({}, { "" }) { 1 } + + val s: String = manyArgumentsBuilder({}, { "" }) { 1 } + + manyArgumentsBuilder({}, { "" }, { 1 }) + manyArgumentsBuilder({}, { 1 }, { 2 }) + + severalParamsInLambda { x, y -> + x checkType { _() } + y checkType { _() } + } +} diff --git a/compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.txt b/compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.txt new file mode 100644 index 00000000000..30a4ea3335e --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.txt @@ -0,0 +1,40 @@ +package + +public fun builder(/*0*/ coroutine c: IntController.() -> kotlin.coroutines.Continuation): kotlin.Int +public fun emptyBuilder(/*0*/ coroutine c: EmptyController.() -> kotlin.coroutines.Continuation): kotlin.Int +public fun foo(): kotlin.Unit +public fun genericBuilder(/*0*/ coroutine c: GenericController.() -> kotlin.coroutines.Continuation): T +public fun manyArgumentsBuilder(/*0*/ coroutine c1: UnitController.() -> kotlin.coroutines.Continuation, /*1*/ coroutine c2: GenericController.() -> kotlin.coroutines.Continuation, /*2*/ coroutine c3: IntController.() -> kotlin.coroutines.Continuation): T +public fun severalParamsInLambda(/*0*/ coroutine c: UnitController.(kotlin.String, kotlin.Int) -> kotlin.coroutines.Continuation): kotlin.Unit +public fun unitBuilder(/*0*/ coroutine c: UnitController.() -> kotlin.coroutines.Continuation): kotlin.Int + +public final class EmptyController { + public constructor EmptyController() + 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 +} + +public final class GenericController { + public constructor GenericController() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun handleResult(/*0*/ x: T, /*1*/ c: kotlin.coroutines.Continuation): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class IntController { + public constructor IntController() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun handleResult(/*0*/ x: kotlin.Int, /*1*/ c: kotlin.coroutines.Continuation): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class UnitController { + public constructor UnitController() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun handleResult(/*0*/ x: kotlin.Unit, /*1*/ c: kotlin.coroutines.Continuation): 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/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index e42abc55631..339a90473c1 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -3865,6 +3865,21 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { } } + @TestMetadata("compiler/testData/diagnostics/tests/coroutines") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Coroutines extends AbstractDiagnosticsTest { + public void testAllFilesPresentInCoroutines() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/coroutines"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("lambdaExpectedType.kt") + public void testLambdaExpectedType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/lambdaExpectedType.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/cyclicHierarchy") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)