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
     }