fixup! [FIR JS] Support dynamic type intersections
diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt
index e6421ec..d98006d 100644
--- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt
+++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt
@@ -235,8 +235,12 @@
explicitReceiver: FirExpression? = null,
isDelegate: Boolean = false
): IrSymbol? {
- val dispatchReceiverLookupTag = when (dispatchReceiver) {
- is FirNoReceiverExpression -> {
+ val dispatchReceiverLookupTag = when {
+ dispatchReceiver is FirExpressionWithSmartcastToNull && dispatchReceiver.smartcastType.coneType is ConeDynamicType -> {
+ val coneType = dispatchReceiver.smartcastTypeWithoutNullableNothing.coneType
+ coneType.findClassRepresentation(coneType, declarationStorage.session)
+ }
+ dispatchReceiver is FirNoReceiverExpression -> {
val containingClass = containingClass()
if (containingClass != null && containingClass.classId != StandardClassIds.Any) {
// Make sure that symbol is not extension and is not from inline class
diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt
index be09700..5e4a108 100644
--- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt
+++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt
@@ -116,41 +116,21 @@
}
@OptIn(PrivateForInline::class)
- inline fun <D : FirCallableSymbol<*>> mapScope(
- scope: FirTypeScope,
- name: Name,
- processCallables: FirScope.(Name, (D) -> Unit) -> Unit,
- ): Pair<FirTypeScope, List<D>>? {
- val resultForScope = mutableListOf<D>()
-
- scope.processCallables(name) {
- if (it !is FirConstructorSymbol) {
- resultForScope.add(it)
- }
- }
-
- return resultForScope.takeIf { it.isNotEmpty() }?.let {
- scope to it
- }
- }
-
- @OptIn(PrivateForInline::class)
inline fun <D : FirCallableSymbol<*>> collectMembersByScope(
name: Name,
processCallables: FirScope.(Name, (D) -> Unit) -> Unit
): MembersByScope<D> {
- val (dynamics, nonDynamics) = scopes.partition { it is FirDynamicScope }
+ return scopes.mapNotNull { scope ->
+ val resultForScope = mutableListOf<D>()
+ scope.processCallables(name) {
+ if (it !is FirConstructorSymbol) {
+ resultForScope.add(it)
+ }
+ }
- val resultForNonDynamics = nonDynamics.mapNotNull {
- mapScope(it, name, processCallables)
- }
-
- if (resultForNonDynamics.isNotEmpty()) {
- return resultForNonDynamics
- }
-
- return dynamics.mapNotNull {
- mapScope(it, name, processCallables)
+ resultForScope.takeIf { it.isNotEmpty() }?.let {
+ scope to it
+ }
}
}
diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt
index e0a7d77b..20fc41b 100644
--- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt
+++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeIntersector.kt
@@ -71,14 +71,12 @@
ConeIntegerLiteralIntersector.findCommonIntersectionType(filteredEqualTypes)?.let { return it }
- val (dynamics, filteredEqualNonDynamicTypes) = filteredEqualTypes.partition { it is ConeDynamicType }
-
/*
* For the case like it(ft(String..String?), String?), where ft(String..String?) == String?, we prefer to _keep_ flexible type.
* When a == b, the former, i.e., the one in the list will be filtered out, and the other one will remain.
* So, here, we sort the interim list such that flexible types appear later.
*/
- val sortedEqualTypes = filteredEqualNonDynamicTypes.sortedWith { p0, p1 ->
+ val sortedEqualTypes = filteredEqualTypes.sortedWith { p0, p1 ->
when {
p0 is ConeFlexibleType && p1 is ConeFlexibleType -> 0
p0 is ConeFlexibleType -> 1
@@ -91,11 +89,9 @@
}
assert(filteredSuperAndEqualTypes.isNotEmpty(), errorMessage)
- if (filteredSuperAndEqualTypes.size < 2 && dynamics.isEmpty()) {
- return filteredSuperAndEqualTypes.single()
- }
+ if (filteredSuperAndEqualTypes.size < 2) return filteredSuperAndEqualTypes.single()
- return ConeIntersectionType(filteredSuperAndEqualTypes + dynamics.take(1))
+ return ConeIntersectionType(filteredSuperAndEqualTypes)
}
private fun filterTypes(
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 f4eab45..3d17715 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
@@ -334,33 +334,42 @@
val allTypes = typesFromSmartCast.also {
it += originalType
}
+ if (allTypes.all { it is ConeDynamicType }) return null
val intersectedType = ConeTypeIntersector.intersectTypes(session.typeContext, allTypes)
- if (intersectedType == originalType) return null
+ if (intersectedType == originalType && intersectedType !is ConeDynamicType) return null
val intersectedTypeRef = buildResolvedTypeRef {
source = expression.resultType.source?.fakeElement(KtFakeSourceElementKind.SmartCastedTypeRef)
type = intersectedType
annotations += expression.resultType.annotations
delegatedTypeRef = expression.resultType
}
- // For example, if (x == null) { ... },
- // we need to track the type without `Nothing?` so that resolution with this as receiver can go through properly.
- if (typesFromSmartCast.any { it.isNullableNothing }) {
- val typesFromSmartcastWithoutNullableNothing =
- typesFromSmartCast.filterTo(mutableListOf()) { !it.isNullableNothing }.also {
- it += originalType
- }
- val intersectedTypeWithoutNullableNothing =
- ConeTypeIntersector.intersectTypes(session.typeContext, typesFromSmartcastWithoutNullableNothing)
- val intersectedTypeRefWithoutNullableNothing = buildResolvedTypeRef {
+
+ val reducedTypes = when {
+ // For example, if (x is String) { ... }, where x: dynamic
+ // the dynamic type will "consume" all other, erasing information
+ intersectedType is ConeDynamicType -> {
+ typesFromSmartCast.filterTo(mutableListOf()) { it !is ConeDynamicType }
+ }
+ // For example, if (x == null) { ... },
+ // we need to track the type without `Nothing?` so that resolution with this as receiver can go through properly.
+ typesFromSmartCast.any { it.isNullableNothing } -> {
+ typesFromSmartCast.filterTo(mutableListOf()) { !it.isNullableNothing }.also { it += originalType }
+ }
+ else -> null
+ }
+
+ if (reducedTypes != null) {
+ val reducedIntersectedType = ConeTypeIntersector.intersectTypes(session.typeContext, reducedTypes)
+ val reducedIntersectedTypeRef = buildResolvedTypeRef {
source = expression.resultType.source?.fakeElement(KtFakeSourceElementKind.SmartCastedTypeRef)
- type = intersectedTypeWithoutNullableNothing
+ type = reducedIntersectedType
annotations += expression.resultType.annotations
delegatedTypeRef = expression.resultType
}
return smartcastToNullBuilder().apply {
originalExpression = expression
smartcastType = intersectedTypeRef
- smartcastTypeWithoutNullableNothing = intersectedTypeRefWithoutNullableNothing
+ smartcastTypeWithoutNullableNothing = reducedIntersectedTypeRef
this.typesFromSmartCast = typesFromSmartCast
this.smartcastStability = smartcastStability
}
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt
index 878081b..de65c49 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt
@@ -9,14 +9,13 @@
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.declarations.utils.isInner
+import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcastToNull
import org.jetbrains.kotlin.fir.expressions.builder.buildResolvedQualifier
-import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
-import org.jetbrains.kotlin.fir.resolve.ScopeSession
+import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.*
-import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
-import org.jetbrains.kotlin.fir.resolve.typeForQualifier
+import org.jetbrains.kotlin.fir.scopes.FakeOverrideTypeCalculator
import org.jetbrains.kotlin.fir.scopes.FirScope
import org.jetbrains.kotlin.fir.scopes.impl.FirDefaultStarImportingScope
import org.jetbrains.kotlin.fir.scopes.impl.importedFromObjectData
@@ -91,15 +90,15 @@
processScopeMembers: FirScope.(processor: (T) -> Unit) -> Unit
): ProcessResult {
var empty = true
- val scope = dispatchReceiverValue.scope(session, scopeSession) ?: return ProcessResult.SCOPE_EMPTY
- scope.processScopeMembers { candidate ->
+
+ fun processCandidate(candidate: T, scope: FirScope) {
empty = false
if (candidate is FirCallableSymbol<*> &&
(implicitExtensionInvokeMode || candidate.hasConsistentExtensionReceiver(extensionReceiver))
) {
val fir = candidate.fir
if ((fir as? FirConstructor)?.isInner == false) {
- return@processScopeMembers
+ return
}
output.consumeCandidate(
@@ -121,6 +120,9 @@
}
}
+ val scope = dispatchReceiverValue.scope(session, scopeSession) ?: return ProcessResult.SCOPE_EMPTY
+ scope.processScopeMembers { processCandidate(it, scope) }
+
if (extensionReceiver == null) {
val withSynthetic = FirSyntheticPropertiesScope(session, scope)
withSynthetic.processScopeMembers { symbol ->
@@ -128,6 +130,15 @@
output.consumeCandidate(symbol, dispatchReceiverValue, null, scope)
}
}
+
+ val maybeSmartcastToNullReceiver = dispatchReceiverValue.receiverExpression as? FirExpressionWithSmartcastToNull
+ val maybeDynamicType = maybeSmartcastToNullReceiver?.smartcastType?.coneType as? ConeDynamicType
+ val maybeDynamicScope = maybeDynamicType?.scope(session, scopeSession, FakeOverrideTypeCalculator.DoNothing)
+
+ if (empty && maybeDynamicScope != null) {
+ maybeDynamicScope.processScopeMembers { processCandidate(it, maybeDynamicScope) }
+ }
+
return if (empty) ProcessResult.SCOPE_EMPTY else ProcessResult.FOUND
}