diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java index 32d658e2096..bc2273415e6 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosticCompilerTestFE10TestdataTestGenerated.java @@ -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 { diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java index 58d7807c182..d47585dc12b 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated.java @@ -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 { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java index a2c19fccf12..93232e07077 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirLightTreeOldFrontendDiagnosticsTestGenerated.java @@ -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 { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java index be278107f70..5c0241ed135 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirPsiOldFrontendDiagnosticsTestGenerated.java @@ -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 { diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/contractSubstitutions.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/contractSubstitutions.kt new file mode 100644 index 00000000000..e8e342bcbd2 --- /dev/null +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/contractSubstitutions.kt @@ -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") + } +} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/ConeEffectExtractor.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/ConeEffectExtractor.kt index a9d9817b96f..8d644b75d10 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/ConeEffectExtractor.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/contracts/ConeEffectExtractor.kt @@ -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? { diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/diagnostics/ConeDiagnostics.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/diagnostics/ConeDiagnostics.kt index 42cc24e8b7d..be2f29b3280 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/diagnostics/ConeDiagnostics.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/diagnostics/ConeDiagnostics.kt @@ -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 { diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt index 0c14b5121c9..c147aea1fa2 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.3.fir.kt @@ -12,9 +12,9 @@ inline fun referToReifiedGeneric(x: Any?) { class Generic { fun referToCaptured(x: Any?) { - contract { - returns() implies (x is T) - } + contract { + returns() implies (x is T) + } } } @@ -50,4 +50,4 @@ fun referToAliasedSimpleType(x: Any?) { contract { returns() implies (x is SimpleType) } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt index e91f22962c6..51e591c35a6 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/typeReferences.1.4.fir.kt @@ -12,9 +12,9 @@ inline fun referToReifiedGeneric(x: Any?) { class Generic { fun referToCaptured(x: Any?) { - contract { - returns() implies (x is T) - } + contract { + returns() implies (x is T) + } } } @@ -50,4 +50,4 @@ fun referToAliasedSimpleType(x: Any?) { contract { returns() implies (x is SimpleType) } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.fir.kt new file mode 100644 index 00000000000..a7f5514f356 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.fir.kt @@ -0,0 +1,83 @@ +// ISSUE: KT-57911 + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +abstract class Base { + @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 T) } + return false + } + + @OptIn(ExperimentalContracts::class) + fun checkIsOwnerR(s: Any?): Boolean { + contract { returns(true) implies (s is R) } + return false + } + + @OptIn(ExperimentalContracts::class) + inline fun 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() { + override fun foo(s: String?) { + checkNotNull(s) + s.length + } + + fun test_1(s: Any) { + if (checkIsT(s)) { + s.length + } + } + + fun test_2(s: Any) { + if (checkIsOwnerR(s)) { + s.length + } + } + + fun test_3(s: Any) { + if (checkIsReifiedR(s)) { + s.length + } + } +} + +fun test_1(d: Derived, s: String?) { + d.checkNotNull(s) + s.length +} + +fun test_2(d: Derived, s: Any?) { + if (d.checkIsT(s)) { + s.length + } +} + +fun test_3(d: Derived, s: Any?) { + if (d.checkIsOwnerR(s)) { + s.length + } +} + +fun test_4(d: Derived, s: Any?) { + if (d.checkIsReifiedR(s)) { + s.length + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt new file mode 100644 index 00000000000..5091cca2999 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/fromStdlib/contractWithSubstitution.kt @@ -0,0 +1,83 @@ +// ISSUE: KT-57911 + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +abstract class Base { + @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 T) } + return false + } + + @OptIn(ExperimentalContracts::class) + fun checkIsOwnerR(s: Any?): Boolean { + contract { returns(true) implies (s is R) } + return false + } + + @OptIn(ExperimentalContracts::class) + inline fun 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() { + override fun foo(s: String?) { + checkNotNull(s) + s.length + } + + fun test_1(s: Any) { + if (checkIsT(s)) { + s.length + } + } + + fun test_2(s: Any) { + if (checkIsOwnerR(s)) { + s.length + } + } + + fun test_3(s: Any) { + if (checkIsReifiedR(s)) { + s.length + } + } +} + +fun test_1(d: Derived, s: String?) { + d.checkNotNull(s) + s.length +} + +fun test_2(d: Derived, s: Any?) { + if (d.checkIsT(s)) { + s.length + } +} + +fun test_3(d: Derived, s: Any?) { + if (d.checkIsOwnerR(s)) { + s.length + } +} + +fun test_4(d: Derived, s: Any?) { + if (d.checkIsReifiedR(s)) { + s.length + } +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 963ba3653aa..d45b3908bd0 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -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 { diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/returns/neg/7.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/returns/neg/7.fir.kt index c2171d3bb41..ac3daf8d6bb 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/returns/neg/7.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/returns/neg/7.fir.kt @@ -5,12 +5,12 @@ import kotlin.contracts.* // TESTCASE NUMBER: 1 fun T.case_1() { - contract { returns() implies (this@case_1 is T) } + contract { returns() implies (this@case_1 is T) } if (!(this@case_1 is T)) throw Exception() } // TESTCASE NUMBER: 2 fun String> T?.case_2(value_1: K?) { - contract { returns() implies (this@case_2 is T && value_1 is K) } + contract { returns() implies (this@case_2 is T && value_1 is K) } if (!(this@case_2 is T && value_1 is K)) throw Exception() }