Fix substitution of generic types in contracts

#KT-41078 Fixed
This commit is contained in:
Dmitriy Novozhilov
2021-04-16 13:39:52 +03:00
committed by TeamCityServer
parent 91fedd6a12
commit b530427bb2
8 changed files with 155 additions and 15 deletions
@@ -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 {
@@ -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<ESKotlinType, ESKotlinType>()
val substitution = mutableMapOf<TypeConstructor, UnwrappedType>()
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? {
@@ -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<ESEffect>
}
typealias ESTypeSubstitution = Map<ESKotlinType, ESKotlinType>
class ESTypeSubstitution(
val substitutor: NewTypeSubstitutor,
val builtIns: KotlinBuiltIns
) {
companion object {
fun empty(builtIns: KotlinBuiltIns): ESTypeSubstitution {
return ESTypeSubstitution(EmptySubstitutor, builtIns)
}
}
}
@@ -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<ConditionalEffect> {
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()))
@@ -0,0 +1,31 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// ISSUE: KT-43260
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
sealed class Either<L : Any, R : Any> {
data class Left<L : Any, R : Any>(val leftValue: L) : Either<L, R>()
data class Right<L : Any, R : Any>(val rightValue: R) : Either<L, R>()
}
inline fun <reified L : Any, reified R : Any> Either<L, R>.isLeft(): Boolean {
contract {
returns(true) implies (this@isLeft is Either.Left<L, R>)
}
return this is Either.Left<L, R>
}
inline fun <reified L : Any, reified R : Any> Either<L, R>.isRight(): Boolean {
contract {
returns(true) implies (this@isRight is Either.Right<L, R>)
}
return this is Either.Right<L, R>
}
fun test() {
val result: Either<Exception, Unit> = Either.Left(RuntimeException("simulating missing code"))
if (result.isLeft()) {
val cause = result.leftValue.cause
}
}
@@ -0,0 +1,31 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// ISSUE: KT-43260
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
sealed class Either<L : Any, R : Any> {
data class Left<L : Any, R : Any>(val leftValue: L) : Either<L, R>()
data class Right<L : Any, R : Any>(val rightValue: R) : Either<L, R>()
}
inline fun <reified L : Any, reified R : Any> Either<L, R>.isLeft(): Boolean {
contract {
returns(true) implies (this@isLeft is Either.Left<L, R>)
}
return this is Either.Left<L, R>
}
inline fun <reified L : Any, reified R : Any> Either<L, R>.isRight(): Boolean {
contract {
returns(true) implies (this@isRight is Either.Right<L, R>)
}
return this is Either.Right<L, R>
}
fun test() {
val result: Either<Exception, Unit> = Either.Left(RuntimeException("simulating missing code"))
if (result.isLeft()) {
val cause = <!DEBUG_INFO_SMARTCAST!>result<!>.leftValue.cause
}
}
@@ -0,0 +1,35 @@
package
public fun test(): kotlin.Unit
public inline fun </*0*/ reified L : kotlin.Any, /*1*/ reified R : kotlin.Any> Either<L, R>.isLeft(): kotlin.Boolean
Returns(TRUE) -> <this> is Left<L, R>
public inline fun </*0*/ reified L : kotlin.Any, /*1*/ reified R : kotlin.Any> Either<L, R>.isRight(): kotlin.Boolean
Returns(TRUE) -> <this> is Right<L, R>
public sealed class Either</*0*/ L : kotlin.Any, /*1*/ R : kotlin.Any> {
protected constructor Either</*0*/ L : kotlin.Any, /*1*/ R : kotlin.Any>()
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</*0*/ L : kotlin.Any, /*1*/ R : kotlin.Any> : Either<L, R> {
public constructor Left</*0*/ L : kotlin.Any, /*1*/ R : kotlin.Any>(/*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<L, R>
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</*0*/ L : kotlin.Any, /*1*/ R : kotlin.Any> : Either<L, R> {
public constructor Right</*0*/ L : kotlin.Any, /*1*/ R : kotlin.Any>(/*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<L, R>
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
}
}
@@ -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 {