[Analysis API] Add recursive value classes handling to `AbstractTypeMapper`

Without this fix, calls to `isPrimitiveBacked` and `valueClassLoweringKind` on recursive value classes result in endless loops and SOE.

^KT-72227 fixed
diff --git a/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forDeclaration/recursiveSingleValueClass.kt b/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forDeclaration/recursiveSingleValueClass.kt
index a95a91b..1b2990e 100644
--- a/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forDeclaration/recursiveSingleValueClass.kt
+++ b/analysis/analysis-api/testData/components/psiTypeProvider/psiType/forDeclaration/recursiveSingleValueClass.kt
@@ -1,5 +1,3 @@
-// IGNORE_FIR
-
 package pack
 
 @JvmInline
diff --git a/compiler/backend.common.jvm/src/org/jetbrains/kotlin/types/AbstractTypeMapper.kt b/compiler/backend.common.jvm/src/org/jetbrains/kotlin/types/AbstractTypeMapper.kt
index a803c7a..106aa86 100644
--- a/compiler/backend.common.jvm/src/org/jetbrains/kotlin/types/AbstractTypeMapper.kt
+++ b/compiler/backend.common.jvm/src/org/jetbrains/kotlin/types/AbstractTypeMapper.kt
@@ -221,8 +221,22 @@
         type: KotlinTypeMarker
     ): Boolean = context.typeContext.isPrimitiveBacked(type)
 
-    private fun TypeSystemCommonBackendContext.isPrimitiveBacked(type: KotlinTypeMarker): Boolean =
-        !type.isNullableType() &&
-                (type is SimpleTypeMarker && type.isPrimitiveType() ||
-                        type.typeConstructor().getValueClassProperties()?.singleOrNull()?.let { isPrimitiveBacked(it.second) } == true)
+    private fun TypeSystemCommonBackendContext.isPrimitiveBacked(
+        type: KotlinTypeMarker,
+        visited: HashSet<TypeConstructorMarker> = hashSetOf()
+    ): Boolean {
+        if (type.isNullableType()) {
+            return false
+        }
+
+        if (type is SimpleTypeMarker && type.isPrimitiveType()) {
+            return true
+        }
+
+        val typeConstructor = type.typeConstructor()
+
+        return visited.add(typeConstructor) &&
+                typeConstructor.getValueClassProperties()?.singleOrNull()
+                    ?.let { isPrimitiveBacked(it.second, visited) } == true
+    }
 }
diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt
index 05af692..4daba93 100644
--- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt
+++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt
@@ -527,7 +527,21 @@
 
     override fun TypeConstructorMarker.isMultiFieldValueClass(): Boolean {
         val fields = getValueClassProperties() ?: return false
-        return this@ConeTypeContext.valueClassLoweringKind(fields) == ValueClassKind.MultiField
+        return isMultiFieldValueClassRecursionAware(fields, visited = hashSetOf())
+    }
+
+    private fun TypeConstructorMarker.isMultiFieldValueClassRecursionAware(
+        fields: List<Pair<Name, RigidTypeMarker>>,
+        visited: MutableSet<TypeConstructorMarker>,
+    ): Boolean {
+        if (fields.size > 1) return true
+        val fieldType = fields.singleOrNull()?.second ?: return false
+        if (!visited.add(this)) return false
+
+        val typeConstructor = fieldType.typeConstructor()
+        return !fieldType.isNullableType() && typeConstructor.getValueClassProperties()?.let {
+            typeConstructor.isMultiFieldValueClassRecursionAware(it, visited)
+        } == true
     }
 
     override fun TypeConstructorMarker.getValueClassProperties(): List<Pair<Name, RigidTypeMarker>>? {