[FIR] Disallow creation of KMutableProperty with captured types
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
index e66a2e8..d8e3cf1 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
@@ -2637,6 +2637,12 @@
             token,
         )
     }
+    add(FirErrors.MUTABLE_PROPERTY_WITH_CAPTURED_TYPE) { firDiagnostic ->
+        MutablePropertyWithCapturedTypeImpl(
+            firDiagnostic as KtPsiDiagnostic,
+            token,
+        )
+    }
     add(FirErrors.NOTHING_TO_OVERRIDE) { firDiagnostic ->
         NothingToOverrideImpl(
             firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a),
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
index 6f944ae..e872449 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
@@ -1868,6 +1868,10 @@
         override val diagnosticClass get() = UnsupportedClassLiteralsWithEmptyLhs::class
     }
 
+    interface MutablePropertyWithCapturedType : KtFirDiagnostic<PsiElement> {
+        override val diagnosticClass get() = MutablePropertyWithCapturedType::class
+    }
+
     interface NothingToOverride : KtFirDiagnostic<KtModifierListOwner> {
         override val diagnosticClass get() = NothingToOverride::class
         val declaration: KtCallableSymbol
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
index 674cf30..32650be5 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
@@ -2245,6 +2245,11 @@
     token: KtLifetimeToken,
 ) : KtAbstractFirDiagnostic<KtElement>(firDiagnostic, token), KtFirDiagnostic.UnsupportedClassLiteralsWithEmptyLhs
 
+internal class MutablePropertyWithCapturedTypeImpl(
+    firDiagnostic: KtPsiDiagnostic,
+    token: KtLifetimeToken,
+) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.MutablePropertyWithCapturedType
+
 internal class NothingToOverrideImpl(
     override val declaration: KtCallableSymbol,
     firDiagnostic: KtPsiDiagnostic,
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFirTestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFirTestdataTestGenerated.java
index d5a623c..e80be36 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFirTestdataTestGenerated.java
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFirTestdataTestGenerated.java
@@ -4112,6 +4112,12 @@
             }
 
             @Test
+            @TestMetadata("mutableReferenceWithCapturedType.kt")
+            public void testMutableReferenceWithCapturedType() throws Exception {
+                runTest("compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.kt");
+            }
+
+            @Test
             @TestMetadata("referenceToExtension.kt")
             public void testReferenceToExtension() throws Exception {
                 runTest("compiler/fir/analysis-tests/testData/resolve/references/referenceToExtension.kt");
diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFirTestDataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFirTestDataTestGenerated.java
index 897df82..0d119d7 100644
--- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFirTestDataTestGenerated.java
+++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFirTestDataTestGenerated.java
@@ -4112,6 +4112,12 @@
             }
 
             @Test
