[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:
Dmitriy Novozhilov
2023-04-12 17:44:51 +03:00
committed by Space Team
parent 5a92eb2c67
commit f8dc8057f0
13 changed files with 302 additions and 11 deletions
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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")
}
}
@@ -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? {
@@ -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 {
@@ -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)
}<!>
}
}
@@ -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)
}<!>
}
}
@@ -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<!>
}
}
@@ -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<!>
}
}
@@ -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 {
@@ -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()
}