From 96bd2c54f0b074c9e025a7ef42de0377dc4a560d Mon Sep 17 00:00:00 2001 From: Tianyu Geng Date: Thu, 6 May 2021 20:17:32 -0700 Subject: [PATCH] FIR: add smartcast stability to FIR --- .../fir/backend/Fir2IrImplicitCastInserter.kt | 6 ++- .../kotlin/fir/resolve/ResolveUtils.kt | 4 +- .../kotlin/fir/resolve/calls/FirReceivers.kt | 4 +- .../expressions/FirExpressionWithSmartcast.kt | 3 ++ .../FirExpressionWithSmartcastToNull.kt | 3 ++ .../FirExpressionWithSmartcastBuilder.kt | 6 ++- ...FirExpressionWithSmartcastToNullBuilder.kt | 6 ++- .../impl/FirExpressionWithSmartcastImpl.kt | 10 ++++- .../FirExpressionWithSmartcastToNullImpl.kt | 10 ++++- .../kotlin/fir/tree/generator/FieldSets.kt | 2 + .../fir/tree/generator/NodeConfigurator.kt | 3 ++ .../kotlin/fir/tree/generator/Types.kt | 2 + .../kotlin/types/SmartcastStability.kt | 39 +++++++++++++++++++ 13 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 core/compiler.common/src/org/jetbrains/kotlin/types/SmartcastStability.kt diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt index ebe7c9e559f..dc07ee5d493 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt @@ -274,7 +274,11 @@ class Fir2IrImplicitCastInserter( } override fun visitExpressionWithSmartcast(expressionWithSmartcast: FirExpressionWithSmartcast, data: IrElement): IrExpression { - return implicitCastOrExpression(data as IrExpression, expressionWithSmartcast.typeRef) + return if (expressionWithSmartcast.smartcastStability == SmartcastStability.STABLE_VALUE) { + implicitCastOrExpression(data as IrExpression, expressionWithSmartcast.typeRef) + } else { + data as IrExpression + } } override fun visitExpressionWithSmartcastToNull( 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 29420ee9e93..2c5e8b5ec2b 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 @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef 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 fun List.toTypeProjections(): Array = asReversed().flatMap { it.typeArgumentList.typeArguments.map { typeArgument -> typeArgument.toConeTypeProjection() } }.toTypedArray() @@ -285,8 +286,9 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo( } return buildExpressionWithSmartcast { originalExpression = qualifiedAccessExpression - typeRef = intersectedTypeRef + smartcastType = intersectedTypeRef this.typesFromSmartCast = typesFromSmartCast + smartcastStability = SmartcastStability.STABLE_VALUE } } 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 0ce7b7d1813..295a7d69eeb 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 @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.fir.types.ConeKotlinType 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 @@ -89,8 +90,9 @@ sealed class ImplicitReceiverValue>( } else { buildExpressionWithSmartcast { originalExpression = originalReceiverExpression - typeRef = originalReceiverExpression.typeRef.resolvedTypeFromPrototype(type) + smartcastType = originalReceiverExpression.typeRef.resolvedTypeFromPrototype(type) typesFromSmartCast = listOf(type) + smartcastStability = SmartcastStability.STABLE_VALUE } } implicitScope = type.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing) diff --git a/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcast.kt b/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcast.kt index a2fbdc5dcea..dded469518b 100644 --- a/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcast.kt +++ b/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcast.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.references.FirReference import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.FirTypeProjection import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.types.SmartcastStability import org.jetbrains.kotlin.fir.visitors.* import org.jetbrains.kotlin.fir.FirImplementationDetail @@ -31,6 +32,8 @@ abstract class FirExpressionWithSmartcast : FirQualifiedAccessExpression() { abstract val originalExpression: FirQualifiedAccessExpression abstract val typesFromSmartCast: Collection abstract val originalType: FirTypeRef + abstract val smartcastType: FirTypeRef + abstract val smartcastStability: SmartcastStability override fun accept(visitor: FirVisitor, data: D): R = visitor.visitExpressionWithSmartcast(this, data) diff --git a/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcastToNull.kt b/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcastToNull.kt index 3e643586063..9181ab521fb 100644 --- a/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcastToNull.kt +++ b/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/expressions/FirExpressionWithSmartcastToNull.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.references.FirReference import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.FirTypeProjection import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.types.SmartcastStability import org.jetbrains.kotlin.fir.visitors.* import org.jetbrains.kotlin.fir.FirImplementationDetail @@ -31,6 +32,8 @@ abstract class FirExpressionWithSmartcastToNull : FirExpressionWithSmartcast() { abstract override val originalExpression: FirQualifiedAccessExpression abstract override val typesFromSmartCast: Collection abstract override val originalType: FirTypeRef + abstract override val smartcastType: FirTypeRef + abstract override val smartcastStability: SmartcastStability override fun accept(visitor: FirVisitor, data: D): R = visitor.visitExpressionWithSmartcastToNull(this, data) diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastBuilder.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastBuilder.kt index 48d595c654d..faa0b3f8c65 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastBuilder.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastBuilder.kt @@ -10,14 +10,16 @@ import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.impl.FirExpressionWithSmartcastImpl import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.types.SmartcastStability class FirExpressionWithSmartcastBuilder { lateinit var originalExpression: FirQualifiedAccessExpression - lateinit var typeRef: FirTypeRef + lateinit var smartcastType: FirTypeRef lateinit var typesFromSmartCast: Collection + lateinit var smartcastStability: SmartcastStability fun build(): FirExpressionWithSmartcast { - return FirExpressionWithSmartcastImpl(originalExpression, typeRef, typesFromSmartCast) + return FirExpressionWithSmartcastImpl(originalExpression, smartcastType, typesFromSmartCast, smartcastStability) } } diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastToNullBuilder.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastToNullBuilder.kt index b639f24c92d..4d3df9a28b6 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastToNullBuilder.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/builder/FirExpressionWithSmartcastToNullBuilder.kt @@ -10,14 +10,16 @@ import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.impl.FirExpressionWithSmartcastToNullImpl import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.types.SmartcastStability class FirExpressionWithSmartcastToNullBuilder { lateinit var originalExpression: FirQualifiedAccessExpression - lateinit var typeRef: FirTypeRef + lateinit var smartcastType: FirTypeRef lateinit var typesFromSmartCast: Collection + lateinit var smartcastStability: SmartcastStability fun build(): FirExpressionWithSmartcastToNull { - return FirExpressionWithSmartcastToNullImpl(originalExpression, typeRef, typesFromSmartCast) + return FirExpressionWithSmartcastToNullImpl(originalExpression, smartcastType, typesFromSmartCast, smartcastStability) } } diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastImpl.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastImpl.kt index 19b1cac569c..792c18878d5 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastImpl.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastImpl.kt @@ -19,11 +19,13 @@ import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.fir.visitors.FirTransformer import org.jetbrains.kotlin.fir.visitors.FirVisitor import org.jetbrains.kotlin.fir.visitors.transformSingle +import org.jetbrains.kotlin.types.SmartcastStability internal class FirExpressionWithSmartcastImpl( override var originalExpression: FirQualifiedAccessExpression, - override val typeRef: FirTypeRef, - override val typesFromSmartCast: Collection + override val smartcastType: FirTypeRef, + override val typesFromSmartCast: Collection, + override val smartcastStability: SmartcastStability ) : FirExpressionWithSmartcast() { init { assert(originalExpression.typeRef is FirResolvedTypeRef) @@ -37,6 +39,10 @@ internal class FirExpressionWithSmartcastImpl( override val extensionReceiver: FirExpression get() = originalExpression.extensionReceiver override val calleeReference: FirReference get() = originalExpression.calleeReference override val originalType: FirTypeRef get() = originalExpression.typeRef + // A FirExpressionWithSmartcast is only an effective smartcast if `smartcastStability == SmartcastStability.STABLE_VALUE`. Otherwise, + // it's the same as the `originalExpression` under the hood. The reason we still create such a smartcast expression is for diagnostics + // purpose only. + override val typeRef: FirTypeRef get() = if (smartcastStability == SmartcastStability.STABLE_VALUE) smartcastType else originalType override fun transformChildren(transformer: FirTransformer, data: D): FirExpressionWithSmartcast { originalExpression = originalExpression.transformSingle(transformer, data) diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastToNullImpl.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastToNullImpl.kt index 117b4c8702b..f9fe37f7e30 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastToNullImpl.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/expressions/impl/FirExpressionWithSmartcastToNullImpl.kt @@ -16,11 +16,13 @@ import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.fir.visitors.FirTransformer import org.jetbrains.kotlin.fir.visitors.FirVisitor import org.jetbrains.kotlin.fir.visitors.transformSingle +import org.jetbrains.kotlin.types.SmartcastStability class FirExpressionWithSmartcastToNullImpl( override var originalExpression: FirQualifiedAccessExpression, - override val typeRef: FirTypeRef, - override val typesFromSmartCast: Collection + override val smartcastType: FirTypeRef, + override val typesFromSmartCast: Collection, + override val smartcastStability: SmartcastStability ) : FirExpressionWithSmartcastToNull() { init { assert(originalExpression.typeRef is FirResolvedTypeRef) @@ -34,6 +36,10 @@ class FirExpressionWithSmartcastToNullImpl( override val extensionReceiver: FirExpression get() = originalExpression.extensionReceiver override val calleeReference: FirReference get() = originalExpression.calleeReference override val originalType: FirTypeRef get() = originalExpression.typeRef + // A FirExpressionWithSmartcast is only an effective smartcast if `smartcastStability == SmartcastStability.STABLE_VALUE`. Otherwise, + // it's the same as the `originalExpression` under the hood. The reason we still create such a smartcast expression is for diagnostics + // purpose only. + override val typeRef: FirTypeRef get() = if (smartcastStability == SmartcastStability.STABLE_VALUE) smartcastType else originalType override fun transformChildren(transformer: FirTransformer, data: D): FirExpressionWithSmartcastToNull { originalExpression = originalExpression.transformSingle(transformer, data) diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/FieldSets.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/FieldSets.kt index 64f13f0dde8..77eae4678df 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/FieldSets.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/FieldSets.kt @@ -83,4 +83,6 @@ object FieldSets { val modality = field(modalityType, nullable = true) val scopeProvider = field("scopeProvider", firScopeProviderType) + + val smartcastStability = field(smartcastStabilityType) } diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt index 03bf0c8fbd7..0e1db276ab2 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.fir.tree.generator.FieldSets.name import org.jetbrains.kotlin.fir.tree.generator.FieldSets.receivers import org.jetbrains.kotlin.fir.tree.generator.FieldSets.returnTypeRef import org.jetbrains.kotlin.fir.tree.generator.FieldSets.scopeProvider +import org.jetbrains.kotlin.fir.tree.generator.FieldSets.smartcastStability import org.jetbrains.kotlin.fir.tree.generator.FieldSets.status import org.jetbrains.kotlin.fir.tree.generator.FieldSets.superTypeRefs import org.jetbrains.kotlin.fir.tree.generator.FieldSets.symbol @@ -458,6 +459,8 @@ object NodeConfigurator : AbstractFieldConfigurator(FirTreeBuild +field("originalExpression", qualifiedAccessExpression) +field("typesFromSmartCast", "Collection", null, customType = coneKotlinTypeType) +field("originalType", typeRef) + +field("smartcastType", typeRef) + +smartcastStability } expressionWithSmartcastToNull.configure { diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt index 807b29ee159..51ae3c6889f 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.SmartcastStability import org.jetbrains.kotlin.types.Variance val sourceElementType = type("fir", "FirSourceElement") @@ -31,6 +32,7 @@ val nameType = type(Name::class) val visibilityType = type(Visibility::class) val effectiveVisibilityType = type("descriptors", "EffectiveVisibility") val modalityType = type(Modality::class) +val smartcastStabilityType = type(SmartcastStability::class) val fqNameType = type(FqName::class) val classIdType = type(ClassId::class) val annotationUseSiteTargetType = type(AnnotationUseSiteTarget::class) diff --git a/core/compiler.common/src/org/jetbrains/kotlin/types/SmartcastStability.kt b/core/compiler.common/src/org/jetbrains/kotlin/types/SmartcastStability.kt new file mode 100644 index 00000000000..a7ba64f1806 --- /dev/null +++ b/core/compiler.common/src/org/jetbrains/kotlin/types/SmartcastStability.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2010-2021 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.types + +/** + * See https://kotlinlang.org/spec/type-inference.html#smart-cast-sink-stability for explanation on smartcast stability. The "mutable value + * capturing" and "concurrent writes" categories in the spec are covered by [CAPTURED_VARIABLE] and [MUTABLE_PROPERTY] together. + * Specifically, we only do capture analysis on local mutable properties. Non-local ones are considered to be always unstable (assuming + * some concurrent writes are always present). + */ +enum class SmartcastStability(private val str: String, val description: String = str) { + // Local value, or parameter, or private / internal member value without open / custom getter, + // or protected / public member value from the same module without open / custom getter + // Smart casts are completely safe + STABLE_VALUE("stable val"), + + // Member value with open / custom getter + // Smart casts are not safe + PROPERTY_WITH_GETTER("custom getter", "property that has open or custom getter"), + + // Protected / public member value from another module + // Smart casts are not safe + ALIEN_PUBLIC_PROPERTY("alien public", "public API property declared in different module"), + + // Local variable already captured by a changing closure + // Smart casts are not safe + CAPTURED_VARIABLE("captured var", "local variable that is captured by a changing closure"), + + // Member variable regardless of its visibility + // Smart casts are not safe + MUTABLE_PROPERTY("member", "mutable property that could have been changed by this time"), + + // A delegated property. + // Smart casts are not safe + DELEGATED_PROPERTY("delegate", "delegated property that could have been changed by this time"), +}