diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 4f4ec1ed454..77aee800a44 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -32506,6 +32506,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/compositions.kt"); } + @Test + @TestMetadata("contractWithDeepGenerics.kt") + public void testContractWithDeepGenerics() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.kt"); + } + @Test @TestMetadata("contractsOnMembers.kt") public void testContractsOnMembers() throws Exception { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt index 1cf8aaa8f29..e5879880253 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt @@ -33,6 +33,8 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.inference.components.EmptySubstitutor +import org.jetbrains.kotlin.resolve.calls.inference.components.NewTypeSubstitutorByConstructorMap import org.jetbrains.kotlin.resolve.calls.model.* import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory @@ -43,6 +45,8 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeConstructor +import org.jetbrains.kotlin.types.UnwrappedType import org.jetbrains.kotlin.utils.addIfNotNull /** @@ -121,7 +125,7 @@ class EffectsExtractingVisitor( val arg = extractOrGetCached(expression.leftHandSide) return CallComputation( ESBooleanType, - IsFunctor(rightType, expression.isNegated).invokeWithArguments(listOf(arg), emptyMap(), reducer) + IsFunctor(rightType, expression.isNegated).invokeWithArguments(listOf(arg), ESTypeSubstitution.empty(builtIns), reducer) ) } @@ -146,10 +150,22 @@ class EffectsExtractingVisitor( val args = listOf(left, right) return when (expression.operationToken) { - KtTokens.EXCLEQ -> CallComputation(ESBooleanType, EqualsFunctor(true).invokeWithArguments(args, emptyMap(), reducer)) - KtTokens.EQEQ -> CallComputation(ESBooleanType, EqualsFunctor(false).invokeWithArguments(args, emptyMap(), reducer)) - KtTokens.ANDAND -> CallComputation(ESBooleanType, AndFunctor().invokeWithArguments(args, emptyMap(), reducer)) - KtTokens.OROR -> CallComputation(ESBooleanType, OrFunctor().invokeWithArguments(args, emptyMap(), reducer)) + KtTokens.EXCLEQ -> CallComputation( + ESBooleanType, + EqualsFunctor(true).invokeWithArguments(args, ESTypeSubstitution.empty(builtIns), reducer) + ) + KtTokens.EQEQ -> CallComputation( + ESBooleanType, + EqualsFunctor(false).invokeWithArguments(args, ESTypeSubstitution.empty(builtIns), reducer) + ) + KtTokens.ANDAND -> CallComputation( + ESBooleanType, + AndFunctor().invokeWithArguments(args, ESTypeSubstitution.empty(builtIns), reducer) + ) + KtTokens.OROR -> CallComputation( + ESBooleanType, + OrFunctor().invokeWithArguments(args, ESTypeSubstitution.empty(builtIns), reducer) + ) else -> UNKNOWN_COMPUTATION } } @@ -214,11 +230,16 @@ class EffectsExtractingVisitor( } private fun ResolvedCall<*>.getTypeSubstitution(): ESTypeSubstitution { - val result = mutableMapOf() + val substitution = mutableMapOf() for ((typeParameter, typeArgument) in typeArguments) { - result[ESKotlinType(typeParameter.defaultType)] = ESKotlinType(typeArgument) + substitution[typeParameter.typeConstructor] = typeArgument.unwrap() } - return result + val substitutor = if (substitution.isNotEmpty()) { + NewTypeSubstitutorByConstructorMap(substitution) + } else { + EmptySubstitutor + } + return ESTypeSubstitution(substitutor, builtIns) } private fun ResolvedValueArgument.toComputation(): Computation? { diff --git a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/Functor.kt b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/Functor.kt index 5025ff4fe2d..6bc36f04561 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/Functor.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/Functor.kt @@ -16,8 +16,10 @@ package org.jetbrains.kotlin.contracts.model -import org.jetbrains.kotlin.contracts.model.structure.ESKotlinType +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.contracts.model.visitors.Reducer +import org.jetbrains.kotlin.resolve.calls.inference.components.EmptySubstitutor +import org.jetbrains.kotlin.resolve.calls.inference.components.NewTypeSubstitutor /** * An abstraction of effect-generating nature of some computation. @@ -43,4 +45,13 @@ abstract class AbstractFunctor : Functor { ): List } -typealias ESTypeSubstitution = Map \ No newline at end of file +class ESTypeSubstitution( + val substitutor: NewTypeSubstitutor, + val builtIns: KotlinBuiltIns +) { + companion object { + fun empty(builtIns: KotlinBuiltIns): ESTypeSubstitution { + return ESTypeSubstitution(EmptySubstitutor, builtIns) + } + } +} diff --git a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/IsFunctor.kt b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/IsFunctor.kt index 6ac71edb971..3e507526b9b 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/IsFunctor.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/IsFunctor.kt @@ -17,10 +17,7 @@ package org.jetbrains.kotlin.contracts.model.functors import org.jetbrains.kotlin.contracts.model.* -import org.jetbrains.kotlin.contracts.model.structure.ESConstants -import org.jetbrains.kotlin.contracts.model.structure.ESIs -import org.jetbrains.kotlin.contracts.model.structure.ESReturns -import org.jetbrains.kotlin.contracts.model.structure.ESType +import org.jetbrains.kotlin.contracts.model.structure.* import org.jetbrains.kotlin.contracts.model.visitors.Reducer class IsFunctor(val type: ESType, val isNegated: Boolean) : AbstractFunctor() { @@ -37,7 +34,9 @@ class IsFunctor(val type: ESType, val isNegated: Boolean) : AbstractFunctor() { } private fun invokeWithValue(value: ESValue, typeSubstitution: ESTypeSubstitution): List { - val substitutedType = typeSubstitution[type] ?: type + val substitutedKotlinType = typeSubstitution.substitutor.safeSubstitute(type.toKotlinType(typeSubstitution.builtIns).unwrap()) + + val substitutedType = ESKotlinType(substitutedKotlinType) val trueIs = ESIs(value, IsFunctor(substitutedType, isNegated)) val falseIs = ESIs(value, IsFunctor(substitutedType, isNegated.not())) diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.fir.kt new file mode 100644 index 00000000000..e68bd7726f9 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.fir.kt @@ -0,0 +1,31 @@ +// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts +// ISSUE: KT-43260 + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +sealed class Either { + data class Left(val leftValue: L) : Either() + data class Right(val rightValue: R) : Either() +} + +inline fun Either.isLeft(): Boolean { + contract { + returns(true) implies (this@isLeft is Either.Left) + } + return this is Either.Left +} + +inline fun Either.isRight(): Boolean { + contract { + returns(true) implies (this@isRight is Either.Right) + } + return this is Either.Right +} + +fun test() { + val result: Either = Either.Left(RuntimeException("simulating missing code")) + if (result.isLeft()) { + val cause = result.leftValue.cause + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.kt new file mode 100644 index 00000000000..13cae5cf1d0 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.kt @@ -0,0 +1,31 @@ +// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts +// ISSUE: KT-43260 + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +sealed class Either { + data class Left(val leftValue: L) : Either() + data class Right(val rightValue: R) : Either() +} + +inline fun Either.isLeft(): Boolean { + contract { + returns(true) implies (this@isLeft is Either.Left) + } + return this is Either.Left +} + +inline fun Either.isRight(): Boolean { + contract { + returns(true) implies (this@isRight is Either.Right) + } + return this is Either.Right +} + +fun test() { + val result: Either = Either.Left(RuntimeException("simulating missing code")) + if (result.isLeft()) { + val cause = result.leftValue.cause + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.txt b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.txt new file mode 100644 index 00000000000..0e8b38a2176 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.txt @@ -0,0 +1,35 @@ +package + +public fun test(): kotlin.Unit +public inline fun Either.isLeft(): kotlin.Boolean + Returns(TRUE) -> is Left + +public inline fun Either.isRight(): kotlin.Boolean + Returns(TRUE) -> is Right + +public sealed class Either { + protected constructor Either() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final data class Left : Either { + public constructor Left(/*0*/ leftValue: L) + public final val leftValue: L + public final operator /*synthesized*/ fun component1(): L + public final /*synthesized*/ fun copy(/*0*/ leftValue: L = ...): Either.Left + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String + } + + public final data class Right : Either { + public constructor Right(/*0*/ rightValue: R) + public final val rightValue: R + public final operator /*synthesized*/ fun component1(): R + public final /*synthesized*/ fun copy(/*0*/ rightValue: R = ...): Either.Right + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String + } +} 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 ec4fb21c449..5750b722b5a 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 @@ -32602,6 +32602,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/compositions.kt"); } + @Test + @TestMetadata("contractWithDeepGenerics.kt") + public void testContractWithDeepGenerics() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/contractWithDeepGenerics.kt"); + } + @Test @TestMetadata("contractsOnMembers.kt") public void testContractsOnMembers() throws Exception {