diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/KotlinTypeRefinerImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/types/KotlinTypeRefinerImpl.kt index e50741114ad..04d6c585247 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/KotlinTypeRefinerImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/KotlinTypeRefinerImpl.kt @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.utils.DFS @OptIn(TypeRefinement::class) class KotlinTypeRefinerImpl( private val moduleDescriptor: ModuleDescriptor, - storageManager: StorageManager + storageManager: StorageManager, ) : KotlinTypeRefiner() { private val refinedTypeCache: CacheWithNotNullValues = storageManager.createCacheWithNotNullValues() @@ -37,7 +37,7 @@ class KotlinTypeRefinerImpl( private constructor( moduleDescriptor: ModuleDescriptor, storageManager: StorageManager, - isStandalone: Boolean + isStandalone: Boolean, ) : this(moduleDescriptor, storageManager) { this.isStandalone = isStandalone } @@ -81,17 +81,24 @@ class KotlinTypeRefinerImpl( require(type is KotlinType) if (type.constructor.declarationDescriptor?.module == moduleDescriptor) return type return when { - !type.needsRefinement() -> type + // See comment on [needsRefinementHackForKtij24195] + type.needsRefinementHackForKtij24195() -> doRefineType((type as AbbreviatedType).abbreviation) - type.canBeCached() -> { - val cached = refinedTypeCache.computeIfAbsent(type.constructor) { - type.constructor.declarationDescriptor!!.defaultType.refineWithRespectToAbbreviatedTypes(this) - } + type.needsRefinement() -> doRefineType(type) - cached.restoreAdditionalTypeInformation(type) + else -> type + } + } + + private fun doRefineType(type: KotlinType): KotlinType { + return if (type.canBeCached()) { + val cached = refinedTypeCache.computeIfAbsent(type.constructor) { + type.constructor.declarationDescriptor!!.defaultType.refineWithRespectToAbbreviatedTypes(this) } - else -> type.refineWithRespectToAbbreviatedTypes(this) + cached.restoreAdditionalTypeInformation(type) + } else { + type.refineWithRespectToAbbreviatedTypes(this) } } @@ -111,6 +118,40 @@ class KotlinTypeRefinerImpl( private fun KotlinType.canBeCached(): Boolean = hasNotTrivialRefinementFactory && constructor.declarationDescriptor != null + /** + * This is a hack for https://youtrack.jetbrains.com/issue/KTIJ-24195 + * + * The rough idea is that if: + * - we see a typealias pointing to a classifier + * - ideally, we'd consider only `actual typealias`es, but we don't write `actual`-flag in metadata :( + * - and that classifier isn't visible from our module + * - then we should re-refine the abbreviation + * + * Read KTIJ-24195 comments for detailed explanation and reasoning why this hack is sufficient. + * + * Performance note: this hack amounts to running an additional `findClassAcrossModuleDependencies` on all `typealias` + * abbreviations. In most cases, the resolution of abbreviation should've happened before, so this call will just hit the cache. + * It is possible to construct a case where this call will actually have to do some non-trivial work, but: + * a) it's quite hard to write such case even knowing how our caches work, so the probability of a real-life user' code hitting that + * case is minuscule + * b) even if we somehow manage to hit that case in real code, this is just one more `getContributedClassifier` per used + * `typealias`, as all subsequent calls will be cached. + */ + private fun KotlinType.needsRefinementHackForKtij24195(): Boolean { + if (this !is AbbreviatedType) return false + if (abbreviation.constructor.declarationDescriptor !is TypeAliasDescriptor) return false + + // Would be nice to have the following line uncommented (nice optimization), but unfortunately, serialized binaries do not + // preserve `isActual`-flags + // if (!abbreviation.constructor.declarationDescriptor.isActual) return false + + val expansionDescriptorClassId = expandedType.constructor.declarationDescriptor.classId ?: return false + // Expansion invisible - need refinement hack + // NB: important to use 'findClassifier' and not 'findClass', because normally this call is expected to resolve + // a typealias descriptor, which is a Classifier but not a ClassDescriptor + return moduleDescriptor.findClassifierAcrossModuleDependencies(expansionDescriptorClassId) == null + } + @TypeRefinement override fun refineSupertypes(classDescriptor: ClassDescriptor): Collection { // Note that we can't omit refinement even if classDescriptor.module == moduleDescriptor,