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<Unit>) = 1
and there is 'fun handleResult(x: X, c: Continuation<Nothing>)',
then expected type for lambda is 'Controller.() -> X'
This commit is contained in:
Denis Zharkov
2016-05-19 11:35:06 +03:00
parent e97376bb2c
commit fcbff72f6f
5 changed files with 189 additions and 1 deletions
@@ -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
}
@@ -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) }
}
}
@@ -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<Nothing>) { }
}
class GenericController<T> {
fun handleResult(x: T, c: Continuation<Nothing>) { }
}
class UnitController {
fun handleResult(x: Unit, c: Continuation<Nothing>) { }
}
class EmptyController
fun builder(coroutine c: IntController.() -> Continuation<Unit>) = 1
fun <T> genericBuilder(coroutine c: GenericController<T>.() -> Continuation<Unit>): T = null!!
fun unitBuilder(coroutine c: UnitController.() -> Continuation<Unit>) = 1
fun emptyBuilder(coroutine c: EmptyController.() -> Continuation<Unit>) = 1
fun <T> manyArgumentsBuilder(
coroutine c1: UnitController.() -> Continuation<Unit>,
coroutine c2: GenericController<T>.() -> Continuation<Unit>,
coroutine c3: IntController.() -> Continuation<Int>
):T = null!!
fun severalParamsInLambda(coroutine c: UnitController.(String, Int) -> Continuation<Unit>) {}
fun foo() {
builder({ 1 })
builder { 1 }
val x = { 1 }
builder(<!TYPE_MISMATCH!>x<!>)
builder(<!UNCHECKED_CAST!>{1} as (IntController.() -> Continuation<Unit>)<!>)
var i: Int = 1
i = genericBuilder({ 1 })
i = genericBuilder { 1 }
<!TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>genericBuilder<!> { 1 }
genericBuilder<Int> { 1 }
genericBuilder<Int> { <!TYPE_MISMATCH!>""<!> }
val y = { 1 }
<!TYPE_INFERENCE_PARAMETER_CONSTRAINT_ERROR!>genericBuilder<!>(<!TYPE_MISMATCH!>y<!>)
unitBuilder {}
unitBuilder { <!UNUSED_EXPRESSION!>1<!> }
unitBuilder({})
unitBuilder({ <!UNUSED_EXPRESSION!>1<!> })
<!TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>manyArgumentsBuilder<!>({}, { "" }) { 1 }
val s: String = manyArgumentsBuilder({}, { "" }) { 1 }
manyArgumentsBuilder<String>({}, { "" }, { 1 })
manyArgumentsBuilder<String>({}, { <!CONSTANT_EXPECTED_TYPE_MISMATCH!>1<!> }, { 2 })
severalParamsInLambda { x, y ->
x checkType { _<String>() }
y checkType { _<Int>() }
}
}
@@ -0,0 +1,40 @@
package
public fun builder(/*0*/ coroutine c: IntController.() -> kotlin.coroutines.Continuation<kotlin.Unit>): kotlin.Int
public fun emptyBuilder(/*0*/ coroutine c: EmptyController.() -> kotlin.coroutines.Continuation<kotlin.Unit>): kotlin.Int
public fun foo(): kotlin.Unit
public fun </*0*/ T> genericBuilder(/*0*/ coroutine c: GenericController<T>.() -> kotlin.coroutines.Continuation<kotlin.Unit>): T
public fun </*0*/ T> manyArgumentsBuilder(/*0*/ coroutine c1: UnitController.() -> kotlin.coroutines.Continuation<kotlin.Unit>, /*1*/ coroutine c2: GenericController<T>.() -> kotlin.coroutines.Continuation<kotlin.Unit>, /*2*/ coroutine c3: IntController.() -> kotlin.coroutines.Continuation<kotlin.Int>): T
public fun severalParamsInLambda(/*0*/ coroutine c: UnitController.(kotlin.String, kotlin.Int) -> kotlin.coroutines.Continuation<kotlin.Unit>): kotlin.Unit
public fun unitBuilder(/*0*/ coroutine c: UnitController.() -> kotlin.coroutines.Continuation<kotlin.Unit>): 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</*0*/ T> {
public constructor GenericController</*0*/ T>()
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.Nothing>): 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.Nothing>): 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.Nothing>): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -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)