FIR: rewrite lambda return type inference

* `return` should only be added to the last statement if the return
   type is not Unit

 * If there is a `return` without an argument, then the expected return
   type is Unit and the last expression is not a return argument (unless
   it's an incomplete call, in which case it is inferred to return Unit;
   this behavior is questionable, but inherited from K1)

 * There should be a constraint on return arguments even if the expected
   type is Unit, otherwise errors will be missed

 * When the expected type is known, using the call completion results
   writer is pointless (and probably subtly wrong).

^KT-54742 Fixed
This commit is contained in:
pyos
2022-12-08 12:02:43 +01:00
committed by Dmitriy Novozhilov
parent 544cf386af
commit 803abfeba8
21 changed files with 241 additions and 538 deletions
@@ -31,9 +31,9 @@ FILE: threeReceiversCorrect.kt
(this@R|special/anonymous|, R|<local>/b|).R|/A.foo|.R|SubstitutionOverride<kotlin/Function1.invoke: R|kotlin/Unit|>|(R|<local>/c|)
}
)
^ R|/with|<R|B|, R|kotlin/Unit|>(R|<local>/b|, <L> = with@fun R|B|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
R|/with|<R|B|, R|kotlin/Unit|>(R|<local>/b|, <L> = with@fun R|B|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
(this@R|special/anonymous|, this@R|special/anonymous|).R|/A.foo|.R|SubstitutionOverride<kotlin/Function1.invoke: R|kotlin/Unit|>|(R|<local>/c|)
^ R|/with|<R|C|, R|kotlin/Unit|>(R|<local>/c|, <L> = with@fun R|C|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
R|/with|<R|C|, R|kotlin/Unit|>(R|<local>/c|, <L> = with@fun R|C|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
(this@R|special/anonymous|, this@R|special/anonymous|).R|/A.foo|.R|SubstitutionOverride<kotlin/Function1.invoke: R|kotlin/Unit|>|(this@R|special/anonymous|)
}
)
@@ -18,7 +18,7 @@ FILE: coercionToUnitWithEarlyReturn.kt
}
}
^ R|<local>/x|?.{ $subj$.R|/A.unit|() }
R|<local>/x|?.{ $subj$.R|/A.unit|() }
}
R|/foo|(R|<local>/lambda|)
@@ -51,7 +51,7 @@ FILE: implicitReceivers.kt
}
public final fun test_3(a: R|kotlin/Any|, b: R|kotlin/Any|, c: R|kotlin/Any|): R|kotlin/Unit| {
R|kotlin/with|<R|kotlin/Any|, R|kotlin/Unit|>(R|<local>/a|, <L> = wa@fun R|kotlin/Any|.<anonymous>(): R|kotlin/Unit| <inline=Inline, kind=EXACTLY_ONCE> {
^ R|kotlin/with|<R|kotlin/Any|, R|kotlin/Unit|>(R|<local>/b|, <L> = wb@fun R|kotlin/Any|.<anonymous>(): R|kotlin/Unit| <inline=Inline, kind=EXACTLY_ONCE> {
R|kotlin/with|<R|kotlin/Any|, R|kotlin/Unit|>(R|<local>/b|, <L> = wb@fun R|kotlin/Any|.<anonymous>(): R|kotlin/Unit| <inline=Inline, kind=EXACTLY_ONCE> {
R|kotlin/with|<R|kotlin/Any|, R|kotlin/Unit|>(R|<local>/c|, <L> = wc@fun R|kotlin/Any|.<anonymous>(): R|kotlin/Unit| <inline=Inline, kind=EXACTLY_ONCE> {
(this@R|special/anonymous| as R|A|)
this@R|special/anonymous|.R|/A.foo|()
@@ -48,7 +48,7 @@ FILE: basic.kt
foo@fun <anonymous>(): R|kotlin/Unit| <inline=Unknown> {
^@foo Unit
}
.R|/foo|(foo@fun R|A|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
.R|/foo<Inapplicable(INAPPLICABLE): /foo>#|(foo@fun R|A|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/A.bar|()
^@foo Int(10)
}
@@ -37,7 +37,7 @@ fun errorWithLambda(): String {
return@foo
} foo {
bar()
return@foo 10
return@foo <!ARGUMENT_TYPE_MISMATCH!>10<!>
}
return ""
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.fir.diagnostics.ConeStubDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.*
import org.jetbrains.kotlin.fir.expressions.impl.FirUnitExpression
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.FirSuperReference
@@ -48,6 +49,7 @@ import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.fir.visitors.FirTransformer
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds
@@ -63,6 +65,39 @@ import kotlin.contracts.contract
fun List<FirQualifierPart>.toTypeProjections(): Array<ConeTypeProjection> =
asReversed().flatMap { it.typeArgumentList.typeArguments.map { typeArgument -> typeArgument.toConeTypeProjection() } }.toTypedArray()
fun FirAnonymousFunction.shouldReturnUnit(returnStatements: Collection<FirStatement>): Boolean =
isLambda && returnStatements.any { it is FirUnitExpression }
fun FirAnonymousFunction.isExplicitlySuspend(session: FirSession): Boolean =
typeRef.coneTypeSafe<ConeKotlinType>()?.isSuspendFunctionType(session) == true
fun FirAnonymousFunction.addReturnToLastStatementIfNeeded() {
// If this lambda's resolved, expected return type is Unit, we don't need an explicit return statement.
// During conversion (to backend IR), the last expression will be coerced to Unit if needed.
if (returnTypeRef.isUnit) return
val body = this.body ?: return
val lastStatement = body.statements.lastOrNull() as? FirExpression ?: return
if (lastStatement is FirReturnExpression) return
val returnType = (body.typeRef as? FirResolvedTypeRef) ?: return
if (returnType.isNothing || returnType.isUnit) return
val returnTarget = FirFunctionTarget(null, isLambda = isLambda).also { it.bind(this) }
val returnExpression = buildReturnExpression {
source = lastStatement.source?.fakeElement(KtFakeSourceElementKind.ImplicitReturn.FromLastStatement)
result = lastStatement
target = returnTarget
}
body.transformStatements(
object : FirTransformer<Nothing?>() {
override fun <E : FirElement> transformElement(element: E, data: Nothing?): E =
@Suppress("UNCHECKED_CAST")
if (element == lastStatement) returnExpression as E else element
}, null
)
}
fun FirFunction.constructFunctionalType(isSuspend: Boolean = false): ConeLookupTagBasedType {
val receiverTypeRef = when (this) {
is FirSimpleFunction -> receiverParameter
@@ -167,18 +167,22 @@ class CandidateFactory private constructor(
}
}
fun PostponedArgumentsAnalyzerContext.addSubsystemFromExpression(statement: FirStatement) {
when (statement) {
fun PostponedArgumentsAnalyzerContext.addSubsystemFromExpression(statement: FirStatement): Boolean {
return when (statement) {
is FirQualifiedAccessExpression,
is FirWhenExpression,
is FirTryExpression,
is FirCheckNotNullCall,
is FirElvisExpression
-> (statement as FirResolvable).candidate()?.let { addOtherSystem(it.system.asReadOnlyStorage()) }
is FirElvisExpression -> {
val candidate = (statement as FirResolvable).candidate() ?: return false
addOtherSystem(candidate.system.asReadOnlyStorage())
true
}
is FirSafeCallExpression -> addSubsystemFromExpression(statement.selector)
is FirWrappedArgumentExpression -> addSubsystemFromExpression(statement.expression)
is FirBlock -> statement.returnExpressions().forEach { addSubsystemFromExpression(it) }
is FirBlock -> statement.returnExpressions().any { addSubsystemFromExpression(it) }
else -> false
}
}
@@ -14,12 +14,12 @@ import org.jetbrains.kotlin.fir.references.builder.buildErrorNamedReference
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedReferenceError
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeLambdaArgumentConstraintPosition
import org.jetbrains.kotlin.fir.resolve.shouldReturnUnit
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeVariable
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isMarkedNullable
import org.jetbrains.kotlin.resolve.calls.components.PostponedArgumentsAnalyzerContext
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
@@ -170,23 +170,29 @@ class PostponedArgumentsAnalyzer(
) {
val (returnArguments, inferenceSession) = results
returnArguments.forEach { c.addSubsystemFromExpression(it) }
val checkerSink: CheckerSink = CheckerSinkImpl(candidate)
val builder = c.getBuilder()
val lastExpression = lambda.atom.body?.statements?.lastOrNull() as? FirExpression
var hasExpressionInReturnArguments = false
// No constraint for return expressions of lambda if it has Unit return type.
val lambdaReturnType = lambda.returnType.let(substitute).takeUnless { it.isUnitOrFlexibleUnit }
val lambdaReturnType = lambda.returnType.let(substitute)
returnArguments.forEach {
val haveSubsystem = c.addSubsystemFromExpression(it)
if (it !is FirExpression) return@forEach
// If the lambda returns Unit, the last expression is not returned and should not be constrained.
// TODO (KT-55837) questionable moment inherited from FE1.0 (the `haveSubsystem` case):
// fun <T> foo(): T
// run {
// if (p) return@run
// foo() // T = Unit, even though there is no implicit return
// }
// Things get even weirder if T has an upper bound incompatible with Unit.
if (it == lastExpression && !haveSubsystem &&
(expectedReturnType?.isUnitOrFlexibleUnit == true || lambda.atom.shouldReturnUnit(returnArguments))
) return@forEach
hasExpressionInReturnArguments = true
// If it is the last expression, and the expected type is Unit, that expression will be coerced to Unit.
// If the last expression is of Unit type, of course it's not coercion-to-Unit case.
val lastExpressionCoercedToUnit =
it == lastExpression && expectedReturnType?.isUnitOrFlexibleUnit == true && !it.typeRef.coneType.isUnitOrFlexibleUnit
// No constraint for the last expression of lambda if it will be coerced to Unit.
if (!lastExpressionCoercedToUnit && !builder.hasContradiction) {
if (!builder.hasContradiction) {
candidate.resolveArgumentExpression(
builder,
it,
@@ -200,7 +206,7 @@ class PostponedArgumentsAnalyzer(
}
}
if (!hasExpressionInReturnArguments && lambdaReturnType != null) {
if (!hasExpressionInReturnArguments && !lambdaReturnType.isUnitOrFlexibleUnit) {
builder.addSubtypeConstraint(
components.session.builtinTypes.unitType.type,
lambdaReturnType,
@@ -42,7 +42,6 @@ import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildStarProjection
import org.jetbrains.kotlin.fir.types.builder.buildTypeProjectionWithVariance
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
import org.jetbrains.kotlin.fir.types.impl.FirImplicitUnitTypeRef
import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer
import org.jetbrains.kotlin.fir.visitors.FirTransformer
import org.jetbrains.kotlin.fir.visitors.transformSingle
@@ -575,12 +574,10 @@ class FirCallCompletionResultsWriterTransformer(
anonymousFunction: FirAnonymousFunction,
data: ExpectedArgumentType?,
): FirStatement {
// This case is not common, and happens when there are anonymous function arguments that aren't mapped to any parameter in the call
// So, we don't run body resolve transformation for them, thus there's no control flow info either
// Control flow info is necessary prerequisite because we collect return expressions in that function
//
// Example: second lambda in the call like list.filter({}, {})
val returnExpressionsOfAnonymousFunction = dataFlowAnalyzer.returnExpressionsOfAnonymousFunctionOrNull(anonymousFunction)
// The case where we can't find any return expressions not common, and happens when there are anonymous function arguments
// that aren't mapped to any parameter in the call. So, we don't run body resolve transformation for them, thus there's
// no control flow info either. Example: second lambda in the call like list.filter({}, {})
val returnStatements = dataFlowAnalyzer.returnExpressionsOfAnonymousFunctionOrNull(anonymousFunction)
?: return transformImplicitTypeRefInAnonymousFunction(anonymousFunction)
val expectedType = data?.getExpectedType(anonymousFunction)?.let { expectedArgumentType ->
@@ -609,7 +606,7 @@ class FirCallCompletionResultsWriterTransformer(
}
}
var needUpdateLambdaType = false
var needUpdateLambdaType = anonymousFunction.typeRef is FirImplicitTypeRef
val receiverParameter = anonymousFunction.receiverParameter
val initialReceiverType = receiverParameter?.typeRef?.coneTypeSafe<ConeKotlinType>()
@@ -619,64 +616,39 @@ class FirCallCompletionResultsWriterTransformer(
needUpdateLambdaType = true
}
val initialType = anonymousFunction.returnTypeRef.coneTypeSafe<ConeKotlinType>()
val initialReturnType = anonymousFunction.returnTypeRef.coneTypeSafe<ConeKotlinType>()
val expectedReturnType = initialReturnType?.let { finalSubstitutor.substituteOrSelf(it) }
?: expectedType?.returnType(session) as? ConeClassLikeType
?: (data as? ExpectedArgumentType.ArgumentsMap)?.lambdasReturnTypes?.get(anonymousFunction)
val finalType = if (anonymousFunction.isLambda) {
expectedType?.returnType(session) as? ConeClassLikeType
?: (data as? ExpectedArgumentType.ArgumentsMap)?.lambdasReturnTypes?.get(anonymousFunction)
?: initialType?.let(finalSubstitutor::substituteOrSelf)
} else {
initialType?.let(finalSubstitutor::substituteOrSelf)
?: expectedType?.returnType(session) as? ConeClassLikeType
?: (data as? ExpectedArgumentType.ArgumentsMap)?.lambdasReturnTypes?.get(anonymousFunction)
val newData = expectedReturnType?.toExpectedType()
val result = transformElement(anonymousFunction, newData)
for (expression in returnStatements) {
expression.transformSingle(this, newData)
}
if (finalType != null) {
if (anonymousFunction.returnTypeRef !is FirImplicitUnitTypeRef) {
val resultType = anonymousFunction.returnTypeRef.withReplacedConeType(finalType)
anonymousFunction.replaceReturnTypeRef(resultType)
}
// Prefer the expected type over the inferred one - the latter is a subtype of the former in valid code,
// and there will be ARGUMENT_TYPE_MISMATCH errors on the lambda's return expressions in invalid code.
val resultReturnType = expectedReturnType
?: session.typeContext.commonSuperTypeOrNull(returnStatements.mapNotNull { (it as? FirExpression)?.resultType?.coneType })
?: session.builtinTypes.unitType.type
if (initialReturnType != resultReturnType) {
result.replaceReturnTypeRef(result.returnTypeRef.resolvedTypeFromPrototype(resultReturnType))
session.lookupTracker?.recordTypeResolveAsLookup(result.returnTypeRef, result.source, context.file.source)
needUpdateLambdaType = true
}
if (needUpdateLambdaType) {
val resolvedTypeRef =
anonymousFunction.constructFunctionalTypeRef(
isSuspend = expectedType?.isSuspendFunctionType(session) == true ||
(expectedType == null && anonymousFunction.isSuspendFunctionType())
)
anonymousFunction.replaceTypeRef(resolvedTypeRef)
session.lookupTracker?.recordTypeResolveAsLookup(resolvedTypeRef, anonymousFunction.source, context.file.source)
val isSuspend = expectedType?.isSuspendFunctionType(session) ?: result.isExplicitlySuspend(session)
result.replaceTypeRef(result.constructFunctionalTypeRef(isSuspend))
session.lookupTracker?.recordTypeResolveAsLookup(result.typeRef, result.source, context.file.source)
}
val result = transformElement(anonymousFunction, null)
for (expression in returnExpressionsOfAnonymousFunction) {
expression.transform<FirElement, ExpectedArgumentType?>(this, finalType?.toExpectedType())
}
if (result.returnTypeRef.coneTypeSafe<ConeIntegerLiteralType>() != null) {
val lastExpressionType =
(returnExpressionsOfAnonymousFunction.lastOrNull() as? FirExpression)
?.typeRef?.coneTypeSafe<ConeKotlinType>()
val newReturnTypeRef = result.returnTypeRef.withReplacedConeType(lastExpressionType)
result.replaceReturnTypeRef(newReturnTypeRef)
val resolvedTypeRef =
result.constructFunctionalTypeRef(isSuspend = expectedType?.isSuspendFunctionType(session) == true)
result.replaceTypeRef(resolvedTypeRef)
session.lookupTracker?.let {
it.recordTypeResolveAsLookup(newReturnTypeRef, anonymousFunction.source, context.file.source)
it.recordTypeResolveAsLookup(resolvedTypeRef, anonymousFunction.source, context.file.source)
}
}
// Have to delay this until the type is written to avoid adding a return if the type is Unit.
result.addReturnToLastStatementIfNeeded()
return result
}
private fun FirAnonymousFunction.isSuspendFunctionType() =
typeRef.coneTypeSafe<ConeKotlinType>()?.isSuspendFunctionType(session) == true
private fun transformImplicitTypeRefInAnonymousFunction(
anonymousFunction: FirAnonymousFunction
): FirStatement {
@@ -22,8 +22,6 @@ import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildReturnExpression
import org.jetbrains.kotlin.fir.expressions.builder.buildUnitExpression
import org.jetbrains.kotlin.fir.expressions.impl.FirLazyBlock
import org.jetbrains.kotlin.fir.references.FirResolvedErrorReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
@@ -36,7 +34,6 @@ import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeLocalVariableNoTypeOrIni
import org.jetbrains.kotlin.fir.resolve.inference.FirStubTypeTransformer
import org.jetbrains.kotlin.fir.resolve.inference.ResolvedLambdaAtom
import org.jetbrains.kotlin.fir.resolve.inference.extractLambdaInfoFromFunctionalType
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.createTypeSubstitutorByTypeConstructor
import org.jetbrains.kotlin.fir.resolve.transformers.FirCallCompletionResultsWriterTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.FirStatusResolver
@@ -49,8 +46,6 @@ import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildImplicitTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.impl.FirImplicitUnitTypeRef
import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer
import org.jetbrains.kotlin.fir.visitors.FirTransformer
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.name.Name
@@ -590,34 +585,6 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
return result
}
private fun transformAnonymousFunctionWithLambdaResolution(
anonymousFunction: FirAnonymousFunction, lambdaResolution: ResolutionMode.LambdaResolution
): FirAnonymousFunction {
val expectedReturnType =
lambdaResolution.expectedReturnTypeRef ?: anonymousFunction.returnTypeRef.takeUnless { it is FirImplicitTypeRef }
val result = transformFunction(anonymousFunction, withExpectedType(expectedReturnType)) as FirAnonymousFunction
val body = result.body
if (result.returnTypeRef is FirImplicitTypeRef && body != null) {
// TODO: This part seems unnecessary because for lambdas in dependent context will be completed and their type
// should be replaced there properly
val returnType =
dataFlowAnalyzer.returnExpressionsOfAnonymousFunction(result)
.firstNotNullOfOrNull { (it as? FirExpression)?.resultType?.coneTypeSafe() }
val resolutionMode = if (returnType != null) {
withExpectedType(returnType)
} else {
withExpectedType(buildErrorTypeRef {
diagnostic =
ConeSimpleDiagnostic("Unresolved lambda return type", DiagnosticKind.InferenceError)
})
}
result.transformReturnTypeRef(transformer, resolutionMode)
}
return result
}
override fun transformSimpleFunction(
simpleFunction: FirSimpleFunction,
data: ResolutionMode
@@ -785,39 +752,40 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
return when (data) {
is ResolutionMode.ContextDependent, is ResolutionMode.ContextDependentDelegate -> {
context.withAnonymousFunction(anonymousFunction, components, data) {
anonymousFunction.addReturn()
anonymousFunction
}
}
is ResolutionMode.LambdaResolution -> {
context.withAnonymousFunction(anonymousFunction, components, data) {
withFullBodyResolve {
transformAnonymousFunctionWithLambdaResolution(anonymousFunction, data).addReturn()
}
}
val expectedReturnTypeRef =
data.expectedReturnTypeRef ?: anonymousFunction.returnTypeRef.takeUnless { it is FirImplicitTypeRef }
transformAnonymousFunctionBody(anonymousFunction, expectedReturnTypeRef, data)
}
is ResolutionMode.WithExpectedType,
is ResolutionMode.ContextIndependent,
is ResolutionMode.ReceiverResolution,
is ResolutionMode.WithSuggestedType -> {
val expectedTypeRef = when (data) {
is ResolutionMode.WithExpectedType -> {
data.expectedTypeRef
}
is ResolutionMode.WithSuggestedType -> {
data.suggestedTypeRef
}
else -> {
buildImplicitTypeRef()
}
}
transformAnonymousFunctionWithExpectedType(anonymousFunction, expectedTypeRef, data)
}
is ResolutionMode.WithStatus, is ResolutionMode.WithExpectedTypeFromCast -> {
is ResolutionMode.WithExpectedType ->
transformAnonymousFunctionWithExpectedType(anonymousFunction, data.expectedTypeRef, data)
is ResolutionMode.WithSuggestedType ->
transformAnonymousFunctionWithExpectedType(anonymousFunction, data.suggestedTypeRef, data)
is ResolutionMode.ContextIndependent, is ResolutionMode.ReceiverResolution ->
transformAnonymousFunctionWithExpectedType(anonymousFunction, buildImplicitTypeRef(), data)
is ResolutionMode.WithStatus, is ResolutionMode.WithExpectedTypeFromCast ->
throw AssertionError("Should not be here in WithStatus/WithExpectedTypeFromCast mode")
}
}
}
private fun transformAnonymousFunctionBody(
anonymousFunction: FirAnonymousFunction,
expectedReturnTypeRef: FirTypeRef?,
data: ResolutionMode
): FirAnonymousFunction {
// `transformFunction` will replace both `typeRef` and `returnTypeRef`, so make sure to keep the former.
val lambdaType = anonymousFunction.typeRef
return context.withAnonymousFunction(anonymousFunction, components, data) {
withFullBodyResolve {
transformFunction(anonymousFunction, withExpectedType(expectedReturnTypeRef)) as FirAnonymousFunction
}
}.apply { replaceTypeRef(lambdaType) }
}
private fun transformAnonymousFunctionWithExpectedType(
anonymousFunction: FirAnonymousFunction,
expectedTypeRef: FirTypeRef,
@@ -829,12 +797,10 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
)
}
var lambda = anonymousFunction
val initialReturnTypeRef = lambda.returnTypeRef
val valueParameters = when {
resolvedLambdaAtom != null -> obtainValueParametersFromResolvedLambdaAtom(resolvedLambdaAtom, lambda)
else -> obtainValueParametersFromExpectedType(expectedTypeRef.coneTypeSafe(), lambda)
}
val returnTypeRefFromResolvedAtom = resolvedLambdaAtom?.returnType?.let { lambda.returnTypeRef.resolvedTypeFromPrototype(it) }
lambda = buildAnonymousFunctionCopy(lambda) {
receiverParameter = lambda.receiverParameter?.takeIf { it.typeRef !is FirImplicitTypeRef }
?: resolvedLambdaAtom?.receiver?.let { coneKotlinType ->
@@ -857,71 +823,38 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
this.valueParameters.clear()
this.valueParameters.addAll(valueParameters)
returnTypeRef = (lambda.returnTypeRef as? FirResolvedTypeRef)
?: returnTypeRefFromResolvedAtom
?: lambda.returnTypeRef
}
lambda = lambda.transformValueParameters(ImplicitToErrorTypeTransformer, null)
val bodyExpectedType = returnTypeRefFromResolvedAtom ?: expectedTypeRef
context.withAnonymousFunction(lambda, components, data) {
withFullBodyResolve {
lambda = transformFunction(lambda, withExpectedType(bodyExpectedType)) as FirAnonymousFunction
}
}
// To separate function and separate commit
val writer = FirCallCompletionResultsWriterTransformer(
session,
ConeSubstitutor.Empty,
components.returnTypeCalculator,
session.typeApproximator,
dataFlowAnalyzer,
components.integerLiteralAndOperatorApproximationTransformer,
components.context
)
lambda.transformSingle(writer, expectedTypeRef.coneTypeSafe<ConeKotlinType>()?.toExpectedType())
}.transformValueParameters(ImplicitToErrorTypeTransformer, null)
val returnStatements = dataFlowAnalyzer.returnExpressionsOfAnonymousFunction(lambda)
val returnExpressionsExceptLast =
if (returnStatements.size > 1)
returnStatements - lambda.body?.statements?.lastOrNull()
else
returnStatements
val implicitReturns = returnExpressionsExceptLast.filter {
(it as? FirExpression)?.typeRef is FirImplicitUnitTypeRef
val initialReturnTypeRef = lambda.returnTypeRef as? FirResolvedTypeRef
val expectedReturnTypeRef = initialReturnTypeRef
?: resolvedLambdaAtom?.returnType?.let { lambda.returnTypeRef.resolvedTypeFromPrototype(it) }
lambda = transformAnonymousFunctionBody(lambda, expectedReturnTypeRef ?: components.noExpectedType, data)
if (initialReturnTypeRef == null) {
lambda.replaceReturnTypeRef(lambda.computeReturnTypeRef(expectedReturnTypeRef))
session.lookupTracker?.recordTypeResolveAsLookup(lambda.returnTypeRef, lambda.source, context.file.source)
}
val returnType = when {
initialReturnTypeRef is FirResolvedTypeRef -> {
initialReturnTypeRef.coneType
}
implicitReturns.isNotEmpty() || (lambda.returnType?.isUnit == true && lambda.isLambda) -> {
// i.e., early return, e.g., l@{ ... return@l ... }
// Note that the last statement will be coerced to Unit if needed.
// also we don't coerce to Unit anonymous functions, only lambdas
session.builtinTypes.unitType.type
}
else -> {
// Otherwise, compute the common super type of all possible return expressions
session.typeContext.commonSuperTypeOrNull(
returnStatements.mapNotNull { (it as? FirExpression)?.resultType?.coneType }
) ?: session.builtinTypes.unitType.type
}
}
if (lambda.returnTypeRef !is FirImplicitUnitTypeRef) {
lambda.replaceReturnTypeRef(
initialReturnTypeRef.resolvedTypeFromPrototype(returnType).also {
session.lookupTracker?.recordTypeResolveAsLookup(it, lambda.source, components.file.source)
}
)
}
lambda.replaceTypeRef(
lambda.constructFunctionalTypeRef(
isSuspend = expectedTypeRef.coneTypeSafe<ConeKotlinType>()?.isSuspendFunctionType(session) == true
).also {
session.lookupTracker?.recordTypeResolveAsLookup(it, lambda.source, components.file.source)
}
)
return lambda.addReturn()
lambda.replaceTypeRef(lambda.constructFunctionalTypeRef(resolvedLambdaAtom?.isSuspend == true))
session.lookupTracker?.recordTypeResolveAsLookup(lambda.typeRef, lambda.source, context.file.source)
lambda.addReturnToLastStatementIfNeeded()
return lambda
}
private fun FirAnonymousFunction.computeReturnTypeRef(expected: FirResolvedTypeRef?): FirResolvedTypeRef {
// Any lambda expression assigned to `(...) -> Unit` returns Unit
if (isLambda && expected?.type?.isUnit == true) return expected
// `lambda@ { return@lambda }` always returns Unit
val returnStatements = dataFlowAnalyzer.returnExpressionsOfAnonymousFunction(this)
if (shouldReturnUnit(returnStatements)) return session.builtinTypes.unitType
// Here is a questionable moment where we could prefer the expected type over an inferred one.
// In correct code this doesn't matter, as all return expression types should be subtypes of the expected type.
// In incorrect code, this would change diagnostics: we can get errors either on the entire lambda, or only on its
// return statements. The former kind of makes more sense, but the latter is more readable.
val inferredFromReturnStatements =
session.typeContext.commonSuperTypeOrNull(returnStatements.mapNotNull { (it as? FirExpression)?.resultType?.coneType })
return inferredFromReturnStatements?.let { returnTypeRef.resolvedTypeFromPrototype(it) }
?: session.builtinTypes.unitType // Empty lambda returns Unit
}
private fun obtainValueParametersFromResolvedLambdaAtom(
@@ -982,47 +915,6 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
}
}
private fun FirAnonymousFunction.addReturn(): FirAnonymousFunction {
// If this lambda's resolved, expected return type is Unit, we don't need an explicit return statement.
// During conversion (to backend IR), the last expression will be coerced to Unit if needed.
// As per KT-41005, we should not force coercion to Unit for nullable return type, though.
if (returnTypeRef.isUnit && body?.typeRef?.isMarkedNullable == false) {
return this
}
val lastStatement = body?.statements?.lastOrNull()
val returnType = (body?.typeRef as? FirResolvedTypeRef) ?: return this
val returnNothing = returnType.isNothing || returnType.isUnit
if (lastStatement is FirExpression && !returnNothing) {
body?.transformChildren(
object : FirDefaultTransformer<FirExpression>() {
override fun <E : FirElement> transformElement(element: E, data: FirExpression): E {
if (element == lastStatement) {
val returnExpression = buildReturnExpression {
source = element.source?.fakeElement(KtFakeSourceElementKind.ImplicitReturn.FromLastStatement)
result = lastStatement
target = FirFunctionTarget(null, isLambda = this@addReturn.isLambda).also {
it.bind(this@addReturn)
}
}
@Suppress("UNCHECKED_CAST")
return (returnExpression as E)
}
return element
}
override fun transformReturnExpression(
returnExpression: FirReturnExpression,
data: FirExpression
): FirStatement {
return returnExpression
}
},
buildUnitExpression()
)
}
return this
}
override fun transformBackingField(
backingField: FirBackingField,
data: ResolutionMode,