[FIR JS] KT-57200: Allow dynamic varargs

^KT-57200 Fixed
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt
index d3a63e4..5bf589a 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/ConeTypeCompatibilityChecker.kt
@@ -13,7 +13,6 @@
 import org.jetbrains.kotlin.fir.declarations.fullyExpandedClass
 import org.jetbrains.kotlin.fir.resolve.getSymbolByLookupTag
 import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
-import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag
 import org.jetbrains.kotlin.fir.symbols.impl.*
 import org.jetbrains.kotlin.fir.types.*
 import org.jetbrains.kotlin.name.ClassId
@@ -239,35 +238,6 @@
         return null
     }
 
-    /**
-     * Collects the upper bounds as [ConeClassLikeType].
-     */
-    private fun ConeKotlinType?.collectUpperBounds(): Set<ConeClassLikeType> {
-        if (this == null) return emptySet()
-        return when (this) {
-            is ConeErrorType -> emptySet() // Ignore error types
-            is ConeLookupTagBasedType -> when (this) {
-                is ConeClassLikeType -> setOf(this)
-                is ConeTypeVariableType -> {
-                    (lookupTag.originalTypeParameter as? ConeTypeParameterLookupTag)?.typeParameterSymbol.collectUpperBounds()
-                }
-                is ConeTypeParameterType -> lookupTag.typeParameterSymbol.collectUpperBounds()
-                else -> throw IllegalStateException("missing branch for ${javaClass.name}")
-            }
-            is ConeDefinitelyNotNullType -> original.collectUpperBounds()
-            is ConeIntersectionType -> intersectedTypes.flatMap { it.collectUpperBounds() }.toSet()
-            is ConeFlexibleType -> upperBound.collectUpperBounds()
-            is ConeCapturedType -> constructor.supertypes?.flatMap { it.collectUpperBounds() }?.toSet().orEmpty()
-            is ConeIntegerConstantOperatorType -> setOf(getApproximatedType())
-            is ConeStubType, is ConeIntegerLiteralConstantType -> throw IllegalStateException("$this should not reach here")
-        }
-    }
-
-    private fun FirTypeParameterSymbol?.collectUpperBounds(): Set<ConeClassLikeType> {
-        if (this == null) return emptySet()
-        return resolvedBounds.flatMap { it.coneType.collectUpperBounds() }.toSet()
-    }
-
     private fun ConeKotlinType?.collectLowerBounds(): Set<ConeClassLikeType> {
         if (this == null) return emptySet()
         return when (this) {
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt
index 9525275..9ce98c2 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt
@@ -29,6 +29,7 @@
 import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
 import org.jetbrains.kotlin.fir.scopes.*
 import org.jetbrains.kotlin.fir.scopes.impl.multipleDelegatesWithTheSameSignature
+import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag
 import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
 import org.jetbrains.kotlin.fir.symbols.SymbolInternals
 import org.jetbrains.kotlin.fir.symbols.impl.*
@@ -732,3 +733,37 @@
     val lastQualified = context.qualifiedAccessOrAssignmentsOrAnnotationCalls.lastOrNull { it != this } ?: return false
     return lastQualified is FirVariableAssignment && lastQualified.lValue == this
 }
+
+/**
+ * Collects the upper bounds as [ConeClassLikeType].
+ */
+fun ConeKotlinType?.collectUpperBounds(): Set<ConeClassLikeType> {
+    if (this == null) return emptySet()
+    return when (this) {
+        is ConeErrorType -> emptySet() // Ignore error types
+        is ConeLookupTagBasedType -> when (this) {
+            is ConeClassLikeType -> setOf(this)
+            is ConeTypeVariableType -> {
+                (lookupTag.originalTypeParameter as? ConeTypeParameterLookupTag)?.typeParameterSymbol.collectUpperBounds()
+            }
+            is ConeTypeParameterType -> lookupTag.typeParameterSymbol.collectUpperBounds()
+            else -> throw IllegalStateException("missing branch for ${javaClass.name}")
+        }
+        is ConeDefinitelyNotNullType -> original.collectUpperBounds()
+        is ConeIntersectionType -> intersectedTypes.flatMap { it.collectUpperBounds() }.toSet()
+        is ConeFlexibleType -> upperBound.collectUpperBounds()
+        is ConeCapturedType -> constructor.supertypes?.flatMap { it.collectUpperBounds() }?.toSet().orEmpty()
+        is ConeIntegerConstantOperatorType -> setOf(getApproximatedType())
+        is ConeStubType, is ConeIntegerLiteralConstantType -> throw IllegalStateException("$this should not reach here")
+    }
+}
+
+private fun FirTypeParameterSymbol?.collectUpperBounds(): Set<ConeClassLikeType> {
+    if (this == null) return emptySet()
+    return resolvedBounds.flatMap { it.coneType.collectUpperBounds() }.toSet()
+}
+
+fun ConeKotlinType.leastUpperBound(session: FirSession): ConeKotlinType {
+    val upperBounds = collectUpperBounds().takeIf { it.isNotEmpty() } ?: return session.builtinTypes.nullableAnyType.type
+    return ConeTypeIntersector.intersectTypes(session.typeContext, upperBounds)
+}
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt
index b7892d7..2ee1382 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFunctionParameterChecker.kt
@@ -12,6 +12,7 @@
 import org.jetbrains.kotlin.fir.FirElement
 import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
 import org.jetbrains.kotlin.fir.analysis.checkers.isValueClass
+import org.jetbrains.kotlin.fir.analysis.checkers.leastUpperBound
 import org.jetbrains.kotlin.fir.analysis.checkers.valOrVarKeyword
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
 import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
@@ -25,7 +26,6 @@
 import org.jetbrains.kotlin.fir.symbols.SymbolInternals
 import org.jetbrains.kotlin.fir.types.*
 import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
-import org.jetbrains.kotlin.types.AbstractTypeChecker
 
 object FirFunctionParameterChecker : FirFunctionChecker() {
     override fun check(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) {
@@ -61,10 +61,13 @@
             }
         }
 
-        val nullableNothingType = context.session.builtinTypes.nullableNothingType.coneType
         for (varargParameter in varargParameters) {
             val varargParameterType = varargParameter.returnTypeRef.coneType.arrayElementType() ?: continue
-            if (AbstractTypeChecker.isSubtypeOf(context.session.typeContext, varargParameterType, nullableNothingType) ||
+            // LUB is checked to ensure varargParameterType may
+            // never be anything except `Nothing` or `Nothing?`
+            // in case it is a complex type that quantifies
+            // over many other types.
+            if (varargParameterType.leastUpperBound(context.session).isNothingOrNullableNothing ||
                 (varargParameterType.isValueClass(context.session) && !varargParameterType.isUnsignedTypeOrNullableUnsignedType)
             // Note: comparing with FE1.0, we skip checking if the type is not primitive because primitive types are not inline. That
             // is any primitive values are already allowed by the inline check.
diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt
index 4d7c8df..865b9c7 100644
--- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt
+++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeBuiltinTypeUtils.kt
@@ -19,11 +19,12 @@
 val ConeKotlinType.isNullableAny: Boolean get() = isBuiltinType(StandardClassIds.Any, true)
 val ConeKotlinType.isNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, false)
 val ConeKotlinType.isNullableNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, true)
+val ConeKotlinType.isNothingOrNullableNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, null)
 
 val ConeKotlinType.isUnit: Boolean get() = isBuiltinType(StandardClassIds.Unit, false)
 val ConeKotlinType.isBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, false)
 val ConeKotlinType.isNullableBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, true)