+            @TestMetadata("mutableReferenceWithCapturedType.kt")
+            public void testMutableReferenceWithCapturedType() throws Exception {
+                runTest("compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.kt");
+            }
+
+            @Test
             @TestMetadata("referenceToExtension.kt")
             public void testReferenceToExtension() throws Exception {
                 runTest("compiler/fir/analysis-tests/testData/resolve/references/referenceToExtension.kt");
diff --git a/compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.fir.txt b/compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.fir.txt
new file mode 100644
index 0000000..c9547d8f
--- /dev/null
+++ b/compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.fir.txt
@@ -0,0 +1,21 @@
+FILE: mutableReferenceWithCapturedType.kt
+    public final class Generic<T> : R|kotlin/Any| {
+        public constructor<T>(): R|Generic<T>| {
+            super<R|kotlin/Any|>()
+        }
+
+    }
+    public final class Klass<T> : R|kotlin/Any| {
+        public constructor<T>(): R|Klass<T>| {
+            super<R|kotlin/Any|>()
+        }
+
+        public final var mutableProperty: R|Generic<T>| = R|/Generic.Generic|<R|T|>()
+            public get(): R|Generic<T>|
+            public set(value: R|Generic<T>|): R|kotlin/Unit|
+
+    }
+    public final fun test(): R|kotlin/Unit| {
+        lval mutableProperty: R|kotlin/reflect/KProperty1<Klass<*>, Generic<out kotlin/Any?>>| = Q|Klass|::R|SubstitutionOverride</Klass.mutableProperty: R|Generic<CapturedType(*)>|>|
+        R|<local>/mutableProperty|.<Unresolved name: set>#(R|/Klass.Klass|<R|kotlin/Int|>(), R|/Generic.Generic|<R|kotlin/String|>())
+    }
diff --git a/compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.kt b/compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.kt
new file mode 100644
index 0000000..e20265a
--- /dev/null
+++ b/compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.kt
@@ -0,0 +1,9 @@
+class Generic<T>
+class Klass<T> {
+    var mutableProperty: Generic<T> = Generic()
+}
+
+fun test() {
+    val mutableProperty = Klass<*>::<!MUTABLE_PROPERTY_WITH_CAPTURED_TYPE!>mutableProperty<!>
+    mutableProperty.<!UNRESOLVED_REFERENCE!>set<!>(Klass<Int>(), Generic<String>())
+}
\ No newline at end of file
diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeDiagnosticsTestGenerated.java
index d502380..744fa67 100644
--- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeDiagnosticsTestGenerated.java
+++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeDiagnosticsTestGenerated.java
@@ -4112,6 +4112,12 @@
             }
 
             @Test
+            @TestMetadata("mutableReferenceWithCapturedType.kt")
+            public void testMutableReferenceWithCapturedType() throws Exception {
+                runTest("compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.kt");
+            }
+
+            @Test
             @TestMetadata("referenceToExtension.kt")
             public void testReferenceToExtension() throws Exception {
                 runTest("compiler/fir/analysis-tests/testData/resolve/references/referenceToExtension.kt");
diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiDiagnosticTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiDiagnosticTestGenerated.java
index d7f5f6d..e3daaa8 100644
--- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiDiagnosticTestGenerated.java
+++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiDiagnosticTestGenerated.java
@@ -4112,6 +4112,12 @@
             }
 
             @Test
+            @TestMetadata("mutableReferenceWithCapturedType.kt")
+            public void testMutableReferenceWithCapturedType() throws Exception {
+                runTest("compiler/fir/analysis-tests/testData/resolve/references/mutableReferenceWithCapturedType.kt");
+            }
+
+            @Test
             @TestMetadata("referenceToExtension.kt")
             public void testReferenceToExtension() throws Exception {
                 runTest("compiler/fir/analysis-tests/testData/resolve/references/referenceToExtension.kt");
diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt
index 3e55fd6..9c3d9dd 100644
--- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt
+++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt
@@ -886,6 +886,7 @@
             parameter<ConeKotlinType>("lhsType")
         }
         val UNSUPPORTED_CLASS_LITERALS_WITH_EMPTY_LHS by error<KtElement>()
+        val MUTABLE_PROPERTY_WITH_CAPTURED_TYPE by warning<PsiElement>()
     }
 
     val OVERRIDES by object : DiagnosticGroup("overrides") {
diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt
index c079073..4a66d03 100644
--- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt
+++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt
@@ -494,6 +494,7 @@
     val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error0<KtExpression>()
     val EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error1<PsiElement, ConeKotlinType>()
     val UNSUPPORTED_CLASS_LITERALS_WITH_EMPTY_LHS by error0<KtElement>()
+    val MUTABLE_PROPERTY_WITH_CAPTURED_TYPE by warning0<PsiElement>()
 
     // overrides
     val NOTHING_TO_OVERRIDE by error1<KtModifierListOwner, FirCallableSymbol<*>>(SourceElementPositioningStrategies.OVERRIDE_MODIFIER)
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCallableReferenceChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCallableReferenceChecker.kt
index 655d44e..7ad4d79 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCallableReferenceChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCallableReferenceChecker.kt
@@ -20,12 +20,17 @@
 import org.jetbrains.kotlin.fir.references.resolved
 import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
 import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirFieldSymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
+import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
+import org.jetbrains.kotlin.fir.types.*
 
 object FirCallableReferenceChecker : FirQualifiedAccessExpressionChecker() {
     override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
         if (expression !is FirCallableReferenceAccess) return
 
         checkReferenceIsToAllowedMember(expression, context, reporter)
+        checkDemotedMutableProperty(expression, context, reporter)
     }
 
     // See FE 1.0 [DoubleColonExpressionResolver#checkReferenceIsToAllowedMember]
@@ -50,4 +55,25 @@
             reporter.reportOn(source, FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED, referredSymbol, context)
         }
     }
