[FIR] Prohibit referencing type parameters in contracts ...
...if they are not reified or not belong to owner declaration of the contract KT-57911
This commit is contained in:
committed by
Space Team
parent
5a92eb2c67
commit
f8dc8057f0
+6
@@ -37191,6 +37191,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/check.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("contractWithSubstitution.kt")
|
||||
public void testContractWithSubstitution() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fromStandardKt.kt")
|
||||
public void testFromStandardKt() throws Exception {
|
||||
|
||||
+6
@@ -37191,6 +37191,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/check.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("contractWithSubstitution.kt")
|
||||
public void testContractWithSubstitution() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fromStandardKt.kt")
|
||||
public void testFromStandardKt() throws Exception {
|
||||
|
||||
+6
@@ -37191,6 +37191,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/check.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("contractWithSubstitution.kt")
|
||||
public void testContractWithSubstitution() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fromStandardKt.kt")
|
||||
public void testFromStandardKt() throws Exception {
|
||||
|
||||
+6
@@ -37287,6 +37287,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/check.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("contractWithSubstitution.kt")
|
||||
public void testContractWithSubstitution() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fromStandardKt.kt")
|
||||
public void testFromStandardKt() throws Exception {
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.declarations
|
||||
|
||||
import org.jetbrains.kotlin.fir.contracts.FirContractDescription
|
||||
import org.jetbrains.kotlin.fir.contracts.FirEffectDeclaration
|
||||
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
|
||||
import org.jetbrains.kotlin.fir.contracts.builder.buildEffectDeclaration
|
||||
import org.jetbrains.kotlin.fir.contracts.builder.buildResolvedContractDescription
|
||||
import org.jetbrains.kotlin.fir.contracts.description.*
|
||||
import org.jetbrains.kotlin.fir.contracts.impl.FirEmptyContractDescription
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||
|
||||
val FirContractDescription?.isNullOrEmpty: Boolean
|
||||
get() = (this == null) || (this is FirEmptyContractDescription)
|
||||
|
||||
fun FirContractDescription.createContractDescriptionForSubstitutionOverride(substitutor: ConeSubstitutor?): FirContractDescription {
|
||||
if (this !is FirResolvedContractDescription) return this
|
||||
if (substitutor == null || substitutor == ConeSubstitutor.Empty) return this
|
||||
return createContractDescriptionForSubstitutionOverride(substitutor)
|
||||
}
|
||||
|
||||
private fun FirResolvedContractDescription.createContractDescriptionForSubstitutionOverride(
|
||||
substitutor: ConeSubstitutor
|
||||
): FirResolvedContractDescription {
|
||||
val original = this
|
||||
return buildResolvedContractDescription {
|
||||
source = original.source
|
||||
original.effects.mapTo(effects) { it.substitute(substitutor) }
|
||||
unresolvedEffects.addAll(original.unresolvedEffects)
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirEffectDeclaration.substitute(substitutor: ConeSubstitutor): FirEffectDeclaration {
|
||||
val original = this
|
||||
return buildEffectDeclaration {
|
||||
source = original.source
|
||||
effect = original.effect.substitute(substitutor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConeEffectDeclaration.substitute(substitutor: ConeSubstitutor): ConeEffectDeclaration {
|
||||
return when (this) {
|
||||
is ConeConditionalEffectDeclaration -> ConeConditionalEffectDeclaration(
|
||||
effect.substitute(substitutor),
|
||||
condition.substitute(substitutor)
|
||||
)
|
||||
is ConeCallsEffectDeclaration -> this
|
||||
is ConeReturnsEffectDeclaration -> this
|
||||
else -> error("Unknown effect declaration: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConeBooleanExpression.substitute(substitutor: ConeSubstitutor): ConeBooleanExpression {
|
||||
return when (this) {
|
||||
is ConeIsInstancePredicate -> {
|
||||
val newType = substitutor.substituteOrNull(type) ?: return this
|
||||
ConeIsInstancePredicate(arg, newType, isNegated)
|
||||
}
|
||||
is ConeBinaryLogicExpression -> ConeBinaryLogicExpression(left.substitute(substitutor), right.substitute(substitutor), kind)
|
||||
is ConeBooleanConstantReference -> this
|
||||
is ConeBooleanValueParameterReference -> this
|
||||
is ConeIsNullPredicate -> this
|
||||
is ConeLogicalNot -> ConeLogicalNot(arg.substitute(substitutor))
|
||||
else -> error("Unknown effect expression: $this")
|
||||
}
|
||||
}
|
||||
+16
-1
@@ -15,12 +15,15 @@ import org.jetbrains.kotlin.fir.references.FirNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeContractDescriptionError
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.resolve.getContainingClass
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.toSymbol
|
||||
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.types.ConstantValueKind
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.runIf
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
|
||||
|
||||
class ConeEffectExtractor(
|
||||
@@ -210,7 +213,19 @@ class ConeEffectExtractor(
|
||||
val arg = typeOperatorCall.argument.asContractValueExpression()
|
||||
val type = typeOperatorCall.conversionTypeRef.coneType.fullyExpandedType(session)
|
||||
val isNegated = typeOperatorCall.operation == FirOperation.NOT_IS
|
||||
return ConeIsInstancePredicate(arg, type, isNegated)
|
||||
val diagnostic = (type.toSymbol(session) as? FirTypeParameterSymbol)?.let { typeParameterSymbol ->
|
||||
val typeParametersOfOwner = (owner as? FirTypeParameterRefsOwner)?.typeParameters.orEmpty()
|
||||
if (typeParametersOfOwner.none { it is FirTypeParameter && it.symbol == typeParameterSymbol }) {
|
||||
return@let ConeContractDescriptionError.NotSelfTypeParameter(typeParameterSymbol)
|
||||
}
|
||||
runIf(!typeParameterSymbol.isReified) {
|
||||
ConeContractDescriptionError.NotReifiedTypeParameter(typeParameterSymbol)
|
||||
}
|
||||
}
|
||||
return when (diagnostic) {
|
||||
null -> ConeIsInstancePredicate(arg, type, isNegated)
|
||||
else -> ConeErroneousIsInstancePredicate(arg, type, isNegated, diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirExpression.parseInvocationKind(): EventOccurrencesRange? {
|
||||
|
||||
+10
@@ -220,6 +220,16 @@ sealed class ConeContractDescriptionError : ConeDiagnostic {
|
||||
override val reason: String
|
||||
get() = "$operation operator call is illegal in contract description"
|
||||
}
|
||||
|
||||
class NotSelfTypeParameter(val symbol: FirTypeParameterSymbol) : ConeContractDescriptionError() {
|
||||
override val reason: String
|
||||
get() = "Type parameter ${symbol.name} does not belong to owner of contract"
|
||||
}
|
||||
|
||||
class NotReifiedTypeParameter(val symbol: FirTypeParameterSymbol) : ConeContractDescriptionError() {
|
||||
override val reason: String
|
||||
get() = "Type parameter ${symbol.name} is not reified"
|
||||
}
|
||||
}
|
||||
|
||||
class ConeIllegalAnnotationError(val name: Name) : ConeDiagnostic {
|
||||
|
||||
Vendored
+4
-4
@@ -12,9 +12,9 @@ inline fun <reified T> referToReifiedGeneric(x: Any?) {
|
||||
|
||||
class Generic<T> {
|
||||
fun referToCaptured(x: Any?) {
|
||||
<!WRONG_IMPLIES_CONDITION!>contract {
|
||||
returns() implies (x is T)
|
||||
}<!>
|
||||
contract {
|
||||
<!ERROR_IN_CONTRACT_DESCRIPTION!>returns() implies (x is T)<!>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@ fun referToAliasedSimpleType(x: Any?) {
|
||||
<!WRONG_IMPLIES_CONDITION!>contract {
|
||||
returns() implies (x is SimpleType)
|
||||
}<!>
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+4
-4
@@ -12,9 +12,9 @@ inline fun <reified T> referToReifiedGeneric(x: Any?) {
|
||||
|
||||
class Generic<T> {
|
||||
fun referToCaptured(x: Any?) {
|
||||
<!WRONG_IMPLIES_CONDITION!>contract {
|
||||
returns() implies (x is T)
|
||||
}<!>
|
||||
contract {
|
||||
<!ERROR_IN_CONTRACT_DESCRIPTION!>returns() implies (x is T)<!>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,4 @@ fun referToAliasedSimpleType(x: Any?) {
|
||||
<!WRONG_IMPLIES_CONDITION!>contract {
|
||||
returns() implies (x is SimpleType)
|
||||
}<!>
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
// ISSUE: KT-57911
|
||||
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
abstract class Base<T> {
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun checkNotNull(s: String?) {
|
||||
contract { returns() implies (s != null) }
|
||||
s!!
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun checkIsT(s: Any?): Boolean {
|
||||
contract { <!ERROR_IN_CONTRACT_DESCRIPTION!>returns(true) implies (s is T)<!> }
|
||||
return false
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun <R> checkIsOwnerR(s: Any?): Boolean {
|
||||
contract { <!ERROR_IN_CONTRACT_DESCRIPTION!>returns(true) implies (s is R)<!> }
|
||||
return false
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <reified R> checkIsReifiedR(s: Any?): Boolean {
|
||||
contract { returns(true) implies (s is R) }
|
||||
return false
|
||||
}
|
||||
|
||||
open fun foo(s: String?) {
|
||||
checkNotNull(s)
|
||||
s.length
|
||||
}
|
||||
}
|
||||
|
||||
class Derived: Base<String>() {
|
||||
override fun foo(s: String?) {
|
||||
checkNotNull(s)
|
||||
s<!UNSAFE_CALL!>.<!>length
|
||||
}
|
||||
|
||||
fun test_1(s: Any) {
|
||||
if (checkIsT(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_2(s: Any) {
|
||||
if (checkIsOwnerR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_3(s: Any) {
|
||||
if (checkIsReifiedR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun test_1(d: Derived, s: String?) {
|
||||
d.checkNotNull(s)
|
||||
s<!UNSAFE_CALL!>.<!>length
|
||||
}
|
||||
|
||||
fun test_2(d: Derived, s: Any?) {
|
||||
if (d.checkIsT(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_3(d: Derived, s: Any?) {
|
||||
if (d.checkIsOwnerR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_4(d: Derived, s: Any?) {
|
||||
if (d.checkIsReifiedR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
// ISSUE: KT-57911
|
||||
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
abstract class Base<T> {
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun checkNotNull(s: String?) {
|
||||
contract { returns() implies (s != null) }
|
||||
s!!
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun checkIsT(s: Any?): Boolean {
|
||||
contract { returns(true) implies (s is <!CANNOT_CHECK_FOR_ERASED, ERROR_IN_CONTRACT_DESCRIPTION!>T<!>) }
|
||||
return false
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun <R> checkIsOwnerR(s: Any?): Boolean {
|
||||
contract { returns(true) implies (s is <!CANNOT_CHECK_FOR_ERASED, ERROR_IN_CONTRACT_DESCRIPTION!>R<!>) }
|
||||
return false
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <reified R> checkIsReifiedR(s: Any?): Boolean {
|
||||
contract { returns(true) implies (s is R) }
|
||||
return false
|
||||
}
|
||||
|
||||
open fun foo(s: String?) {
|
||||
checkNotNull(s)
|
||||
<!DEBUG_INFO_SMARTCAST!>s<!>.length
|
||||
}
|
||||
}
|
||||
|
||||
class Derived: Base<String>() {
|
||||
override fun foo(s: String?) {
|
||||
checkNotNull(s)
|
||||
<!DEBUG_INFO_SMARTCAST!>s<!>.length
|
||||
}
|
||||
|
||||
fun test_1(s: Any) {
|
||||
if (checkIsT(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_2(s: Any) {
|
||||
if (checkIsOwnerR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_3(s: Any) {
|
||||
if (checkIsReifiedR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun test_1(d: Derived, s: String?) {
|
||||
d.checkNotNull(s)
|
||||
<!DEBUG_INFO_SMARTCAST!>s<!>.length
|
||||
}
|
||||
|
||||
fun test_2(d: Derived, s: Any?) {
|
||||
if (d.checkIsT(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_3(d: Derived, s: Any?) {
|
||||
if (d.checkIsOwnerR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun test_4(d: Derived, s: Any?) {
|
||||
if (d.checkIsReifiedR<String>(s)) {
|
||||
s.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
Generated
+6
@@ -38053,6 +38053,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/check.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("contractWithSubstitution.kt")
|
||||
public void testContractWithSubstitution() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fromStandardKt.kt")
|
||||
public void testFromStandardKt() throws Exception {
|
||||
|
||||
+2
-2
@@ -5,12 +5,12 @@ import kotlin.contracts.*
|
||||
|
||||
// TESTCASE NUMBER: 1
|
||||
fun <T : Number?> T.case_1() {
|
||||
contract { returns() implies (<!USELESS_IS_CHECK!>this@case_1 is T<!>) }
|
||||
contract { <!ERROR_IN_CONTRACT_DESCRIPTION!>returns() implies (<!USELESS_IS_CHECK!>this@case_1 is T<!>)<!> }
|
||||
if (!(<!USELESS_IS_CHECK!>this@case_1 is T<!>)) throw Exception()
|
||||
}
|
||||
|
||||
// TESTCASE NUMBER: 2
|
||||
fun <T : Number, K : <!FINAL_UPPER_BOUND!>String<!>> T?.case_2(value_1: K?) {
|
||||
contract { returns() implies (this@case_2 is T && value_1 is K) }
|
||||
contract { <!ERROR_IN_CONTRACT_DESCRIPTION!>returns() implies (this@case_2 is T && value_1 is K)<!> }
|
||||
if (!(this@case_2 is T && value_1 is K)) throw Exception()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user