[NI] Commonize type-conversions (SAM/Suspend)

- Allow participating subtypes of functional types in conversions
 - Fix several subtle inconsistencies
 - Place logic about conversions at one place

 Now conversions operations have two stages: before usual subtyping
 check and after one. This is needed to support conversions of
 subtypes (of functional types, for example). First, the compiler
 checks if it possible to resolve an argument without conversion and
 only then it tries to perform conversion.
 Note that it'd be incorrect to perform conversion eagerly as it can
 change resolve (Runnable & () -> Unit <: KRunnable), plus we can't
 guess whether conversion is needed at all as it's important not to
 look into supertypes if resolution doesn't actually needed it

 #KT-36448 Fixed
 #KT-37574 Fixed
 #KT-38604 Fixed
This commit is contained in:
Mikhail Zarechenskiy
2020-05-15 15:27:37 +03:00
parent f702da5c51
commit 8bdc4d34f7
24 changed files with 636 additions and 107 deletions
@@ -413,7 +413,21 @@ private fun KotlinResolutionCandidate.resolveKotlinArgument(
candidateParameter: ParameterDescriptor?,
receiverInfo: ReceiverInfo
) {
val expectedType = candidateParameter?.let { prepareExpectedType(argument, candidateParameter) }
val candidateExpectedType = candidateParameter?.let { argument.getExpectedType(it, callComponents.languageVersionSettings) }
val conversionDataBeforeSubtyping =
if (candidateParameter == null || candidateExpectedType == null) {
null
} else {
TypeConversions.performCompositeConversionBeforeSubtyping(
this, argument, candidateParameter, candidateExpectedType
)
}
val convertedExpectedType = conversionDataBeforeSubtyping?.convertedType
val unsubstitutedExpectedType = conversionDataBeforeSubtyping?.convertedType ?: candidateExpectedType
val expectedType = unsubstitutedExpectedType?.let { prepareExpectedType(it) }
val convertedArgument = if (expectedType != null && shouldRunConversionForConstants(expectedType)) {
val convertedConstant = resolutionCallbacks.convertSignedConstantToUnsigned(argument)
if (convertedConstant != null) {
@@ -423,8 +437,14 @@ private fun KotlinResolutionCandidate.resolveKotlinArgument(
convertedConstant
} else null
addResolvedKtPrimitive(
resolveKtPrimitive(
if (candidateExpectedType == null || // Nothing to convert
convertedExpectedType != null || // Type is already converted
conversionDataBeforeSubtyping?.wasConversion == true || // We tried to convert type but failed
conversionDataBeforeSubtyping?.conversionDefinitelyNotNeeded == true ||
csBuilder.hasContradiction
) {
val resolvedAtom = resolveKtPrimitive(
csBuilder,
argument,
expectedType,
@@ -432,7 +452,54 @@ private fun KotlinResolutionCandidate.resolveKotlinArgument(
receiverInfo,
convertedArgument?.unknownIntegerType?.unwrap()
)
)
addResolvedKtPrimitive(resolvedAtom)
} else {
var convertedTypeAfterSubtyping: UnwrappedType? = null
csBuilder.runTransaction {
val resolvedAtom = resolveKtPrimitive(
csBuilder,
argument,
expectedType,
this@resolveKotlinArgument,
receiverInfo,
convertedArgument?.unknownIntegerType?.unwrap()
)
if (!hasContradiction) {
addResolvedKtPrimitive(resolvedAtom)
return@runTransaction true
}
convertedTypeAfterSubtyping =
TypeConversions.performCompositeConversionAfterSubtyping(
this@resolveKotlinArgument,
argument,
candidateParameter,
candidateExpectedType
)?.let { prepareExpectedType(it) }
if (convertedTypeAfterSubtyping == null) {
addResolvedKtPrimitive(resolvedAtom)
return@runTransaction true
}
false
}
if (convertedTypeAfterSubtyping != null) {
val resolvedAtom = resolveKtPrimitive(
csBuilder,
argument,
convertedTypeAfterSubtyping,
this@resolveKotlinArgument,
receiverInfo,
convertedArgument?.unknownIntegerType?.unwrap()
)
addResolvedKtPrimitive(resolvedAtom)
}
}
}
private fun KotlinResolutionCandidate.shouldRunConversionForConstants(expectedType: UnwrappedType): Boolean {
@@ -474,23 +541,6 @@ private fun KotlinResolutionCandidate.checkUnsafeImplicitInvokeAfterSafeCall(arg
return ImplicitInvokeCheckStatus.INVOKE_ON_NOT_NULL_VARIABLE
}
private fun KotlinResolutionCandidate.prepareExpectedType(
argument: KotlinCallArgument,
parameter: ParameterDescriptor
): UnwrappedType {
val expectedType =
performExpectedTypeConversion(argument, parameter)
?: argument.getExpectedType(parameter, callComponents.languageVersionSettings)
return prepareExpectedType(expectedType)
}
private fun KotlinResolutionCandidate.performExpectedTypeConversion(
argument: KotlinCallArgument,
parameter: ParameterDescriptor
): UnwrappedType? {
return getExpectedTypeWithSAMConversion(argument, parameter) ?: getExpectedTypeWithSuspendConversion(argument, parameter)
}
private fun KotlinResolutionCandidate.prepareExpectedType(expectedType: UnwrappedType): UnwrappedType {
val resultType = knownTypeParametersResultingSubstitutor?.substitute(expectedType) ?: expectedType
return resolvedCall.freshVariablesSubstitutor.safeSubstitute(resultType)
@@ -17,23 +17,11 @@ import org.jetbrains.kotlin.resolve.sam.getFunctionTypeForPossibleSamType
import org.jetbrains.kotlin.types.UnwrappedType
import org.jetbrains.kotlin.types.typeUtil.isNothing
object SamTypeConversions {
fun conversionMightBeNeededBeforeSubtypingCheck(argument: KotlinCallArgument): Boolean {
return when (argument) {
is SimpleKotlinCallArgument -> argument.receiver.stableType.isFunctionType
is LambdaKotlinCallArgument, is CallableReferenceKotlinCallArgument -> true
else -> false
}
}
fun conversionMightBeNeededAfterSubtypingCheck(argument: KotlinCallArgument): Boolean {
return argument is SimpleKotlinCallArgument && argument.receiver.stableType.isFunctionTypeOrSubtype
}
fun conversionDefinitelyNotNeeded(
object SamTypeConversions : ParameterTypeConversion {
override fun conversionDefinitelyNotNeeded(
candidate: KotlinResolutionCandidate,
candidateParameter: ParameterDescriptor
argument: KotlinCallArgument,
expectedParameterType: UnwrappedType
): Boolean {
val callComponents = candidate.callComponents
val generatingAdditionalSamCandidateIsEnabled =
@@ -41,7 +29,7 @@ object SamTypeConversions {
!callComponents.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitVarargAsArrayAfterSamArgument)
if (generatingAdditionalSamCandidateIsEnabled) return true
if (candidateParameter.type.isNothing()) return true
if (expectedParameterType.isNothing()) return true
val samConversionOracle = callComponents.samConversionOracle
if (!callComponents.languageVersionSettings.supportsFeature(LanguageFeature.SamConversionForKotlinFunctions)) {
@@ -50,48 +38,56 @@ object SamTypeConversions {
return false
}
}
fun KotlinResolutionCandidate.getExpectedTypeWithSAMConversion(
argument: KotlinCallArgument,
candidateParameter: ParameterDescriptor
): UnwrappedType? {
if (SamTypeConversions.conversionDefinitelyNotNeeded(this, candidateParameter)) return null
val argumentIsFunctional = when (argument) {
is SimpleKotlinCallArgument -> argument.receiver.stableType.isFunctionType
is LambdaKotlinCallArgument, is CallableReferenceKotlinCallArgument -> true
else -> false
override fun conversionIsNeededBeforeSubtypingCheck(argument: KotlinCallArgument): Boolean {
return when (argument) {
is SimpleKotlinCallArgument -> argument.receiver.stableType.isFunctionType
is LambdaKotlinCallArgument, is CallableReferenceKotlinCallArgument -> true
else -> false
}
}
if (!argumentIsFunctional) return null
val originalExpectedType = argument.getExpectedType(candidateParameter.original, callComponents.languageVersionSettings)
override fun conversionIsNeededAfterSubtypingCheck(argument: KotlinCallArgument): Boolean {
return argument is SimpleKotlinCallArgument && argument.receiver.stableType.isFunctionTypeOrSubtype
}
val convertedTypeByOriginal =
callComponents.samConversionResolver.getFunctionTypeForPossibleSamType(
originalExpectedType,
callComponents.samConversionOracle
) ?: return null
override fun convertParameterType(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
parameter: ParameterDescriptor,
expectedParameterType: UnwrappedType
): UnwrappedType? {
val callComponents = candidate.callComponents
val originalExpectedType = argument.getExpectedType(parameter.original, callComponents.languageVersionSettings)
val candidateExpectedType = argument.getExpectedType(candidateParameter, callComponents.languageVersionSettings)
val convertedTypeByCandidate =
callComponents.samConversionResolver.getFunctionTypeForPossibleSamType(
candidateExpectedType,
callComponents.samConversionOracle
val convertedTypeByOriginal =
callComponents.samConversionResolver.getFunctionTypeForPossibleSamType(
originalExpectedType,
callComponents.samConversionOracle
) ?: return null
val convertedTypeByCandidate =
callComponents.samConversionResolver.getFunctionTypeForPossibleSamType(
expectedParameterType,
callComponents.samConversionOracle
)
assert(expectedParameterType.constructor == originalExpectedType.constructor && convertedTypeByCandidate != null) {
"If original type is SAM type, then candidate should have same type constructor and corresponding function type\n" +
"originalExpectType: $originalExpectedType, candidateExpectType: $expectedParameterType\n" +
"functionTypeByOriginal: $convertedTypeByOriginal, functionTypeByCandidate: $convertedTypeByCandidate"
}
candidate.resolvedCall.registerArgumentWithSamConversion(
argument,
SamConversionDescription(convertedTypeByOriginal, convertedTypeByCandidate!!)
)
assert(candidateExpectedType.constructor == originalExpectedType.constructor && convertedTypeByCandidate != null) {
"If original type is SAM type, then candidate should have same type constructor and corresponding function type\n" +
"originalExpectType: $originalExpectedType, candidateExpectType: $candidateExpectedType\n" +
"functionTypeByOriginal: $convertedTypeByOriginal, functionTypeByCandidate: $convertedTypeByCandidate"
val samDescriptor = originalExpectedType.constructor.declarationDescriptor
if (samDescriptor is ClassDescriptor) {
callComponents.lookupTracker.record(candidate.scopeTower.location, samDescriptor, SAM_LOOKUP_NAME)
}
return convertedTypeByCandidate
}
resolvedCall.registerArgumentWithSamConversion(argument, SamConversionDescription(convertedTypeByOriginal, convertedTypeByCandidate!!))
val samDescriptor = originalExpectedType.constructor.declarationDescriptor
if (samDescriptor is ClassDescriptor) {
callComponents.lookupTracker.record(scopeTower.location, samDescriptor, SAM_LOOKUP_NAME)
}
return convertedTypeByCandidate
}
@@ -11,11 +11,11 @@ import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.types.UnwrappedType
object SuspendTypeConversions {
fun conversionDefinitelyNotNeeded(
object SuspendTypeConversions : ParameterTypeConversion {
override fun conversionDefinitelyNotNeeded(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
candidateParameter: ParameterDescriptor
expectedParameterType: UnwrappedType
): Boolean {
if (!candidate.callComponents.languageVersionSettings.supportsFeature(LanguageFeature.SuspendConversion)) return true
@@ -25,34 +25,33 @@ object SuspendTypeConversions {
if (!argumentType.isFunctionType) return true
if (argumentType.isSuspendFunctionType) return true
if (!candidateParameter.type.isSuspendFunctionType) return true
if (!expectedParameterType.isSuspendFunctionType) return true
return false
}
fun KotlinResolutionCandidate.conversionMightBeNeededBeforeSubtypingCheck(): Boolean = true
fun KotlinResolutionCandidate.conversionMightBeNeededAfterSubtypingCheck(): Boolean = false
override fun conversionIsNeededBeforeSubtypingCheck(argument: KotlinCallArgument): Boolean = true
override fun conversionIsNeededAfterSubtypingCheck(argument: KotlinCallArgument): Boolean =
argument is SimpleKotlinCallArgument && argument.receiver.stableType.isFunctionTypeOrSubtype
override fun convertParameterType(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
parameter: ParameterDescriptor,
expectedParameterType: UnwrappedType
): UnwrappedType? {
val nonSuspendParameterType = createFunctionType(
candidate.callComponents.builtIns,
expectedParameterType.annotations,
expectedParameterType.getReceiverTypeFromFunctionType(),
expectedParameterType.getValueParameterTypesFromFunctionType().map { it.type },
parameterNames = null,
expectedParameterType.getReturnTypeFromFunctionType(),
suspendFunction = false
)
candidate.resolvedCall.registerArgumentWithSuspendConversion(argument, nonSuspendParameterType)
return nonSuspendParameterType
}
}
fun KotlinResolutionCandidate.getExpectedTypeWithSuspendConversion(
argument: KotlinCallArgument,
candidateParameter: ParameterDescriptor
): UnwrappedType? {
if (SuspendTypeConversions.conversionDefinitelyNotNeeded(this, argument, candidateParameter)) return null
val parameterType = candidateParameter.type
val nonSuspendParameterType = createFunctionType(
callComponents.builtIns,
parameterType.annotations,
parameterType.getReceiverTypeFromFunctionType(),
parameterType.getValueParameterTypesFromFunctionType().map { it.type },
parameterNames = null,
parameterType.getReturnTypeFromFunctionType(),
suspendFunction = false
)
resolvedCall.registerArgumentWithSuspendConversion(argument, nonSuspendParameterType)
return nonSuspendParameterType
}
@@ -0,0 +1,107 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.calls.components
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.resolve.calls.model.KotlinCallArgument
import org.jetbrains.kotlin.resolve.calls.model.KotlinResolutionCandidate
import org.jetbrains.kotlin.types.UnwrappedType
interface ParameterTypeConversion {
fun conversionDefinitelyNotNeeded(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
expectedParameterType: UnwrappedType
): Boolean
fun conversionIsNeededBeforeSubtypingCheck(argument: KotlinCallArgument): Boolean
fun conversionIsNeededAfterSubtypingCheck(argument: KotlinCallArgument): Boolean
fun convertParameterType(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
parameter: ParameterDescriptor,
expectedParameterType: UnwrappedType
): UnwrappedType?
}
object TypeConversions {
fun performCompositeConversionBeforeSubtyping(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
candidateParameter: ParameterDescriptor,
candidateExpectedType: UnwrappedType,
): ConversionData {
val samConversionData = performConversionBeforeSubtyping(
candidate, argument, candidateParameter, candidateExpectedType, SamTypeConversions
)
val suspendConversionData = if (samConversionData.convertedType == null) {
performConversionBeforeSubtyping(candidate, argument, candidateParameter, candidateExpectedType, SuspendTypeConversions)
} else {
null
}
return ConversionData(
convertedType = samConversionData.convertedType ?: suspendConversionData?.convertedType,
wasConversion = samConversionData.wasConversion || suspendConversionData?.wasConversion == true,
conversionDefinitelyNotNeeded = samConversionData.conversionDefinitelyNotNeeded &&
(suspendConversionData == null || suspendConversionData.conversionDefinitelyNotNeeded)
)
}
fun performCompositeConversionAfterSubtyping(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
candidateParameter: ParameterDescriptor,
candidateExpectedType: UnwrappedType,
): UnwrappedType? {
val samConvertedType = performConversionAfterSubtyping(
candidate, argument, candidateParameter, candidateExpectedType, SamTypeConversions
)
if (samConvertedType != null) return samConvertedType
return performConversionAfterSubtyping(candidate, argument, candidateParameter, candidateExpectedType, SuspendTypeConversions)
}
private fun performConversionAfterSubtyping(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
candidateParameter: ParameterDescriptor,
candidateExpectedType: UnwrappedType,
conversion: ParameterTypeConversion
): UnwrappedType? {
return if (
conversion.conversionIsNeededAfterSubtypingCheck(argument) &&
!conversion.conversionDefinitelyNotNeeded(candidate, argument, candidateExpectedType)
) {
conversion.convertParameterType(candidate, argument, candidateParameter, candidateExpectedType)
} else {
null
}
}
private fun performConversionBeforeSubtyping(
candidate: KotlinResolutionCandidate,
argument: KotlinCallArgument,
candidateParameter: ParameterDescriptor,
candidateExpectedType: UnwrappedType,
conversion: ParameterTypeConversion
): ConversionData {
val conversionDefinitelyNotNeeded = conversion.conversionDefinitelyNotNeeded(candidate, argument, candidateExpectedType)
return if (!conversionDefinitelyNotNeeded && conversion.conversionIsNeededBeforeSubtypingCheck(argument)) {
ConversionData(
conversion.convertParameterType(candidate, argument, candidateParameter, candidateExpectedType),
wasConversion = true,
conversionDefinitelyNotNeeded
)
} else {
ConversionData(convertedType = null, wasConversion = false, conversionDefinitelyNotNeeded)
}
}
class ConversionData(val convertedType: UnwrappedType?, val wasConversion: Boolean, val conversionDefinitelyNotNeeded: Boolean)
}