[FIR] Allow, but also warn, KMutableProperty with captured types
^KT-63589 Fixed
This commit is contained in:
committed by
Space Team
parent
80d9933543
commit
b077293396
+6
@@ -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),
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
+5
@@ -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,
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
+34
@@ -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|>())
|
||||
}
|
||||
Vendored
+24
@@ -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>())
|
||||
}
|
||||
Vendored
+21
@@ -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|>())
|
||||
}
|
||||
+9
@@ -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>())
|
||||
}
|
||||
+12
@@ -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 {
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
+1
@@ -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)
|
||||
|
||||
+29
-9
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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 }
|
||||
|
||||
+14
-15
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user