-val ConeKotlinType.isBooleanOrNullableBoolean: Boolean get() = isAnyOfBuiltinType(setOf(StandardClassIds.Boolean))
+val ConeKotlinType.isBooleanOrNullableBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, null)
 
 val ConeKotlinType.isChar: Boolean get() = isBuiltinType(StandardClassIds.Char, false)
 val ConeKotlinType.isString: Boolean get() = isBuiltinType(StandardClassIds.String, false)
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 73d6470..4c5843c 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
@@ -10,7 +10,7 @@
 object ConeTypeIntersector {
     fun intersectTypes(
         context: ConeInferenceContext,
-        types: List<ConeKotlinType>
+        types: Collection<ConeKotlinType>
     ): ConeKotlinType {
         when (types.size) {
             0 -> error("Expected some types")
diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamic.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamic.fir.kt
index 99811d8..e5f5181 100644
--- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamic.fir.kt
+++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamic.fir.kt
@@ -1,18 +1,18 @@
 // SKIP_TXT
 
-fun case_1(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_1(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf(null) + args<!>).toTypedArray()
 }
 
-fun case_2(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_2(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf<Nothing>() + args<!>).toTypedArray()
 }
 
-fun case_3(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_3(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf(1) + args<!>).toTypedArray()
 }
 
-fun case_4(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_4(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf<Any?>() + args<!>).toTypedArray()
 }
 
diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamicNI.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamicNI.fir.kt
index 99811d8..e5f5181 100644
--- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamicNI.fir.kt
+++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/capturedDynamicNI.fir.kt
@@ -1,18 +1,18 @@
 // SKIP_TXT
 
-fun case_1(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_1(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf(null) + args<!>).toTypedArray()
 }
 
-fun case_2(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_2(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf<Nothing>() + args<!>).toTypedArray()
 }
 
-fun case_3(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_3(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf(1) + args<!>).toTypedArray()
 }
 
-fun case_4(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> args: dynamic) {
+fun case_4(vararg args: dynamic) {
     (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<kotlin.Nothing..kotlin.Any?!>")!>listOf<Any?>() + args<!>).toTypedArray()
 }
 
diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/varargs.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/varargs.fir.kt
deleted file mode 100644
index 30ac939..0000000
--- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/varargs.fir.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-// !MARK_DYNAMIC_CALLS
-
-fun test() {
-    v()
-    v(1)
-    v(1, "")
-}
-
-fun v(<!FORBIDDEN_VARARG_PARAMETER_TYPE!>vararg<!> d: dynamic) {
-    for (dd in d) {
-        dd.<!DEBUG_INFO_DYNAMIC!>foo<!>()
-    }
-}
diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/varargs.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/varargs.kt
index 8199005..e31aca8 100644
--- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/varargs.kt
+++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/varargs.kt
@@ -1,3 +1,4 @@
+// FIR_IDENTICAL
 // !MARK_DYNAMIC_CALLS
 
 fun test() {