[FIR] KT-59368 context receiver subtyping checker

This commit is contained in:
Tomas Husak
2023-11-29 16:52:01 +01:00
committed by Space Team
parent 3d77d09260
commit d7eb67a436
17 changed files with 181 additions and 268 deletions
@@ -2105,6 +2105,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.SUBTYPING_BETWEEN_CONTEXT_RECEIVERS) { firDiagnostic ->
SubtypingBetweenContextReceiversImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.RECURSION_IN_IMPLICIT_TYPES) { firDiagnostic ->
RecursionInImplicitTypesImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -1500,6 +1500,10 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = UnsupportedContextualDeclarationCall::class
}
interface SubtypingBetweenContextReceivers : KtFirDiagnostic<KtElement> {
override val diagnosticClass get() = SubtypingBetweenContextReceivers::class
}
interface RecursionInImplicitTypes : KtFirDiagnostic<PsiElement> {
override val diagnosticClass get() = RecursionInImplicitTypes::class
}
@@ -1800,6 +1800,11 @@ internal class UnsupportedContextualDeclarationCallImpl(
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtElement>(firDiagnostic, token), KtFirDiagnostic.UnsupportedContextualDeclarationCall
internal class SubtypingBetweenContextReceiversImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtElement>(firDiagnostic, token), KtFirDiagnostic.SubtypingBetweenContextReceivers
internal class RecursionInImplicitTypesImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
@@ -0,0 +1,27 @@
FILE: contextReceiversSubtyping.kt
public abstract interface D : R|D1|, R|D2| {
}
public abstract interface D1 : R|kotlin/Any| {
}
public abstract interface D2 : R|kotlin/Any| {
}
context(R|T|, R|D2|)
public final fun <T : R|D1|, R|D2|> foo(): R|kotlin/Unit| {
}
public abstract interface Cov<out T> : R|kotlin/Any| {
}
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
}
public final class B : R|kotlin/Any| {
public constructor(): R|B| {
super<R|kotlin/Any|>()
}
}
context(R|Cov<T>|, R|Cov<B>|)
public final fun <T : R|A|> foo(): R|kotlin/Unit| {
}
@@ -0,0 +1,19 @@
// FIR_IDENTICAL
// !LANGUAGE: +ContextReceivers
interface D: D1, D2 {}
interface D1 {}
interface D2 {}
<!SUBTYPING_BETWEEN_CONTEXT_RECEIVERS!>context(T, D2)<!>
fun <T> foo() where T: D1, T: D2 {}
interface Cov<out T> {}
class A {}
class B {}
// This sholdn't be an error since the type parameter is predetermined to be `A`,
// which causes that there can't be subtyping between the context receivers.
// However, the cheking is simplified because it is time-consuming process.
<!SUBTYPING_BETWEEN_CONTEXT_RECEIVERS!>context(Cov<T>, Cov<B>)<!>
fun <T: <!FINAL_UPPER_BOUND!>A<!>> foo() {}
@@ -1503,6 +1503,12 @@ public class FirLightTreeDiagnosticsTestGenerated extends AbstractFirLightTreeDi
runTest("compiler/fir/analysis-tests/testData/resolve/checkers/complexConflictingProjections.kt");
}
@Test
@TestMetadata("contextReceiversSubtyping.kt")
public void testContextReceiversSubtyping() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/checkers/contextReceiversSubtyping.kt");
}
@Test
@TestMetadata("importAnnotationWithRequiresOptIn.kt")
public void testImportAnnotationWithRequiresOptIn() throws Exception {
@@ -1503,6 +1503,12 @@ public class FirPsiDiagnosticTestGenerated extends AbstractFirPsiDiagnosticTest
runTest("compiler/fir/analysis-tests/testData/resolve/checkers/complexConflictingProjections.kt");
}
@Test
@TestMetadata("contextReceiversSubtyping.kt")
public void testContextReceiversSubtyping() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/checkers/contextReceiversSubtyping.kt");
}
@Test
@TestMetadata("importAnnotationWithRequiresOptIn.kt")
public void testImportAnnotationWithRequiresOptIn() throws Exception {
@@ -676,6 +676,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
}
val AMBIGUOUS_CALL_WITH_IMPLICIT_CONTEXT_RECEIVER by error<KtElement>(PositioningStrategy.REFERENCE_BY_QUALIFIED)
val UNSUPPORTED_CONTEXTUAL_DECLARATION_CALL by error<KtElement>(PositioningStrategy.NAME_IDENTIFIER)
val SUBTYPING_BETWEEN_CONTEXT_RECEIVERS by error<KtElement>(PositioningStrategy.DEFAULT)
}
val TYPES_AND_TYPE_PARAMETERS by object : DiagnosticGroup("Types & type parameters") {
@@ -415,6 +415,7 @@ object FirErrors {
val MULTIPLE_ARGUMENTS_APPLICABLE_FOR_CONTEXT_RECEIVER by error1<KtElement, ConeKotlinType>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val AMBIGUOUS_CALL_WITH_IMPLICIT_CONTEXT_RECEIVER by error0<KtElement>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val UNSUPPORTED_CONTEXTUAL_DECLARATION_CALL by error0<KtElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
val SUBTYPING_BETWEEN_CONTEXT_RECEIVERS by error0<KtElement>()
// Types & type parameters
val RECURSION_IN_IMPLICIT_TYPES by error0<PsiElement>()
@@ -263,6 +263,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"MULTIPLE_ARGUMENTS_APPLICABLE_FOR_CONTEXT_RECEIVER",
"AMBIGUOUS_CALL_WITH_IMPLICIT_CONTEXT_RECEIVER",
"UNSUPPORTED_CONTEXTUAL_DECLARATION_CALL",
"SUBTYPING_BETWEEN_CONTEXT_RECEIVERS",
"RECURSION_IN_IMPLICIT_TYPES",
"INFERENCE_ERROR",
"PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT",
@@ -5,10 +5,19 @@
package org.jetbrains.kotlin.fir.analysis.checkers
import com.intellij.lang.LighterASTNode
import org.jetbrains.kotlin.KtLightSourceElement
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.diagnostics.findChildByType
import org.jetbrains.kotlin.diagnostics.findDescendantByType
import org.jetbrains.kotlin.diagnostics.traverseDescendants
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.toKtLightSourceElement
import org.jetbrains.kotlin.util.getChildren
/**
@@ -23,3 +32,17 @@ fun KtModifierKeywordToken.toVisibilityOrNull(): Visibility? {
else -> null
}
}
/**
* Locates first [CONTEXT_RECEIVER_LIST] and returns position in source.
*/
fun KtSourceElement.findContextReceiverListSource(): KtLightSourceElement? {
if (this.lighterASTNode.tokenType == KtNodeTypes.CONTEXT_RECEIVER_LIST)
return this.lighterASTNode.toKtLightSourceElement(treeStructure)
return treeStructure.findDescendantByType(
lighterASTNode,
KtNodeTypes.CONTEXT_RECEIVER_LIST,
false
)?.toKtLightSourceElement(treeStructure)
}
@@ -5,27 +5,34 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import com.intellij.lang.LighterASTNode
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtLightSourceElement
import org.jetbrains.kotlin.KtNodeTypes.CONTEXT_RECEIVER_LIST
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.findContextReceiverListSource
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.toKtLightSourceElement
import org.jetbrains.kotlin.util.getChildren
import org.jetbrains.kotlin.fir.types.*
object FirContextReceiversDeclarationChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (context.languageVersionSettings.supportsFeature(LanguageFeature.ContextReceivers)) return
if (declaration.source?.kind is KtFakeSourceElementKind) return
if (!declaration.hasContextReceiver()) return
val contextReceivers = declaration.getContextReceiver()
if (contextReceivers.isEmpty()) return
val source = declaration.source?.findContextReceiverListSource() ?: return
if (context.languageVersionSettings.supportsFeature(LanguageFeature.ContextReceivers)) {
if (checkSubTypes(contextReceivers.map { it.typeRef.coneType }, context)) {
reporter.reportOn(
source,
FirErrors.SUBTYPING_BETWEEN_CONTEXT_RECEIVERS,
context
)
}
return
}
reporter.reportOn(
source,
FirErrors.UNSUPPORTED_FEATURE,
@@ -34,36 +41,47 @@ object FirContextReceiversDeclarationChecker : FirBasicDeclarationChecker() {
)
}
private fun FirDeclaration.hasContextReceiver(): Boolean {
val contextReceivers = when (this) {
private fun FirDeclaration.getContextReceiver(): List<FirContextReceiver> {
return when (this) {
is FirCallableDeclaration -> contextReceivers
is FirRegularClass -> contextReceivers
is FirScript -> contextReceivers
else -> emptyList()
}
return contextReceivers.isNotEmpty()
}
private fun KtSourceElement.findContextReceiverListSource(): KtLightSourceElement? {
var contextReceiverList: LighterASTNode? = null
var contextReceiverListOffset = startOffset
val nodes = lighterASTNode.getChildren(treeStructure)
for (node in nodes) {
if (node.tokenType == CONTEXT_RECEIVER_LIST) {
contextReceiverList = node
break
} else {
contextReceiverListOffset += node.endOffset - node.startOffset
}
}
if (contextReceiverList == null) return null
return contextReceiverList.toKtLightSourceElement(
treeStructure,
startOffset = contextReceiverListOffset,
endOffset = contextReceiverListOffset + contextReceiverList.textLength,
)
}
}
/**
* Simplified checking of subtype relation used in context receiver checkers.
* It converts type parameters to star projections and top level type parameters to its supertypes. Then it checks the relation.
*/
fun checkSubTypes(types: List<ConeKotlinType>, context: CheckerContext): Boolean {
fun replaceTypeParametersByStarProjections(type: ConeClassLikeType): ConeClassLikeType {
return type.withArguments(type.typeArguments.map {
when {
it.isStarProjection -> it
it.type!! is ConeTypeParameterType -> ConeStarProjection
it.type!! is ConeClassLikeType -> replaceTypeParametersByStarProjections(it.type as ConeClassLikeType)
else -> it
}
}.toTypedArray())
}
val replacedTypeParameters = types.flatMap { r ->
when (r) {
is ConeTypeParameterType -> r.lookupTag.typeParameterSymbol.resolvedBounds.map { it.type }
is ConeClassLikeType -> listOf(replaceTypeParametersByStarProjections(r))
else -> listOf(r)
}
}
for (i in replacedTypeParameters.indices)
for (j in i + 1..<replacedTypeParameters.size) {
if (replacedTypeParameters[i].isSubtypeOf(replacedTypeParameters[j], context.session)
|| replacedTypeParameters[j].isSubtypeOf(replacedTypeParameters[i], context.session)
)
return true
}
return false
}
@@ -5,29 +5,40 @@
package org.jetbrains.kotlin.fir.analysis.checkers.type
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.checkSubTypes
import org.jetbrains.kotlin.fir.analysis.checkers.findContextReceiverListSource
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.types.*
object FirContextReceiversTypeChecker : FirTypeRefChecker() {
override fun check(typeRef: FirTypeRef, context: CheckerContext, reporter: DiagnosticReporter) {
if (context.languageVersionSettings.supportsFeature(LanguageFeature.ContextReceivers)) return
if (typeRef !is FirResolvedTypeRef) return
val source = typeRef.source ?: return
if (source.kind is KtFakeSourceElementKind) return
if (typeRef.source?.kind is KtFakeSourceElementKind) return
if (!typeRef.coneType.hasContextReceivers) return
val source = typeRef.source?.findContextReceiverListSource() ?: return
if (typeRef.coneType.hasContextReceivers) {
reporter.reportOn(
source,
FirErrors.UNSUPPORTED_FEATURE,
LanguageFeature.ContextReceivers to context.languageVersionSettings,
context
)
if (context.languageVersionSettings.supportsFeature(LanguageFeature.ContextReceivers)) {
if (checkSubTypes(typeRef.coneType.contextReceiversTypes(context.session), context)) {
reporter.reportOn(
source,
FirErrors.SUBTYPING_BETWEEN_CONTEXT_RECEIVERS,
context
)
}
return
}
reporter.reportOn(
source,
FirErrors.UNSUPPORTED_FEATURE,
LanguageFeature.ContextReceivers to context.languageVersionSettings,
context
)
}
}
@@ -581,6 +581,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SINGLE_ANONYMOUS_
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SMARTCAST_IMPOSSIBLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SPREAD_OF_NULLABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUBCLASS_OPT_IN_INAPPLICABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUBTYPING_BETWEEN_CONTEXT_RECEIVERS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERTYPES_FOR_ANNOTATION_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPERTYPE_APPEARS_TWICE
@@ -1276,6 +1277,10 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
UNSUPPORTED_CONTEXTUAL_DECLARATION_CALL,
"To use contextual declarations, specify the '-Xcontext-receivers' compiler option."
)
map.put(
SUBTYPING_BETWEEN_CONTEXT_RECEIVERS,
"Subtyping relation between context receivers is prohibited."
)
// Ambiguity
map.put(OVERLOAD_RESOLUTION_AMBIGUITY, "Overload resolution ambiguity between candidates: {0}", SYMBOLS)
@@ -1,221 +0,0 @@
// !DIAGNOSTICS: -UNCHECKED_CAST
// !LANGUAGE: +ContextReceivers
interface A
interface B : A
typealias C = B
interface Inv<T>
interface Cov<out T>
// Functions
context(A, A)
fun f1() {}
context(A, B)
fun f2() {}
context(A, C)
fun f3() {}
context(B, C)
fun f4() {}
context(C, C)
fun f5() {}
context(A, A, A)
fun f6() {}
context(Inv<A>, Inv<B>)
fun f7() {}
context(Inv<A>, Inv<A>)
fun f8() {}
context(Inv<T>, Inv<A>)
fun <T> f9() {}
context(Cov<A>, Cov<B>)
fun f10() {}
context(Cov<T>, Cov<A>)
fun <T> f11() {}
context(T, A)
fun <T> f12() {}
// Classes
context(A, A)
class C1 {
context(A, A)
val p: Any get() = 42
context(A, A)
fun m() {}
}
context(A, B)
class C2 {
context(A, B)
val p: Any get() = 42
context(A, B)
fun m() {}
}
context(A, C)
class C3 {
context(A, C)
val p: Any get() = 42
context(A, C)
fun m() {}
}
context(B, C)
class C4 {
context(B, C)
val p: Any get() = 42
context(B, C)
fun m() {}
}
context(C, C)
class C5 {
context(C, C)
val p: Any get() = 42
context(C, C)
fun m() {}
}
context(A, A, A)
class C6 {
context(A, A, A)
val p: Any get() = 42
context(A, A, A)
fun m() {}
}
context(Inv<A>, Inv<B>)
class C7 {
context(Inv<A>, Inv<B>)
val p: Any get() = 42
context(Inv<A>, Inv<B>)
fun m() {}
}
context(Inv<A>, Inv<A>)
class C8 {
context(Inv<A>, Inv<A>)
val p: Any get() = 42
context(Inv<A>, Inv<A>)
fun m() {}
}
context(Inv<T>, Inv<A>)
class C9<T> {
context(Inv<T>, Inv<A>)
val p: Any get() = 42
context(Inv<T>, Inv<A>)
fun m() {}
}
context(Cov<A>, Cov<B>)
class C10 {
context(Cov<A>, Cov<B>)
val p: Any get() = 42
context(Cov<A>, Cov<B>)
fun m() {}
}
context(Cov<T>, Cov<A>)
class C11<T> {
context(Cov<T>, Cov<A>)
val p: Any get() = 42
context(Cov<T>, Cov<A>)
fun m() {}
}
context(T, A)
class C12<T> {
context(T, A)
val p: Any get() = 42
context(T, A)
fun m() {}
}
// Properties
context(A, A)
val p1: Any?
get() { return null }
context(A, B)
val p2: Any?
get() { return null }
context(A, C)
val p3: Any?
get() { return null }
context(B, C)
val p4: Any?
get() { return null }
context(C, C)
val p5: Any?
get() { return null }
context(A, A, A)
val p6: Any?
get() { return null }
context(Inv<A>, Inv<B>)
val p7: Any?
get() { return null }
context(Inv<A>, Inv<A>)
val p8: Any?
get() { return null }
context(Inv<T>, Inv<A>)
val <T> p9: Any?
get() { return null }
context(Cov<A>, Cov<B>)
val p10: Any?
get() { return null }
context(Cov<T>, Cov<A>)
val <T> p11: Any?
get() { return null }
context(T, A)
val <T> p12: Any?
get() { return null }
// Function types
// Function types are processed with the same function that is used for checking contextual declarations
// So we check here only one simple case: `context(A, B)`
context(A, B)
fun f(g: context(A, B) () -> Unit, value: Any): context(A, B) () -> Unit {
return value as (context(A, B) () -> Unit)
}
fun test() {
val lf: context(A, B) () -> Unit = { }
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNCHECKED_CAST
// !LANGUAGE: +ContextReceivers
@@ -1,8 +1,8 @@
// !DIAGNOSTICS: -UNCHECKED_CAST
<!UNSUPPORTED_FEATURE!>context(Any)<!>
fun f(g: <!UNSUPPORTED_FEATURE!>context(Any) () -> Unit<!>, value: Any): <!UNSUPPORTED_FEATURE!>context(A) () -> Unit<!> {
return value as <!UNSUPPORTED_FEATURE!>(context(A) () -> Unit)<!>
fun f(g: <!UNSUPPORTED_FEATURE!>context(Any)<!> () -> Unit, value: Any): <!UNSUPPORTED_FEATURE!>context(A)<!> () -> Unit {
return value as (<!UNSUPPORTED_FEATURE!>context(A)<!> () -> Unit)
}
fun f(g: () -> Unit, value: Any) : () -> Unit {