From ee22d0b9380d5f02742234897d911a3f5e1c3219 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Sat, 6 Jun 2020 20:29:28 +0300 Subject: [PATCH] FIR IDE: introduce TypeInfo as a wrapper for types in high level API Needed for correct handling types lifecycle --- ...ExpressionsSmartcastHighlightingVisitor.kt | 4 +- .../frontend/api/FrontendAnalysisSession.kt | 9 +- .../frontend/api/ImplicitReceiverSmartCast.kt | 4 +- .../kotlin/idea/frontend/api/TypeInfo.kt | 28 ++++++ .../idea/frontend/api/fir/ConeTypeInfo.kt | 98 +++++++++++++++++++ .../frontend/api/fir/FirAnalysisSession.kt | 69 +++++++------ 6 files changed, 173 insertions(+), 39 deletions(-) create mode 100644 idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/TypeInfo.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/ConeTypeInfo.kt diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt index 7099a0c64bf..b802fa5665e 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt @@ -29,7 +29,7 @@ internal class ExpressionsSmartcastHighlightingVisitor( KotlinIdeaAnalysisBundle.message( "0.smart.cast.to.1", receiverName, - analysisSession.renderType(type) + type.asDenotableTypeStringRepresentation() ) ).textAttributes = org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors.SMART_CAST_RECEIVER } @@ -39,7 +39,7 @@ internal class ExpressionsSmartcastHighlightingVisitor( getSmartCastTarget(expression), KotlinIdeaAnalysisBundle.message( "smart.cast.to.0", - analysisSession.renderType(type) + type.asDenotableTypeStringRepresentation() ) ).textAttributes = org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors.SMART_CAST_VALUE } diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt index 284ee484c52..f420db0d37c 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt @@ -5,20 +5,19 @@ package org.jetbrains.kotlin.idea.frontend.api +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.types.model.KotlinTypeMarker abstract class FrontendAnalysisSession : Invalidatable { + abstract fun getSmartCastedToTypes(expression: KtExpression): Collection? abstract fun getImplicitReceiverSmartCasts(expression: KtExpression): Collection - abstract fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): KotlinTypeMarker? + abstract fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): TypeInfo? - abstract fun renderType(type: KotlinTypeMarker): String - - abstract fun getKtExpressionType(expression: KtExpression): KotlinTypeMarker? + abstract fun getKtExpressionType(expression: KtExpression): TypeInfo? abstract fun isSubclassOf(klass: KtClassOrObject, superClassId: ClassId): Boolean diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt index 8b31bffd649..1a93bc286de 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt @@ -5,9 +5,7 @@ package org.jetbrains.kotlin.idea.frontend.api -import org.jetbrains.kotlin.types.model.KotlinTypeMarker - -data class ImplicitReceiverSmartCast(val types: Collection, val kind: ImplicitReceiverSmartcastKind) +data class ImplicitReceiverSmartCast(val types: Collection, val kind: ImplicitReceiverSmartcastKind) enum class ImplicitReceiverSmartcastKind { DISPATCH, EXTENSION diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/TypeInfo.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/TypeInfo.kt new file mode 100644 index 00000000000..c0390c13f73 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/TypeInfo.kt @@ -0,0 +1,28 @@ +/* + * 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.idea.frontend.api + +import org.jetbrains.kotlin.name.ClassId + +abstract class TypeInfo : Invalidatable { + abstract fun isClassType(): Boolean + abstract fun classIdIfClassTypeOrError(): ClassId + + abstract fun isErrorType(): Boolean + + abstract fun asDenotableTypeStringRepresentation(): String + + abstract fun isEqualTo(other: TypeInfo): Boolean + abstract fun isSubTypeOf(superType: TypeInfo): Boolean + + abstract fun isDefinitelyNullable(): Boolean + abstract fun isDefinitelyNotNull(): Boolean + + override fun toString(): String = asDenotableTypeStringRepresentation() +} + +class ErrorTypeClassIdAccessException(override val message: String? = null) : IllegalStateException() +class ClassTypeExpectedException(override val message: String? = null) : IllegalStateException() \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/ConeTypeInfo.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/ConeTypeInfo.kt new file mode 100644 index 00000000000..b79ae9232d7 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/ConeTypeInfo.kt @@ -0,0 +1,98 @@ +/* + * 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.idea.frontend.api.fir + +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl +import org.jetbrains.kotlin.idea.frontend.api.ClassTypeExpectedException +import org.jetbrains.kotlin.idea.frontend.api.ErrorTypeClassIdAccessException +import org.jetbrains.kotlin.idea.frontend.api.Invalidatable +import org.jetbrains.kotlin.idea.frontend.api.TypeInfo +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.AbstractTypeCheckerContext +import java.lang.ref.WeakReference + +internal class ConeTypeInfo( + coneType: ConeKotlinType, + private val typeCheckerContext: ConeTypeCheckerContext, + private val validityToken: Invalidatable +) : TypeInfo() { + private val coneTypeWeakRef = WeakReference(coneType) + + override fun isClassType(): Boolean { + assertIsValid() + return coneType is ConeClassLikeType + } + + override fun isErrorType(): Boolean { + assertIsValid() + return coneType is ConeClassErrorType + } + + override fun classIdIfClassTypeOrError(): ClassId { + assertIsValid() + return when (val coneType = coneType) { + is ConeClassLikeTypeImpl -> coneType.lookupTag.classId + is ConeClassErrorType -> throw ErrorTypeClassIdAccessException() + else -> throw ClassTypeExpectedException() + } + } + + override fun asDenotableTypeStringRepresentation(): String { + assertIsValid() + return coneType.render()//TODO + } + + override fun isEqualTo(other: TypeInfo): Boolean { + assertIsValid() + other.assertIsValid() + check(other is ConeTypeInfo) + return AbstractTypeChecker.equalTypes( + typeCheckerContext as AbstractTypeCheckerContext, + coneType, + other.coneType + ) + } + + override fun isSubTypeOf(superType: TypeInfo): Boolean { + assertIsValid() + superType.assertIsValid() + check(superType is ConeTypeInfo) + return AbstractTypeChecker.isSubtypeOf( + typeCheckerContext as AbstractTypeCheckerContext, + coneType, + superType.coneType + ) + } + + override fun isDefinitelyNullable(): Boolean { + assertIsValid() + return coneType.nullability == ConeNullability.NULLABLE + } + + override fun isDefinitelyNotNull(): Boolean { + assertIsValid() + return coneType.nullability == ConeNullability.NOT_NULL + } + + override fun isValid(): Boolean { + if (coneTypeWeakRef.get() == null) return false + return validityToken.isValid() + } + + override fun invalidationReason(): String { + if (coneTypeWeakRef.get() == null) return "Cone type was garbage collected" + return validityToken.invalidationReason() + } + + private inline val coneType + get() = coneTypeWeakRef.get() ?: if (validityToken.isValid()) { + error("Cone type was garbage collected while analysis session is still valid") + } else { + error("Accessing the invalid coneType") + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt index e6c5a39fb14..a0bd055d387 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt @@ -12,10 +12,8 @@ import com.intellij.psi.PsiMethod import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.fir.FirElement -import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration -import org.jetbrains.kotlin.fir.declarations.FirClass -import org.jetbrains.kotlin.fir.declarations.FirResolvePhase -import org.jetbrains.kotlin.fir.declarations.isSuspend +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast import org.jetbrains.kotlin.fir.expressions.FirFunctionCall @@ -25,10 +23,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike import org.jetbrains.kotlin.fir.symbols.CallableId import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol -import org.jetbrains.kotlin.fir.types.ConeKotlinType -import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef -import org.jetbrains.kotlin.fir.types.coneTypeSafe -import org.jetbrains.kotlin.fir.types.render +import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.idea.fir.getOrBuildFir import org.jetbrains.kotlin.idea.fir.getOrBuildFirSafe import org.jetbrains.kotlin.idea.fir.isImplicitFunctionCall @@ -40,19 +35,24 @@ import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper.toTargetPs import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.types.model.KotlinTypeMarker -internal class FirAnalysisSession( +class FirAnalysisSession( project: Project -) : FrontendAnalysisSession(), Invalidatable by ReadActionConfinementValidityToken(project) { +) : FrontendAnalysisSession() { + private val validityToken = ReadActionConfinementValidityToken(project) + override fun isValid(): Boolean = validityToken.isValid() + override fun invalidationReason(): String = validityToken.invalidationReason() + + constructor(element: KtElement) : this(element.project) + init { assertIsValid() } - override fun getSmartCastedToTypes(expression: KtExpression): Collection? { + override fun getSmartCastedToTypes(expression: KtExpression): Collection? { assertIsValid() // TODO filter out not used smartcasts - return expression.getOrBuildFirSafe()?.typesFromSmartCast + return expression.getOrBuildFirSafe()?.typesFromSmartCast?.map { it.asTypeInfo(expression.session) } } @OptIn(ExperimentalStdlibApi::class) @@ -63,35 +63,39 @@ internal class FirAnalysisSession( if (qualifiedExpression.dispatchReceiver !is FirExpressionWithSmartcast && qualifiedExpression.extensionReceiver !is FirExpressionWithSmartcast ) return emptyList() + val session = expression.session return buildList { (qualifiedExpression.dispatchReceiver as? FirExpressionWithSmartcast)?.let { smartCasted -> - ImplicitReceiverSmartCast(smartCasted.typesFromSmartCast, ImplicitReceiverSmartcastKind.DISPATCH) + ImplicitReceiverSmartCast( + smartCasted.typesFromSmartCast.map { it.asTypeInfo(session) }, + ImplicitReceiverSmartcastKind.DISPATCH + ) }?.let(::add) (qualifiedExpression.extensionReceiver as? FirExpressionWithSmartcast)?.let { smartCasted -> - ImplicitReceiverSmartCast(smartCasted.typesFromSmartCast, ImplicitReceiverSmartcastKind.EXTENSION) + ImplicitReceiverSmartCast( + smartCasted.typesFromSmartCast.map { it.asTypeInfo(session) }, + ImplicitReceiverSmartcastKind.EXTENSION + ) }?.let(::add) } } - override fun renderType(type: KotlinTypeMarker): String { + + override fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): TypeInfo? { assertIsValid() - return type.asConeType().render() + val firDeclaration = declaration.toFir>() ?: return null + return firDeclaration.returnTypeRef.coneTypeSafe()?.asTypeInfo(declaration.session) } - override fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): KotlinTypeMarker? { + override fun getKtExpressionType(expression: KtExpression): TypeInfo? { assertIsValid() - return declaration.toFir>()?.returnTypeRef?.coneTypeSafe() - } - - override fun getKtExpressionType(expression: KtExpression): ConeKotlinType? { - assertIsValid() - return expression.toFir()?.typeRef?.coneTypeSafe() + return expression.toFir()?.typeRef?.coneTypeSafe()?.asTypeInfo(expression.session) } override fun isSubclassOf(klass: KtClassOrObject, superClassId: ClassId): Boolean { assertIsValid() var result = false - forEachSubClass(klass.toFir() ?: return false) { type -> + forEachSuperClass(klass.toFir() ?: return false) { type -> result = result || type.firClassLike(klass.session)?.symbol?.classId == superClassId } return result @@ -155,16 +159,23 @@ internal class FirAnalysisSession( private inline fun KtElement.toFir(phase: FirResolvePhase = FirResolvePhase.BODY_RESOLVE): F? = getOrBuildFir(phase) as? F - private fun forEachSubClass(firClass: FirClass<*>, action: (FirResolvedTypeRef) -> Unit) { + private fun forEachSuperClass(firClass: FirClass<*>, action: (FirResolvedTypeRef) -> Unit) { firClass.superTypeRefs.forEach { superType -> (superType as? FirResolvedTypeRef)?.let(action) - (superType.firClassLike(firClass.session) as? FirClass<*>?)?.let { forEachSubClass(it, action) } + (superType.firClassLike(firClass.session) as? FirClass<*>?)?.let { forEachSuperClass(it, action) } } } + private fun ConeKotlinType.asTypeInfo(session: FirSession) = + ConeTypeInfo(this, createTypeCheckingContext(session), validityToken) + + private fun createTypeCheckingContext(session: FirSession) = ConeTypeCheckerContext( + isErrorTypeEqualsToAnything = true, // TODO? + isStubTypeEqualsToAnything = true, // TODO? + session = session + ) + companion object { - private fun KotlinTypeMarker.asConeType(): ConeKotlinType = - this as? ConeKotlinType ?: error("$this should be ConeKotlinType") private val kotlinFunctionInvokeCallableIds = (0..23).flatMapTo(hashSetOf()) { arity -> listOf(