[FIR] Allow, but also warn, KMutableProperty with captured types

^KT-63589 Fixed
This commit is contained in:
Alejandro Serrano Mena
2023-12-13 12:22:26 +01:00
committed by Space Team
parent 80d9933543
commit b077293396
17 changed files with 200 additions and 24 deletions
@@ -2662,6 +2662,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
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),
@@ -1885,6 +1885,10 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
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
@@ -2266,6 +2266,11 @@ internal class UnsupportedClassLiteralsWithEmptyLhsImpl(
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,
@@ -4177,6 +4177,18 @@ public class DiagnosticCompilerTestFirTestdataTestGenerated extends AbstractDiag
runTest("compiler/fir/analysis-tests/testData/resolve/references/leakedImplicitType.kt");
}
@Test
@TestMetadata("mutablePropertyGenericButNotCapturedType.kt")
public void testMutablePropertyGenericButNotCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyGenericButNotCapturedType.kt");
}
@Test
@TestMetadata("mutablePropertyWithCapturedType.kt")
public void testMutablePropertyWithCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyWithCapturedType.kt");
}
@Test
@TestMetadata("referenceToExtension.kt")
public void testReferenceToExtension() throws Exception {
@@ -4177,6 +4177,18 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFirTestDataTestGenerated
runTest("compiler/fir/analysis-tests/testData/resolve/references/leakedImplicitType.kt");
}
@Test
@TestMetadata("mutablePropertyGenericButNotCapturedType.kt")
public void testMutablePropertyGenericButNotCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyGenericButNotCapturedType.kt");
}
@Test
@TestMetadata("mutablePropertyWithCapturedType.kt")
public void testMutablePropertyWithCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyWithCapturedType.kt");
}
@Test
@TestMetadata("referenceToExtension.kt")
public void testReferenceToExtension() throws Exception {
@@ -0,0 +1,34 @@
FILE: mutablePropertyGenericButNotCapturedType.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 testWithinClass(): R|kotlin/Unit| {
lval mutableProperty: R|kotlin/reflect/KMutableProperty1<Klass<T>, Generic<T>>| = Q|Klass|::R|/Klass.mutableProperty|
R|<local>/mutableProperty|.R|SubstitutionOverride<kotlin/reflect/KMutableProperty1.set: R|kotlin/Unit|>|(this@R|/Klass|, R|/Generic.Generic|<R|T|>())
}
}
public final fun testConcreteType(): R|kotlin/Unit| {
lval mutableProperty: R|kotlin/reflect/KMutableProperty1<Klass<kotlin/Int>, Generic<kotlin/Int>>| = Q|Klass|::R|SubstitutionOverride</Klass.mutableProperty: R|Generic<kotlin/Int>|>|
R|<local>/mutableProperty|.R|SubstitutionOverride<kotlin/reflect/KMutableProperty1.set: R|kotlin/Unit|>|(R|/Klass.Klass|<R|kotlin/Int|>(), R|/Generic.Generic|<R|kotlin/Int|>())
}
public final fun <A> testGenericType(): R|kotlin/Unit| {
lval mutableProperty: R|kotlin/reflect/KMutableProperty1<Klass<A>, Generic<A>>| = Q|Klass|::R|SubstitutionOverride</Klass.mutableProperty: R|Generic<A>|>|
R|<local>/mutableProperty|.R|SubstitutionOverride<kotlin/reflect/KMutableProperty1.set: R|kotlin/Unit|>|(R|/Klass.Klass|<R|A|>(), R|/Generic.Generic|<R|A|>())
}
public final fun <S : R|kotlin/CharSequence|> testGenericTypeWithBounds(): R|kotlin/Unit| {
lval mutableProperty: R|kotlin/reflect/KMutableProperty1<Klass<S>, Generic<S>>| = Q|Klass|::R|SubstitutionOverride</Klass.mutableProperty: R|Generic<S>|>|
R|<local>/mutableProperty|.R|SubstitutionOverride<kotlin/reflect/KMutableProperty1.set: R|kotlin/Unit|>|(R|/Klass.Klass|<R|S|>(), R|/Generic.Generic|<R|S|>())
}
@@ -0,0 +1,24 @@
class Generic<T>
class Klass<T> {
var mutableProperty: Generic<T> = Generic()
fun testWithinClass() {
val mutableProperty = Klass<T>::mutableProperty
mutableProperty.set(this, Generic<T>())
}
}
fun testConcreteType() {
val mutableProperty = Klass<Int>::mutableProperty
mutableProperty.set(Klass<Int>(), Generic<Int>())
}
fun <A> testGenericType() {
val mutableProperty = Klass<A>::mutableProperty
mutableProperty.set(Klass<A>(), Generic<A>())
}
fun <S: CharSequence> testGenericTypeWithBounds() {
val mutableProperty = Klass<S>::mutableProperty
mutableProperty.set(Klass<S>(), Generic<S>())
}
@@ -0,0 +1,21 @@
FILE: mutablePropertyWithCapturedType.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/KMutableProperty1<Klass<*>, Generic<out kotlin/Any?>>| = Q|Klass|::R|SubstitutionOverride</Klass.mutableProperty: R|Generic<CapturedType(*)>|>|
R|<local>/mutableProperty|.R|SubstitutionOverride<kotlin/reflect/KMutableProperty1.set: R|kotlin/Unit|>|(R|/Klass.Klass|<R|kotlin/Int|>(), R|/Generic.Generic|<R|kotlin/String|>())
}
@@ -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.set(Klass<Int>(), Generic<String>())
}
@@ -4177,6 +4177,18 @@ public class FirLightTreeDiagnosticsTestGenerated extends AbstractFirLightTreeDi
runTest("compiler/fir/analysis-tests/testData/resolve/references/leakedImplicitType.kt");
}
@Test
@TestMetadata("mutablePropertyGenericButNotCapturedType.kt")
public void testMutablePropertyGenericButNotCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyGenericButNotCapturedType.kt");
}
@Test
@TestMetadata("mutablePropertyWithCapturedType.kt")
public void testMutablePropertyWithCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyWithCapturedType.kt");
}
@Test
@TestMetadata("referenceToExtension.kt")
public void testReferenceToExtension() throws Exception {
@@ -4177,6 +4177,18 @@ public class FirPsiDiagnosticTestGenerated extends AbstractFirPsiDiagnosticTest
runTest("compiler/fir/analysis-tests/testData/resolve/references/leakedImplicitType.kt");
}
@Test
@TestMetadata("mutablePropertyGenericButNotCapturedType.kt")
public void testMutablePropertyGenericButNotCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyGenericButNotCapturedType.kt");
}
@Test
@TestMetadata("mutablePropertyWithCapturedType.kt")
public void testMutablePropertyWithCapturedType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/references/mutablePropertyWithCapturedType.kt");
}
@Test
@TestMetadata("referenceToExtension.kt")
public void testReferenceToExtension() throws Exception {
@@ -892,6 +892,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
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") {
@@ -507,6 +507,7 @@ object FirErrors {
val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS: KtDiagnosticFactory0 by error0<KtExpression>()
val EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS: KtDiagnosticFactory1<ConeKotlinType> by error1<PsiElement, ConeKotlinType>()
val UNSUPPORTED_CLASS_LITERALS_WITH_EMPTY_LHS: KtDiagnosticFactory0 by error0<KtElement>()
val MUTABLE_PROPERTY_WITH_CAPTURED_TYPE: KtDiagnosticFactory0 by warning0<PsiElement>()
// overrides
val NOTHING_TO_OVERRIDE: KtDiagnosticFactory1<FirCallableSymbol<*>> by error1<KtModifierListOwner, FirCallableSymbol<*>>(SourceElementPositioningStrategies.OVERRIDE_MODIFIER)
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.analysis.checkers.classKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
@@ -18,29 +19,32 @@ import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.expressions.FirCallableReferenceAccess
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.references.resolved
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
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)
// UNRESOLVED_REFERENCE will be reported separately.
val reference = expression.calleeReference.resolved ?: return
val referredSymbol = reference.resolvedSymbol
val source = reference.source ?: return
if (source.kind is KtFakeSourceElementKind) return
checkReferenceIsToAllowedMember(referredSymbol, source, context, reporter)
checkCapturedTypeInMutableReference(expression, referredSymbol, source, context, reporter)
}
// See FE 1.0 [DoubleColonExpressionResolver#checkReferenceIsToAllowedMember]
private fun checkReferenceIsToAllowedMember(
callableReferenceAccess: FirCallableReferenceAccess,
referredSymbol: FirBasedSymbol<*>,
source: KtSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
// UNRESOLVED_REFERENCE will be reported separately.
val reference = callableReferenceAccess.calleeReference.resolved ?: return
val source = reference.source ?: return
if (source.kind is KtFakeSourceElementKind) return
val referredSymbol = reference.resolvedSymbol
if (referredSymbol is FirConstructorSymbol && referredSymbol.getContainingClassSymbol(context.session)?.classKind == ClassKind.ANNOTATION_CLASS) {
reporter.reportOn(source, FirErrors.CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR, context)
}
@@ -50,4 +54,20 @@ object FirCallableReferenceChecker : FirQualifiedAccessExpressionChecker() {
reporter.reportOn(source, FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED, referredSymbol, context)
}
}
private fun checkCapturedTypeInMutableReference(
callableReferenceAccess: FirCallableReferenceAccess,
referredSymbol: FirBasedSymbol<*>,
source: KtSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (!callableReferenceAccess.resolvedType.isKMutableProperty(context.session)) return
if (referredSymbol !is FirCallableSymbol<*>) return
val returnType = context.returnTypeCalculator.tryCalculateReturnType(referredSymbol)
if (returnType.type.hasCapture()) {
reporter.reportOn(source, FirErrors.MUTABLE_PROPERTY_WITH_CAPTURED_TYPE, context)
}
}
}
@@ -403,6 +403,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZ
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
@@ -1515,6 +1516,7 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
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 in mutable property reference. Usages of 'set' may lead to cast exceptions.")
// Value classes
map.put(VALUE_CLASS_NOT_TOP_LEVEL, "Value class cannot be local or inner.")
@@ -137,3 +137,5 @@ fun ConeKotlinType.renderReadableWithFqNames(): String {
}
fun ConeKotlinType.hasError(): Boolean = contains { it is ConeErrorType }
fun ConeKotlinType.hasCapture(): Boolean = contains { it is ConeCapturedType }
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation
import org.jetbrains.kotlin.resolve.calls.inference.runTransaction
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.expressions.CoercionStrategy
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
@@ -175,7 +176,19 @@ private fun buildResultingTypeAndAdaptation(
contextReceivers = fir.contextReceivers.map { it.typeRef.coneType }
) to callableReferenceAdaptation
}
is FirVariable -> createKPropertyType(fir, receiverType, returnTypeRef, candidate) to null
is FirVariable -> {
val returnType = returnTypeRef.type
val isMutable = fir.canBeMutableReference(candidate)
val propertyType = when {
isMutable && returnType.hasCapture() ->
// capturing types in mutable property references is unsound in general
context.inferenceComponents.resultTypeResolver.typeApproximator
.approximateToSuperType(returnType, TypeApproximatorConfiguration.InternalTypesApproximation) as? ConeKotlinType
?: returnType
else -> returnType
}
createKPropertyType(receiverType, propertyType, isMutable) to null
}
else -> ConeErrorType(ConeUnsupportedCallableReferenceTarget(candidate)) to null
}
}
@@ -482,20 +495,6 @@ fun ConeKotlinType.isKCallableType(): Boolean {
return this.classId == StandardClassIds.KCallable
}
private fun createKPropertyType(
propertyOrField: FirVariable,
receiverType: ConeKotlinType?,
returnTypeRef: FirResolvedTypeRef,
candidate: Candidate,
): ConeKotlinType {
val propertyType = returnTypeRef.type
return org.jetbrains.kotlin.fir.resolve.createKPropertyType(
receiverType,
propertyType,
isMutable = propertyOrField.canBeMutableReference(candidate)
)
}
private fun FirVariable.canBeMutableReference(candidate: Candidate): Boolean {
if (!isVar) return false
if (this is FirField) return true