[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()
)
}