+
+    private fun checkDemotedMutableProperty(
+        callableReferenceAccess: FirCallableReferenceAccess,
+        context: CheckerContext,
+        reporter: DiagnosticReporter
+    ) {
+        val reference = callableReferenceAccess.calleeReference.resolved ?: return
+        val source = reference.source?.takeIf { it.kind !is KtFakeSourceElementKind } ?: return
+        val type = callableReferenceAccess.resolvedType.takeIf { it.isKProperty(context.session) && !it.isKMutableProperty(context.session) } ?: return
+        val symbol = reference.resolvedSymbol as? FirVariableSymbol<*> ?: return
+
+        if (symbol.canBeMutableReference() && type.typeArguments[1].type?.captures() == true) {
+            reporter.reportOn(source, FirErrors.MUTABLE_PROPERTY_WITH_CAPTURED_TYPE, context)
+        }
+    }
+
+    private fun FirVariableSymbol<*>.canBeMutableReference(): Boolean = when (this) {
+        is FirFieldSymbol -> isVar
+        is FirPropertySymbol -> isVar || setterSymbol != null
+        else -> false
+    }
 }
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt
index d1faef5..517eae9 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt
@@ -400,6 +400,7 @@
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED_OR_FINAL_OR_ABSTRACT
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED_OR_FINAL_OR_ABSTRACT_WARNING
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED_WARNING
+import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUTABLE_PROPERTY_WITH_CAPTURED_TYPE
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NAMED_ARGUMENTS_NOT_ALLOWED
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NAMED_PARAMETER_NOT_FOUND
 import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NAME_FOR_AMBIGUOUS_PARAMETER
@@ -1497,6 +1498,7 @@
             RENDER_TYPE
         )
         map.put(UNSUPPORTED_CLASS_LITERALS_WITH_EMPTY_LHS, "Class literals with empty left hand side are unsupported.")
+        map.put(MUTABLE_PROPERTY_WITH_CAPTURED_TYPE, "Captured type prevents the creation of KMutableProperty, falling back to KProperty")
 
         // Value classes
         map.put(VALUE_CLASS_NOT_TOP_LEVEL, "Value class cannot be local or inner.")
diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt
index 2fd3a13..beace72 100644
--- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt
+++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt
@@ -137,3 +137,6 @@
 }
 
 fun ConeKotlinType.hasError(): Boolean = contains { it is ConeErrorType }
+
+fun ConeKotlinType.captures(): Boolean =
+    this is ConeCapturedType || typeArguments.any { it.type?.captures() == true }
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallableReferenceResolution.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallableReferenceResolution.kt
index f8bd4d0..07bd638 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallableReferenceResolution.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallableReferenceResolution.kt
@@ -474,10 +474,10 @@
     candidate: Candidate,
 ): ConeKotlinType {
     val propertyType = returnTypeRef.type
-    return org.jetbrains.kotlin.fir.resolve.createKPropertyType(
+    return createKPropertyType(
         receiverType,
         propertyType,
-        isMutable = propertyOrField.canBeMutableReference(candidate)
+        isMutable = propertyOrField.canBeMutableReference(candidate) && !propertyType.captures()
     )
 }