FIR: Support adding expect type to calls in foo() as R position

See https://kotlinlang.org/docs/whatsnew12.html#support-for-foo-as-a-shorthand-for-this-foo
This commit is contained in:
Denis.Zharkov
2021-05-26 17:44:05 +03:00
committed by TeamCityServer
parent 6136526a3a
commit d932d5b0a5
20 changed files with 219 additions and 49 deletions
@@ -12197,6 +12197,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/inference/expectedTypeWithGenerics.kt");
}
@Test
@TestMetadata("expectedTypeWithGenericsSafeCalls.kt")
public void testExpectedTypeWithGenericsSafeCalls() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/expectedTypeWithGenericsSafeCalls.kt");
}
@Test
@TestMetadata("extensionLambdasAndArrow.kt")
public void testExtensionLambdasAndArrow() throws Exception {
@@ -12197,6 +12197,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/inference/expectedTypeWithGenerics.kt");
}
@Test
@TestMetadata("expectedTypeWithGenericsSafeCalls.kt")
public void testExpectedTypeWithGenericsSafeCalls() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/expectedTypeWithGenericsSafeCalls.kt");
}
@Test
@TestMetadata("extensionLambdasAndArrow.kt")
public void testExtensionLambdasAndArrow() throws Exception {
@@ -9,8 +9,10 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.inference.isFunctionForExpectTypeFromCastFeature
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
@@ -34,7 +36,7 @@ object FirUselessTypeOperationCallChecker : FirTypeOperatorCallChecker() {
} else {
targetType
}
if (isRefinementUseless(context, candidateType, refinedTargetType, shouldCheckForExactType(expression, context))) {
if (isRefinementUseless(context, candidateType, refinedTargetType, shouldCheckForExactType(expression, context), arg)) {
when (expression.operation) {
FirOperation.IS -> reporter.reportOn(expression.source, FirErrors.USELESS_IS_CHECK, true, context)
FirOperation.NOT_IS -> reporter.reportOn(expression.source, FirErrors.USELESS_IS_CHECK, false, context)
@@ -62,8 +64,14 @@ object FirUselessTypeOperationCallChecker : FirTypeOperatorCallChecker() {
candidateType: ConeKotlinType,
targetType: ConeKotlinType,
shouldCheckForExactType: Boolean,
arg: FirExpression,
): Boolean {
return if (shouldCheckForExactType) {
if (arg is FirFunctionCall) {
val function = arg.toResolvedCallableSymbol()?.fir as? FirFunction
if (function != null && function.isFunctionForExpectTypeFromCastFeature()) return false
}
isExactTypeCast(context, candidateType, targetType)
} else {
isUpcast(context, candidateType, targetType)
@@ -26,11 +26,16 @@ sealed class ResolutionMode {
class WithStatus(val status: FirDeclarationStatus) : ResolutionMode()
class LambdaResolution(val expectedReturnTypeRef: FirResolvedTypeRef?) : ResolutionMode()
class WithExpectedTypeFromCast(
val expectedTypeRef: FirTypeRef,
) : ResolutionMode()
}
fun ResolutionMode.expectedType(components: BodyResolveComponents): FirTypeRef? = when (this) {
fun ResolutionMode.expectedType(components: BodyResolveComponents, allowFromCast: Boolean = false): FirTypeRef? = when (this) {
is ResolutionMode.WithExpectedType -> expectedTypeRef
is ResolutionMode.ContextIndependent -> components.noExpectedType
is ResolutionMode.WithExpectedTypeFromCast -> expectedTypeRef.takeIf { allowFromCast }
else -> null
}
@@ -8,16 +8,14 @@ package org.jetbrains.kotlin.fir.resolve.inference
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirResolvable
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext
import org.jetbrains.kotlin.fir.resolve.calls.isUnitOrFlexibleUnit
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.expectedType
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeArgumentConstraintPosition
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeExpectedTypeConstraintPosition
@@ -59,20 +57,22 @@ class FirCallCompleter(
expectedTypeRef: FirTypeRef?,
expectedTypeMismatchIsReportedInChecker: Boolean = false,
): CompletionResult<T> where T : FirResolvable, T : FirStatement =
completeCall(call, expectedTypeRef, mayBeCoercionToUnitApplied = false, expectedTypeMismatchIsReportedInChecker)
completeCall(call, expectedTypeRef, mayBeCoercionToUnitApplied = false, expectedTypeMismatchIsReportedInChecker, isFromCast = false)
fun <T> completeCall(call: T, data: ResolutionMode): CompletionResult<T> where T : FirResolvable, T : FirStatement =
completeCall(
call,
data.expectedType(components),
data.expectedType(components, allowFromCast = true),
(data as? ResolutionMode.WithExpectedType)?.mayBeCoercionToUnitApplied == true,
(data as? ResolutionMode.WithExpectedType)?.expectedTypeMismatchIsReportedInChecker == true,
isFromCast = data is ResolutionMode.WithExpectedTypeFromCast,
)
private fun <T> completeCall(
call: T, expectedTypeRef: FirTypeRef?,
mayBeCoercionToUnitApplied: Boolean,
expectedTypeMismatchIsReportedInChecker: Boolean,
isFromCast: Boolean,
): CompletionResult<T>
where T : FirResolvable, T : FirStatement {
val typeRef = components.typeFromCallee(call)
@@ -93,15 +93,24 @@ class FirCallCompleter(
}
if (expectedTypeRef is FirResolvedTypeRef) {
val expectedTypeConstraintPosition = ConeExpectedTypeConstraintPosition(expectedTypeMismatchIsReportedInChecker)
if (expectedTypeRef.coneType.isUnitOrFlexibleUnit && mayBeCoercionToUnitApplied) {
if (candidate.system.notFixedTypeVariables.isNotEmpty()) {
candidate.system.addSubtypeConstraintIfCompatible(
initialType, expectedTypeRef.type, expectedTypeConstraintPosition
if (isFromCast) {
if (candidate.isFunctionForExpectTypeFromCastFeature()) {
candidate.system.addSubtypeConstraint(
initialType, expectedTypeRef.type,
ConeExpectedTypeConstraintPosition(expectedTypeMismatchIsReportedInChecker = false),
)
}
} else {
candidate.system.addSubtypeConstraint(initialType, expectedTypeRef.type, expectedTypeConstraintPosition)
val expectedTypeConstraintPosition = ConeExpectedTypeConstraintPosition(expectedTypeMismatchIsReportedInChecker)
if (expectedTypeRef.coneType.isUnitOrFlexibleUnit && mayBeCoercionToUnitApplied) {
if (candidate.system.notFixedTypeVariables.isNotEmpty()) {
candidate.system.addSubtypeConstraintIfCompatible(
initialType, expectedTypeRef.type, expectedTypeConstraintPosition
)
}
} else {
candidate.system.addSubtypeConstraint(initialType, expectedTypeRef.type, expectedTypeConstraintPosition)
}
}
}
@@ -288,3 +297,30 @@ class FirCallCompleter(
this, TypeApproximatorConfiguration.FinalApproximationAfterResolutionAndInference
) ?: this
}
private fun Candidate.isFunctionForExpectTypeFromCastFeature(): Boolean {
if (typeArgumentMapping != TypeArgumentMapping.NoExplicitArguments) return false
val fir = symbol.fir as? FirFunction ?: return false
return fir.isFunctionForExpectTypeFromCastFeature()
}
// Expect type is only being added to calls in a position of cast argument: foo() as R
// And that call should be resolved to something materialize()-like: it returns its single generic parameter and doesn't have value parameters
// fun <T> materialize(): T
fun FirFunction<*>.isFunctionForExpectTypeFromCastFeature(): Boolean {
val typeParameter = typeParameters.singleOrNull() ?: return false
val returnType = returnTypeRef.coneTypeSafe<ConeKotlinType>() ?: return false
if ((returnType.lowerBoundIfFlexible() as? ConeTypeParameterType)?.lookupTag != typeParameter.symbol.toLookupTag()) return false
fun FirTypeRef.isBadType() =
coneTypeSafe<ConeKotlinType>()
?.contains { (it.lowerBoundIfFlexible() as? ConeTypeParameterType)?.lookupTag == typeParameter.symbol.toLookupTag() } != false
if (valueParameters.any { it.returnTypeRef.isBadType() } || receiverTypeRef?.isBadType() == true) return false
return true
}
@@ -760,8 +760,8 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor
)
lambda.addReturn()
}
is ResolutionMode.WithStatus -> {
throw AssertionError("Should not be here in WithStatus mode")
is ResolutionMode.WithStatus, is ResolutionMode.WithExpectedTypeFromCast -> {
throw AssertionError("Should not be here in WithStatus/WithExpectedTypeFromCast mode")
}
}
}
@@ -594,7 +594,7 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
): FirStatement {
val resolved = components.typeResolverTransformer.withAllowedBareTypes {
typeOperatorCall.transformConversionTypeRef(transformer, ResolutionMode.ContextIndependent)
}.transformOtherChildren(transformer, ResolutionMode.ContextIndependent)
}.transformTypeOperatorCallChildren()
val conversionTypeRef = resolved.conversionTypeRef.withTypeArgumentsForBareType(resolved.argument)
resolved.transformChildren(object : FirDefaultTransformer<Any?>() {
@@ -636,6 +636,37 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
return resolved
}
private fun FirTypeOperatorCall.transformTypeOperatorCallChildren(): FirTypeOperatorCall {
if (operation == FirOperation.AS || operation == FirOperation.SAFE_AS) {
val argument = argumentList.arguments.singleOrNull() ?: error("Not a single argument: ${this.render()}")
// For calls in the form of (materialize() as MyClass) we've got a special rule that adds expect type to the `materialize()` call
// AS operator doesn't add expected type to any other expressions
// See https://kotlinlang.org/docs/whatsnew12.html#support-for-foo-as-a-shorthand-for-this-foo
// And limitations at org.jetbrains.kotlin.fir.resolve.inference.FirCallCompleterKt.isFunctionForExpectTypeFromCastFeature(org.jetbrains.kotlin.fir.declarations.FirFunction<?>)
if (argument is FirFunctionCall || (argument is FirSafeCallExpression && argument.regularQualifiedAccess is FirFunctionCall)) {
val expectedType = conversionTypeRef.coneTypeSafe<ConeKotlinType>()?.takeIf {
// is not bare type
it !is ConeClassLikeType ||
it.typeArguments.isNotEmpty() ||
(it.lookupTag.toSymbol(session)?.fir as? FirTypeParameterRefsOwner)?.typeParameters?.isEmpty() == true
}?.let {
if (operation == FirOperation.SAFE_AS)
it.withNullability(ConeNullability.NULLABLE, session.typeContext)
else
it
}
if (expectedType != null) {
val newMode = ResolutionMode.WithExpectedTypeFromCast(conversionTypeRef.withReplacedConeType(expectedType))
return transformOtherChildren(transformer, newMode)
}
}
}
return transformOtherChildren(transformer, ResolutionMode.ContextIndependent)
}
override fun transformCheckNotNullCall(
checkNotNullCall: FirCheckNotNullCall,
data: ResolutionMode,