From cc0d63117a4ecb50070b30a992de3242aa7e4a84 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Fri, 13 Aug 2021 12:22:35 +0300 Subject: [PATCH] [FIR] Add property type narrowing --- .../fir/backend/Fir2IrDeclarationStorage.kt | 13 ++-- .../kotlin/fir/resolve/ResolutionMode.kt | 24 +++++++ .../kotlin/fir/resolve/ResolveUtils.kt | 14 ++++ .../kotlin/fir/resolve/calls/FirReceivers.kt | 12 ++-- .../calls/tower/FirTowerResolveTask.kt | 66 ++++++++++++++++++- .../fir/resolve/inference/FirCallCompleter.kt | 46 ++++++++----- .../FirDeclarationsResolveTransformer.kt | 36 +++++++--- .../body/resolve/FirImplicitBodyResolve.kt | 5 +- .../utils/declarationAttributes.kt | 9 +++ 9 files changed, 190 insertions(+), 35 deletions(-) diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt index 0dc470b8be0..676b77670ef 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrDeclarationStorage.kt @@ -24,10 +24,7 @@ import org.jetbrains.kotlin.fir.expressions.FirConstExpression import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess import org.jetbrains.kotlin.fir.expressions.impl.FirExpressionStub -import org.jetbrains.kotlin.fir.lazy.Fir2IrLazyClass -import org.jetbrains.kotlin.fir.lazy.Fir2IrLazyConstructor -import org.jetbrains.kotlin.fir.lazy.Fir2IrLazyProperty -import org.jetbrains.kotlin.fir.lazy.Fir2IrLazySimpleFunction +import org.jetbrains.kotlin.fir.lazy.* import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.firProvider import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType @@ -613,7 +610,13 @@ class Fir2IrDeclarationStorage( signature, containerSource ) { symbol -> - val accessorReturnType = if (isSetter) irBuiltIns.unitType else propertyType + val accessorReturnType = if (isSetter) { + irBuiltIns.unitType + } else if (property.canNarrowDownGetterType) { + property.backingField?.returnTypeRef?.toIrType() ?: propertyType + } else { + propertyType + } val visibility = propertyAccessor?.visibility?.let { components.visibilityConverter.convertToDescriptorVisibility(it) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolutionMode.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolutionMode.kt index 4b0569e09ca..8586af6266d 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolutionMode.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolutionMode.kt @@ -30,12 +30,36 @@ sealed class ResolutionMode { class WithExpectedTypeFromCast( val expectedTypeRef: FirTypeRef, ) : ResolutionMode() + + /** + * This resolution mode is similar to + * WithExpectedType, but it's ok if the + * types turn out to be incompatible. + * Consider the following examples with + * properties and their backing fields: + * + * val items: List + * field = mutableListOf() + * + * val s: String + * field = 10 + * get() = ... + * + * In these examples we should try using + * the property type information while + * resolving the initializer, but it's ok + * if it's not applicable. + */ + class WithSuggestedType( + val suggestedTypeRef: FirTypeRef, + ) : ResolutionMode() } 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 } + is ResolutionMode.WithSuggestedType -> suggestedTypeRef else -> null } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt index 3f67ee10262..a320ee03ea3 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.fir.references.FirSuperReference import org.jetbrains.kotlin.fir.references.FirThisReference import org.jetbrains.kotlin.fir.resolve.calls.Candidate import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate +import org.jetbrains.kotlin.fir.resolve.calls.FirPropertyWithExplicitBackingFieldResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents @@ -45,6 +46,7 @@ import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl import org.jetbrains.kotlin.name.* import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget import org.jetbrains.kotlin.types.SmartcastStability +import org.jetbrains.kotlin.utils.addToStdlib.safeAs fun List.toTypeProjections(): Array = asReversed().flatMap { it.typeArgumentList.typeArguments.map { typeArgument -> typeArgument.toConeTypeProjection() } }.toTypedArray() @@ -195,6 +197,18 @@ fun BodyResolveComponents.typeFromCallee(access: T): FirReso is FirNamedReferenceWithCandidate -> { typeFromSymbol(newCallee.candidateSymbol, false) } + is FirPropertyWithExplicitBackingFieldResolvedNamedReference -> { + val backingField = newCallee.resolvedSymbol + .safeAs() + ?.fir + ?.backingField + + if (newCallee.hasVisibleBackingField && backingField != null) { + typeFromSymbol(backingField.symbol, false) + } else { + typeFromSymbol(newCallee.resolvedSymbol, false) + } + } is FirResolvedNamedReference -> { typeFromSymbol(newCallee.resolvedSymbol, false) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt index 513a098b4f9..a12751fbe4b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/FirReceivers.kt @@ -22,11 +22,8 @@ import org.jetbrains.kotlin.fir.scopes.FirTypeScope import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol -import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType -import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef -import org.jetbrains.kotlin.fir.types.coneType -import org.jetbrains.kotlin.fir.types.coneTypeSafe import org.jetbrains.kotlin.types.SmartcastStability interface Receiver @@ -55,7 +52,7 @@ abstract class AbstractExplicitReceiverValue : AbstractExplic get() = explicitReceiver } -class ExpressionReceiverValue( +open class ExpressionReceiverValue( override val explicitReceiver: FirExpression ) : AbstractExplicitReceiverValue(), ReceiverValue { override fun scope(useSiteSession: FirSession, scopeSession: ScopeSession): FirTypeScope? { @@ -72,6 +69,11 @@ class ExpressionReceiverValue( } } +class NarrowedExpressionReceiverValue( + explicitReceiver: FirExpression, + override val type: ConeKotlinType, +) : ExpressionReceiverValue(explicitReceiver) + sealed class ImplicitReceiverValue>( val boundSymbol: S, type: ConeKotlinType, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt index 7d0759c81bd..7d901a8d8f6 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/FirTowerResolveTask.kt @@ -5,21 +5,33 @@ package org.jetbrains.kotlin.fir.resolve.calls.tower +import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.asReversedFrozen +import org.jetbrains.kotlin.fir.containingClass +import org.jetbrains.kotlin.fir.declarations.utils.canNarrowDownGetterType +import org.jetbrains.kotlin.fir.declarations.utils.isFinal +import org.jetbrains.kotlin.fir.declarations.utils.visibility import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.builder.buildExpressionStub +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS import org.jetbrains.kotlin.fir.resolve.FirTowerDataContext import org.jetbrains.kotlin.fir.resolve.calls.* +import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.scopes.FirScope +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef +import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.impl.FirImplicitBuiltinTypeRef import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind import org.jetbrains.kotlin.resolve.descriptorUtil.HIDES_MEMBERS_NAME_LIST +import org.jetbrains.kotlin.utils.addToStdlib.safeAs internal class TowerDataElementsForName( @@ -224,12 +236,64 @@ internal open class FirTowerResolveTask( ) } + private fun FirPropertySymbol.hasAccessibleBackingField(info: CallInfo): Boolean { + val receiverContainer = containingClass()?.toSymbol(info.session) + + if (!this.isFinal) { + return false + } + + return when (fir.backingField?.visibility) { + Visibilities.Private -> { + info.containingDeclarations.any { + it.symbol == receiverContainer + } + } + Visibilities.Internal -> { + receiverContainer?.moduleData == info.containingFile.moduleData + } + else -> { + false + } + } + } + + private fun FirExpression.getNarrowedDownReturnType(info: CallInfo): ConeKotlinType? { + val propertyReceiver = this.safeAs() + ?.calleeReference + ?.safeAs() + ?.resolvedSymbol + ?.safeAs() + ?: return null + + // This can happen in case of 2 properties referencing + // each other recursively. See: Jet81.fir.kt + if ( + propertyReceiver.fir.returnTypeRef is FirImplicitTypeRef || + propertyReceiver.fir.backingField?.returnTypeRef is FirImplicitTypeRef + ) { + return null + } + + if (propertyReceiver.hasAccessibleBackingField(info) && propertyReceiver.canNarrowDownGetterType) { + return propertyReceiver.fir.backingField?.returnTypeRef?.coneType + } + + return null + } + suspend fun runResolverForExpressionReceiver( info: CallInfo, receiver: FirExpression, parentGroup: TowerGroup = TowerGroup.EmptyRoot ) { - val explicitReceiverValue = ExpressionReceiverValue(receiver) + val narrowedType = receiver.getNarrowedDownReturnType(info) + + val explicitReceiverValue = if (narrowedType != null) { + NarrowedExpressionReceiverValue(receiver, narrowedType) + } else { + ExpressionReceiverValue(receiver) + } processExtensionsThatHideMembers(info, explicitReceiverValue, parentGroup) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt index c741bb9e8f8..eab0d15a7c5 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt @@ -57,7 +57,14 @@ class FirCallCompleter( expectedTypeRef: FirTypeRef?, expectedTypeMismatchIsReportedInChecker: Boolean = false, ): CompletionResult where T : FirResolvable, T : FirStatement = - completeCall(call, expectedTypeRef, mayBeCoercionToUnitApplied = false, expectedTypeMismatchIsReportedInChecker, isFromCast = false) + completeCall( + call, + expectedTypeRef, + mayBeCoercionToUnitApplied = false, + expectedTypeMismatchIsReportedInChecker, + isFromCast = false, + shouldEnforceExpectedType = true, + ) fun completeCall(call: T, data: ResolutionMode): CompletionResult where T : FirResolvable, T : FirStatement = completeCall( @@ -66,6 +73,7 @@ class FirCallCompleter( (data as? ResolutionMode.WithExpectedType)?.mayBeCoercionToUnitApplied == true, (data as? ResolutionMode.WithExpectedType)?.expectedTypeMismatchIsReportedInChecker == true, isFromCast = data is ResolutionMode.WithExpectedTypeFromCast, + shouldEnforceExpectedType = data !is ResolutionMode.WithSuggestedType, ) private fun completeCall( @@ -73,6 +81,7 @@ class FirCallCompleter( mayBeCoercionToUnitApplied: Boolean, expectedTypeMismatchIsReportedInChecker: Boolean, isFromCast: Boolean, + shouldEnforceExpectedType: Boolean, ): CompletionResult where T : FirResolvable, T : FirStatement { val typeRef = components.typeFromCallee(call) @@ -92,25 +101,32 @@ class FirCallCompleter( session.lookupTracker?.recordTypeResolveAsLookup(resolvedTypeRef, call.source, null) } - if (expectedTypeRef is FirResolvedTypeRef) { - if (isFromCast) { - if (candidate.isFunctionForExpectTypeFromCastFeature()) { + val expectedTypeConstraintPosition = ConeExpectedTypeConstraintPosition(expectedTypeMismatchIsReportedInChecker) + + when { + expectedTypeRef !is FirResolvedTypeRef -> { + // nothing + } + !shouldEnforceExpectedType -> { + candidate.system.addSubtypeConstraintIfCompatible( + initialType, expectedTypeRef.type, expectedTypeConstraintPosition + ) + } + isFromCast -> when { + candidate.isFunctionForExpectTypeFromCastFeature() -> { candidate.system.addSubtypeConstraint( initialType, expectedTypeRef.type, ConeExpectedTypeConstraintPosition(expectedTypeMismatchIsReportedInChecker = false), ) } - } else { - 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) - } + } + !expectedTypeRef.coneType.isUnitOrFlexibleUnit || !mayBeCoercionToUnitApplied -> { + candidate.system.addSubtypeConstraint(initialType, expectedTypeRef.type, expectedTypeConstraintPosition) + } + candidate.system.notFixedTypeVariables.isNotEmpty() -> { + candidate.system.addSubtypeConstraintIfCompatible( + initialType, expectedTypeRef.type, expectedTypeConstraintPosition + ) } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt index 2137f21ad35..5522995a375 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirDeclarationsResolveTransformer.kt @@ -45,8 +45,6 @@ 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 -import org.jetbrains.kotlin.types.AbstractTypeChecker -import org.jetbrains.kotlin.name.SpecialNames open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransformer) : FirPartialBodyResolveTransformer(transformer) { private val statusResolver: FirStatusResolver = FirStatusResolver(session, scopeSession) @@ -128,7 +126,12 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor val returnTypeRef = property.returnTypeRef val bodyResolveState = property.bodyResolveState if (bodyResolveState == FirPropertyBodyResolveState.EVERYTHING_RESOLVED) return property - if (returnTypeRef !is FirImplicitTypeRef && implicitTypeOnly) return property + + val canHaveDeepImplicitTypeRefs = property.hasExplicitBackingField + + if (returnTypeRef !is FirImplicitTypeRef && implicitTypeOnly && !canHaveDeepImplicitTypeRefs) { + return property + } property.transformReceiverTypeRef(transformer, ResolutionMode.ContextIndependent) dataFlowAnalyzer.enterProperty(property) @@ -727,8 +730,20 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor } } } - is ResolutionMode.WithExpectedType, is ResolutionMode.ContextIndependent -> { - val expectedTypeRef = (data as? ResolutionMode.WithExpectedType)?.expectedTypeRef ?: buildImplicitTypeRef() + is ResolutionMode.WithExpectedType, + is ResolutionMode.ContextIndependent, + is ResolutionMode.WithSuggestedType -> { + val expectedTypeRef = when (data) { + is ResolutionMode.WithExpectedType -> { + data.expectedTypeRef + } + is ResolutionMode.WithSuggestedType -> { + data.suggestedTypeRef + } + else -> { + buildImplicitTypeRef() + } + } val resolvedLambdaAtom = (expectedTypeRef as? FirResolvedTypeRef)?.let { extractLambdaInfoFromFunctionalType( it.type, it, anonymousFunction, returnTypeVariable = null, components, candidate = null @@ -883,10 +898,15 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor backingField: FirBackingField, data: ResolutionMode, ): FirStatement { - backingField.transformInitializer( - transformer, + val propertyType = data.expectedType + val initializerData = if (backingField.returnTypeRef is FirResolvedTypeRef) { withExpectedType(backingField.returnTypeRef) - ) + } else if (propertyType != null) { + ResolutionMode.WithSuggestedType(propertyType) + } else { + ResolutionMode.ContextDependent + } + backingField.transformInitializer(transformer, initializerData) if ( backingField.returnTypeRef is FirErrorTypeRef || backingField.returnTypeRef is FirResolvedTypeRef diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt index 6e0d80efcc9..c9c33c4cce7 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.resolve.transformers.body.resolve import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticProperty +import org.jetbrains.kotlin.fir.declarations.utils.hasExplicitBackingField import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind import org.jetbrains.kotlin.fir.resolve.ResolutionMode @@ -166,7 +167,9 @@ open class FirImplicitAwareBodyResolveTransformer( return transform() } - if (member.returnTypeRef is FirResolvedTypeRef) return member + val canHaveDeepImplicitTypeRefs = member is FirProperty && member.hasExplicitBackingField + + if (member.returnTypeRef is FirResolvedTypeRef && !canHaveDeepImplicitTypeRefs) return member val symbol = member.symbol val status = implicitBodyResolveComputationSession.getStatus(symbol) if (status is ImplicitBodyResolveComputationStatus.Computed) { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/declarationAttributes.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/declarationAttributes.kt index 9bbe99312f9..eed4c818735 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/declarationAttributes.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/utils/declarationAttributes.kt @@ -64,6 +64,15 @@ fun FirPropertySymbol.getExplicitBackingField(): FirBackingField? { return fir.getExplicitBackingField() } +val FirProperty.canNarrowDownGetterType: Boolean + get() { + val backingFieldHasDifferentType = backingField != null && backingField?.returnTypeRef?.coneType != returnTypeRef.coneType + return backingFieldHasDifferentType && getter is FirDefaultPropertyGetter + } + +val FirPropertySymbol.canNarrowDownGetterType: Boolean + get() = fir.canNarrowDownGetterType + // See [BindingContext.BACKING_FIELD_REQUIRED] val FirProperty.hasBackingField: Boolean get() {