Effects: Add inner representation of contracts
Add model of contracts used by compiler during analysis. It should be thought of as structure which used by the compiler to implement semantics, expressed by the ContractDescription. ========== Effect System introduction: 2/18
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model
|
||||
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
/**
|
||||
* Generic abstraction of static information about some part of program.
|
||||
*/
|
||||
interface Computation {
|
||||
/**
|
||||
* Return-type of corresponding part of program.
|
||||
* If type is unknown or computation doesn't have a type (e.g. if
|
||||
* it is some construction, like "for"-loop), then type is 'null'
|
||||
*/
|
||||
val type: KotlinType?
|
||||
|
||||
/**
|
||||
* List of all possible effects of this computation.
|
||||
* Note that it's not guaranteed to be complete, i.e. if list
|
||||
* doesn't mention some effect, then it should be interpreted
|
||||
* as the absence of information about that effect.
|
||||
*/
|
||||
val effects: List<ESEffect>
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model
|
||||
|
||||
sealed class ESEffect {
|
||||
/**
|
||||
* Returns:
|
||||
* - true, when presence of `this`-effect necessary implies presence of `other`-effect
|
||||
* - false, when presence of `this`-effect necessary implies absence of `other`-effect
|
||||
* - null, when presence of `this`-effect doesn't implies neither presence nor absence of `other`-effect
|
||||
*/
|
||||
abstract fun isImplies(other: ESEffect): Boolean?
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstraction of some side-effect of a computation.
|
||||
*
|
||||
* SimpleEffect alone means that this effect will definitely be fired.
|
||||
*/
|
||||
abstract class SimpleEffect : ESEffect()
|
||||
|
||||
/**
|
||||
* Effect with condition attached to it.
|
||||
*
|
||||
* Has the same semantics as [org.jetbrains.kotlin.contracts.description.ConditionalEffectDeclaration]
|
||||
*/
|
||||
class ConditionalEffect(val condition: ESExpression, val simpleEffect: SimpleEffect) : ESEffect() {
|
||||
// Conservatively, always return null, indicating absence of information
|
||||
override fun isImplies(other: ESEffect): Boolean? = null
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.*
|
||||
|
||||
|
||||
interface ESExpressionVisitor<out T> {
|
||||
fun visitIs(isOperator: ESIs): T
|
||||
fun visitEqual(equal: ESEqual): T
|
||||
fun visitAnd(and: ESAnd): T
|
||||
fun visitNot(not: ESNot): T
|
||||
fun visitOr(or: ESOr): T
|
||||
|
||||
fun visitVariable(esVariable: ESVariable): T
|
||||
fun visitConstant(esConstant: ESConstant): T
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model
|
||||
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
interface ESExpression {
|
||||
fun <T> accept(visitor: ESExpressionVisitor<T>): T
|
||||
}
|
||||
|
||||
interface ESOperator : ESExpression {
|
||||
val functor: Functor
|
||||
}
|
||||
|
||||
abstract class ESValue(override val type: KotlinType?) : Computation, ESExpression {
|
||||
override val effects: List<ESEffect> = listOf()
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model
|
||||
|
||||
/**
|
||||
* An abstraction of effect-generating nature of some computation.
|
||||
*
|
||||
* One can think of Functor as of adjoint to function declaration, responsible
|
||||
* for generating effects. It's [invokeWithArguments] method roughly corresponds
|
||||
* to call of corresponding function, but instead of taking values and returning
|
||||
* values, it takes effects and returns effects.
|
||||
*/
|
||||
interface Functor {
|
||||
fun invokeWithArguments(arguments: List<Computation>): List<ESEffect>
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model
|
||||
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
/**
|
||||
* Collection of information about context.
|
||||
*
|
||||
* This class is pretty close semantically to DataFlowInfo, but
|
||||
* supports broader variety of information (like, not just information
|
||||
* about subtypes of a variable, but also about types that are definitely
|
||||
* not subtypes of a variable).
|
||||
*
|
||||
* Also, it's abstracted away from PSI
|
||||
*/
|
||||
class MutableContextInfo private constructor(
|
||||
val firedEffects: MutableList<ESEffect>,
|
||||
val subtypes: MutableMap<ESValue, MutableSet<KotlinType>>,
|
||||
val notSubtypes: MutableMap<ESValue, MutableSet<KotlinType>>,
|
||||
val equalValues: MutableMap<ESValue, MutableSet<ESValue>>,
|
||||
val notEqualValues: MutableMap<ESValue, MutableSet<ESValue>>
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY: MutableContextInfo
|
||||
get() = MutableContextInfo(
|
||||
firedEffects = mutableListOf(),
|
||||
subtypes = mutableMapOf(),
|
||||
notSubtypes = mutableMapOf(),
|
||||
equalValues = mutableMapOf(),
|
||||
notEqualValues = mutableMapOf()
|
||||
)
|
||||
}
|
||||
|
||||
fun subtype(value: ESValue, type: KotlinType) = apply { subtypes.initAndAdd(value, type) }
|
||||
|
||||
fun notSubtype(value: ESValue, type: KotlinType) = apply { notSubtypes.initAndAdd(value, type) }
|
||||
|
||||
fun equal(left: ESValue, right: ESValue) = apply {
|
||||
equalValues.initAndAdd(left, right)
|
||||
equalValues.initAndAdd(right, left)
|
||||
}
|
||||
|
||||
fun notEqual(left: ESValue, right: ESValue) = apply {
|
||||
notEqualValues.initAndAdd(left, right)
|
||||
notEqualValues.initAndAdd(right, left)
|
||||
}
|
||||
|
||||
fun fire(effect: ESEffect) = apply { firedEffects += effect }
|
||||
|
||||
fun or(other: MutableContextInfo): MutableContextInfo = MutableContextInfo(
|
||||
firedEffects = firedEffects.intersect(other.firedEffects).toMutableList(),
|
||||
subtypes = subtypes.intersect(other.subtypes),
|
||||
notSubtypes = notSubtypes.intersect(other.notSubtypes),
|
||||
equalValues = equalValues.intersect(other.equalValues),
|
||||
notEqualValues = notEqualValues.intersect(other.notEqualValues)
|
||||
)
|
||||
|
||||
fun and(other: MutableContextInfo): MutableContextInfo = MutableContextInfo(
|
||||
firedEffects = firedEffects.union(other.firedEffects).toMutableList(),
|
||||
subtypes = subtypes.union(other.subtypes),
|
||||
notSubtypes = notSubtypes.union(other.notSubtypes),
|
||||
equalValues = equalValues.union(other.equalValues),
|
||||
notEqualValues = notEqualValues.union(other.notEqualValues)
|
||||
)
|
||||
|
||||
private fun <D> MutableMap<ESValue, MutableSet<D>>.intersect(that: MutableMap<ESValue, MutableSet<D>>): MutableMap<ESValue, MutableSet<D>> {
|
||||
val result = mutableMapOf<ESValue, MutableSet<D>>()
|
||||
|
||||
val allKeys = this.keys.intersect(that.keys)
|
||||
allKeys.forEach {
|
||||
val newValues = this[it]!!.intersect(that[it]!!)
|
||||
if (newValues.isNotEmpty()) result[it] = newValues.toMutableSet()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun <D> Map<ESValue, MutableSet<D>>.union(that: Map<ESValue, MutableSet<D>>): MutableMap<ESValue, MutableSet<D>> {
|
||||
val result = mutableMapOf<ESValue, MutableSet<D>>()
|
||||
result.putAll(this)
|
||||
that.entries.forEach { (thatKey, thatValue) ->
|
||||
val oldValue = result[thatKey] ?: mutableSetOf()
|
||||
oldValue.addAll(thatValue)
|
||||
result[thatKey] = oldValue
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun <D> MutableMap<ESValue, MutableSet<D>>.initAndAdd(key: ESValue, value: D) {
|
||||
this.compute(key) { _, maybeValues ->
|
||||
val setOfValues = maybeValues ?: mutableSetOf()
|
||||
setOfValues.add(value)
|
||||
setOfValues
|
||||
}
|
||||
}
|
||||
|
||||
fun print(): String = buildString {
|
||||
val info = this@MutableContextInfo
|
||||
|
||||
fun <D> Map<ESValue, Set<D>>.printMapEntriesWithSeparator(separator: String) {
|
||||
this.entries.filter { it.value.isNotEmpty() }.forEach { (key, value) ->
|
||||
append(key.toString())
|
||||
append(" $separator ")
|
||||
appendln(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
append("Fired effects: ")
|
||||
append(info.firedEffects.joinToString(separator = ", " ))
|
||||
appendln("")
|
||||
|
||||
subtypes.printMapEntriesWithSeparator("is")
|
||||
|
||||
notSubtypes.printMapEntriesWithSeparator("!is")
|
||||
|
||||
equalValues.printMapEntriesWithSeparator("==")
|
||||
|
||||
notEqualValues.printMapEntriesWithSeparator("!=")
|
||||
|
||||
this.toString()
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESConstant
|
||||
import org.jetbrains.kotlin.contracts.model.ConditionalEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
|
||||
abstract class AbstractBinaryFunctor : AbstractReducingFunctor() {
|
||||
override fun doInvocation(arguments: List<Computation>): List<ESEffect> {
|
||||
assert(arguments.size == 2, { "Wrong size of arguments list for Binary functor: expected 2, got ${arguments.size}" })
|
||||
return invokeWithArguments(arguments[0], arguments[1])
|
||||
}
|
||||
|
||||
fun invokeWithArguments(left: Computation, right: Computation): List<ESEffect> {
|
||||
if (left is ESConstant) return invokeWithConstant(right, left)
|
||||
if (right is ESConstant) return invokeWithConstant(left, right)
|
||||
|
||||
val nonInterestingEffects = mutableListOf<ESEffect>()
|
||||
val leftValueReturning = mutableListOf<ConditionalEffect>()
|
||||
val rightValueReturning = mutableListOf<ConditionalEffect>()
|
||||
|
||||
left.effects.forEach {
|
||||
if (it !is ConditionalEffect || it.simpleEffect !is ESReturns || it.simpleEffect.value == ESConstant.WILDCARD)
|
||||
nonInterestingEffects += it
|
||||
else
|
||||
leftValueReturning += it
|
||||
}
|
||||
|
||||
right.effects.forEach {
|
||||
if (it !is ConditionalEffect || it.simpleEffect !is ESReturns || it.simpleEffect.value == ESConstant.WILDCARD)
|
||||
nonInterestingEffects += it
|
||||
else
|
||||
rightValueReturning += it
|
||||
}
|
||||
|
||||
val evaluatedByFunctor = invokeWithReturningEffects(leftValueReturning, rightValueReturning)
|
||||
|
||||
return nonInterestingEffects + evaluatedByFunctor
|
||||
}
|
||||
|
||||
protected abstract fun invokeWithConstant(computation: Computation, constant: ESConstant): List<ESEffect>
|
||||
protected abstract fun invokeWithReturningEffects(left: List<ConditionalEffect>, right: List<ConditionalEffect>): List<ConditionalEffect>
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.Functor
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
import org.jetbrains.kotlin.contracts.model.visitors.Reducer
|
||||
|
||||
/**
|
||||
* Abstract implementation of Functor with some routine house-holding
|
||||
* automatically performed. *
|
||||
*/
|
||||
abstract class AbstractReducingFunctor : Functor {
|
||||
private val reducer = Reducer()
|
||||
|
||||
override fun invokeWithArguments(arguments: List<Computation>): List<ESEffect> = reducer.reduceEffects(doInvocation(arguments))
|
||||
|
||||
abstract protected fun doInvocation(arguments: List<Computation>): List<ESEffect>
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESConstant
|
||||
import org.jetbrains.kotlin.contracts.model.ConditionalEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
|
||||
|
||||
/**
|
||||
* Unary functor that has sequential semantics, i.e. it won't apply to
|
||||
* computations that can't be guaranteed to be finished.
|
||||
*
|
||||
* It provides [applyToFinishingClauses] method for successors, which is guaranteed to
|
||||
* be called only on clauses that haven't failed before reaching functor transformation.
|
||||
*/
|
||||
abstract class AbstractUnaryFunctor : AbstractReducingFunctor() {
|
||||
override fun doInvocation(arguments: List<Computation>): List<ESEffect> {
|
||||
assert(arguments.size == 1, { "Wrong size of arguments list for Unary operator: expected 1, got ${arguments.size}" })
|
||||
return invokeWithArguments(arguments[0])
|
||||
}
|
||||
|
||||
fun invokeWithArguments(arg: Computation): List<ESEffect> {
|
||||
val returning = mutableListOf<ConditionalEffect>()
|
||||
val rest = mutableListOf<ESEffect>()
|
||||
|
||||
arg.effects.forEach { if (it !is ConditionalEffect || it.simpleEffect !is ESReturns || it.simpleEffect.value == ESConstant.WILDCARD) rest += it else returning += it }
|
||||
|
||||
val evaluatedByFunctor = invokeWithReturningEffects(returning)
|
||||
|
||||
return rest + evaluatedByFunctor
|
||||
}
|
||||
|
||||
protected abstract fun invokeWithReturningEffects(list: List<ConditionalEffect>): List<ConditionalEffect>
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESAnd
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESConstant
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESOr
|
||||
import org.jetbrains.kotlin.contracts.model.structure.lift
|
||||
import org.jetbrains.kotlin.contracts.model.ConditionalEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
|
||||
class AndFunctor : AbstractBinaryFunctor() {
|
||||
override fun invokeWithConstant(computation: Computation, constant: ESConstant): List<ESEffect> = when (constant) {
|
||||
ESConstant.TRUE -> computation.effects
|
||||
ESConstant.FALSE -> emptyList()
|
||||
|
||||
// This means that expression isn't typechecked properly
|
||||
else -> computation.effects
|
||||
}
|
||||
|
||||
override fun invokeWithReturningEffects(left: List<ConditionalEffect>, right: List<ConditionalEffect>): List<ConditionalEffect> {
|
||||
/* Normally, `left` and `right` contain clauses that end with Returns(false/true), but if
|
||||
expression wasn't properly typechecked, we could get some senseless clauses here, e.g.
|
||||
with Returns(1) (note that they still *return* as guaranteed by AbstractSequentialBinaryFunctor).
|
||||
We will just ignore such clauses in order to make smartcasting robust while typing */
|
||||
|
||||
val (leftTrue, leftFalse) = left.strictPartition(ESReturns(true.lift()), ESReturns(false.lift()))
|
||||
val (rightTrue, rightFalse) = right.strictPartition(ESReturns(true.lift()), ESReturns(false.lift()))
|
||||
|
||||
val whenLeftReturnsTrue = foldConditionsWithOr(leftTrue)
|
||||
val whenRightReturnsTrue = foldConditionsWithOr(rightTrue)
|
||||
val whenLeftReturnsFalse = foldConditionsWithOr(leftFalse)
|
||||
val whenRightReturnsFalse = foldConditionsWithOr(rightFalse)
|
||||
|
||||
// Even if one of 'Returns(true)' is missing, we still can argue that other condition
|
||||
// *must* be true when whole functor returns true
|
||||
val conditionWhenTrue = applyWithDefault(whenLeftReturnsTrue, whenRightReturnsTrue, { l, r -> ESAnd(l, r) })
|
||||
|
||||
// When whole And-functor returns false, we can only argue that one of arguments was false, and to do so we
|
||||
// have to know *both* 'Returns(false)'-conditions
|
||||
val conditionWhenFalse = applyIfBothNotNull(whenLeftReturnsFalse, whenRightReturnsFalse, { l, r -> ESOr(l, r) })
|
||||
|
||||
val result = mutableListOf<ConditionalEffect>()
|
||||
|
||||
if (conditionWhenTrue != null) {
|
||||
result.add(ConditionalEffect(conditionWhenTrue, ESReturns(true.lift())))
|
||||
}
|
||||
|
||||
if (conditionWhenFalse != null) {
|
||||
result.add(ConditionalEffect(conditionWhenFalse, ESReturns(false.lift())))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESConstant
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESEqual
|
||||
import org.jetbrains.kotlin.contracts.model.ESValue
|
||||
import org.jetbrains.kotlin.contracts.model.structure.lift
|
||||
import org.jetbrains.kotlin.contracts.model.*
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
|
||||
class EqualsFunctor(val isNegated: Boolean) : AbstractReducingFunctor() {
|
||||
/*
|
||||
Equals is a bit tricky case to produce clauses, because e.g. if we want to emit "Returns(true)"-clause,
|
||||
then we have to guarantee that we know *all* cases when 'true' could've been returned, and join
|
||||
them with OR properly.
|
||||
To understand this, consider following example:
|
||||
|
||||
foo(x) == bar(x)
|
||||
Effects of foo(x): "Returns(true) -> x is String"
|
||||
Effects of bar(x): "Returns(true) -> x is Int"
|
||||
|
||||
Of course, we can't say that the whole expression has effect "Returns(true) -> x is String && x is Int"
|
||||
because it could've returned in 'true' also when 'foo(x) == false' and 'bar(x) == false', and we don't
|
||||
know anything about such cases.
|
||||
|
||||
We don't want to code here fair analysis for general cases, because it's too complex. Instead, we just
|
||||
check some specific cases, which are useful enough in practice
|
||||
*/
|
||||
override fun doInvocation(arguments: List<Computation>): List<ESEffect> {
|
||||
assert(arguments.size == 2) { "Equals functor expected 2 arguments, got ${arguments.size}" }
|
||||
|
||||
// TODO: AnnotationConstructorCaller kills this with implicit receiver. Investigate, how.
|
||||
if (arguments.size != 2) return emptyList()
|
||||
return invokeWithArguments(arguments[0], arguments[1])
|
||||
}
|
||||
|
||||
fun invokeWithArguments(left: Computation, right: Computation): List<ESEffect> {
|
||||
// First, check if both arguments are values: then we can produce both 'true' and 'false' clauses
|
||||
if (left is ESValue && right is ESValue) {
|
||||
return equateValues(left, right)
|
||||
}
|
||||
|
||||
// Second, check is at least one of argument is Constant: then we can produce 'true'-clause and maybe even 'false'
|
||||
if (left is ESConstant) {
|
||||
return equateCallAndConstant(right, left)
|
||||
}
|
||||
if (right is ESConstant) {
|
||||
return equateCallAndConstant(left, right)
|
||||
}
|
||||
|
||||
// Otherwise, don't even try to produce something. We can improve this in future, if we would like to
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun equateCallAndConstant(call: Computation, constant: ESConstant): List<ESEffect> {
|
||||
val resultingClauses = mutableListOf<ESEffect>()
|
||||
|
||||
for (effect in call.effects) {
|
||||
if (effect !is ConditionalEffect || effect.simpleEffect !is ESReturns || effect.simpleEffect.value == ESConstant.WILDCARD) {
|
||||
resultingClauses += effect
|
||||
continue
|
||||
}
|
||||
|
||||
if (effect.simpleEffect.value == constant) {
|
||||
val trueClause = ConditionalEffect(effect.condition, ESReturns(isNegated.not().lift()))
|
||||
resultingClauses.add(trueClause)
|
||||
}
|
||||
|
||||
if (effect.simpleEffect.value != constant && effect.simpleEffect.value is ESConstant && isSafeToProduceFalse(call, effect.simpleEffect.value, constant)) {
|
||||
val falseClause = ConditionalEffect(effect.condition, ESReturns(isNegated.lift()))
|
||||
resultingClauses.add(falseClause)
|
||||
}
|
||||
}
|
||||
|
||||
return resultingClauses
|
||||
}
|
||||
|
||||
// It is safe to produce false if we're comparing types which are isomorphic to Boolean. For such types we can be sure, that
|
||||
// if leftConstant != rightConstant, then this is the only way to produce 'false'.
|
||||
private fun isSafeToProduceFalse(leftCall: Computation, leftConstant: ESConstant, rightConstant: ESConstant): Boolean = when {
|
||||
// Comparison of Boolean
|
||||
KotlinBuiltIns.isBoolean(rightConstant.type) && leftCall.type != null && KotlinBuiltIns.isBoolean(leftCall.type!!) -> true
|
||||
|
||||
// Comparison of NULL/NOT_NULL, which is essentially Boolean
|
||||
leftConstant.isNullConstant() && rightConstant.isNullConstant() -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun equateValues(left: ESValue, right: ESValue): List<ESEffect> {
|
||||
return listOf(
|
||||
ConditionalEffect(ESEqual(left, right, isNegated), ESReturns(true.lift())),
|
||||
ConditionalEffect(ESEqual(left, right, isNegated.not()), ESReturns(false.lift()))
|
||||
)
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESOr
|
||||
import org.jetbrains.kotlin.contracts.model.ConditionalEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESExpression
|
||||
|
||||
/**
|
||||
* Applies [operation] to [first] and [second] if both not-null, otherwise returns null
|
||||
*/
|
||||
internal fun <F, S, R> applyIfBothNotNull(first: F?, second: S?, operation: (F, S) -> R): R? =
|
||||
if (first == null || second == null) null else operation(first, second)
|
||||
|
||||
/**
|
||||
* If both [first] and [second] are null, then return null
|
||||
* If only one of [first] and [second] is null, then return other one
|
||||
* Otherwise, return result of [operation]
|
||||
*/
|
||||
internal fun <F : R, S : R, R> applyWithDefault(first: F?, second: S?, operation: (F, S) -> R): R? = when {
|
||||
first == null && second == null -> null
|
||||
first == null -> second
|
||||
second == null -> first
|
||||
else -> operation(first, second)
|
||||
}
|
||||
|
||||
internal fun foldConditionsWithOr(list: List<ConditionalEffect>): ESExpression? =
|
||||
if (list.isEmpty())
|
||||
null
|
||||
else
|
||||
list.map { it.condition }.reduce { acc, condition -> ESOr(acc, condition) }
|
||||
|
||||
/**
|
||||
* Places all clauses that equal to `firstModel` into first list, and all clauses that equal to `secondModel` into second list
|
||||
*/
|
||||
internal fun List<ConditionalEffect>.strictPartition(firstModel: ESEffect, secondModel: ESEffect): Pair<List<ConditionalEffect>, List<ConditionalEffect>> {
|
||||
val first = mutableListOf<ConditionalEffect>()
|
||||
val second = mutableListOf<ConditionalEffect>()
|
||||
|
||||
forEach {
|
||||
if (it.simpleEffect == firstModel) first += it
|
||||
if (it.simpleEffect == secondModel) second += it
|
||||
}
|
||||
|
||||
return first to second
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.*
|
||||
import org.jetbrains.kotlin.contracts.model.structure.*
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class IsFunctor(val type: KotlinType, val isNegated: Boolean) : AbstractReducingFunctor() {
|
||||
override fun doInvocation(arguments: List<Computation>): List<ESEffect> {
|
||||
assert(arguments.size == 1, { "Wrong size of arguments list for Unary operator: expected 1, got ${arguments.size}" })
|
||||
return invokeWithArguments(arguments[0])
|
||||
}
|
||||
|
||||
fun invokeWithArguments(arg: Computation): List<ESEffect> {
|
||||
return if (arg is ESValue)
|
||||
invokeWithValue(arg, null)
|
||||
else
|
||||
arg.effects.flatMap {
|
||||
if (it !is ConditionalEffect || it.simpleEffect !is ESReturns || it.simpleEffect.value == ESConstant.WILDCARD)
|
||||
listOf(it)
|
||||
else
|
||||
invokeWithValue(it.simpleEffect.value, it.condition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun invokeWithValue(value: ESValue, additionalCondition: ESExpression?): List<ConditionalEffect> {
|
||||
val trueIs = ESIs(value, this)
|
||||
val falseIs = ESIs(value, IsFunctor(type, isNegated.not()))
|
||||
|
||||
val trueResult = ConditionalEffect(trueIs.and(additionalCondition), ESReturns(true.lift()))
|
||||
val falseResult = ConditionalEffect(falseIs.and(additionalCondition), ESReturns(false.lift()))
|
||||
return listOf(trueResult, falseResult)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESConstant
|
||||
import org.jetbrains.kotlin.contracts.model.ConditionalEffect
|
||||
|
||||
class NotFunctor : AbstractUnaryFunctor() {
|
||||
override fun invokeWithReturningEffects(list: List<ConditionalEffect>): List<ConditionalEffect> = list.mapNotNull {
|
||||
val outcome = it.simpleEffect
|
||||
|
||||
// Outcome guaranteed to be Returns by AbstractSequentialUnaryFunctor, but value
|
||||
// can be non-boolean in case of type-errors in the whole expression, like "foo(bar) && 1"
|
||||
val returnValue = (outcome as ESReturns).value
|
||||
|
||||
when (returnValue) {
|
||||
ESConstant.TRUE -> ConditionalEffect(it.condition, ESReturns(ESConstant.FALSE))
|
||||
ESConstant.FALSE -> ConditionalEffect(it.condition, ESReturns(ESConstant.TRUE))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESAnd
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESConstant
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESOr
|
||||
import org.jetbrains.kotlin.contracts.model.structure.lift
|
||||
import org.jetbrains.kotlin.contracts.model.ConditionalEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
|
||||
class OrFunctor : AbstractBinaryFunctor() {
|
||||
override fun invokeWithConstant(computation: Computation, constant: ESConstant): List<ESEffect> = when (constant) {
|
||||
ESConstant.FALSE -> computation.effects
|
||||
ESConstant.TRUE -> emptyList()
|
||||
|
||||
// This means that expression isn't typechecked properly
|
||||
else -> computation.effects
|
||||
}
|
||||
|
||||
override fun invokeWithReturningEffects(left: List<ConditionalEffect>, right: List<ConditionalEffect>): List<ConditionalEffect> {
|
||||
/* Normally, `left` and `right` contain clauses that end with Returns(false/true), but if
|
||||
expression wasn't properly typechecked, we could get some senseless clauses here, e.g.
|
||||
with Returns(1) (note that they still *return* as guaranteed by AbstractSequentialBinaryFunctor).
|
||||
We will just ignore such clauses in order to make smartcasting robust while typing */
|
||||
val (leftTrue, leftFalse) = left.strictPartition(ESReturns(true.lift()), ESReturns(false.lift()))
|
||||
val (rightTrue, rightFalse) = right.strictPartition(ESReturns(true.lift()), ESReturns(false.lift()))
|
||||
|
||||
val whenLeftReturnsTrue = foldConditionsWithOr(leftTrue)
|
||||
val whenRightReturnsTrue = foldConditionsWithOr(rightTrue)
|
||||
val whenLeftReturnsFalse = foldConditionsWithOr(leftFalse)
|
||||
val whenRightReturnsFalse = foldConditionsWithOr(rightFalse)
|
||||
|
||||
// When whole Or-functor returns true, all we know is that one of arguments was true.
|
||||
// So, to make a correct clause we have to know *both* 'Returns(true)'-conditions
|
||||
val conditionWhenTrue = applyIfBothNotNull(whenLeftReturnsTrue, whenRightReturnsTrue, { l, r -> ESOr(l, r) })
|
||||
|
||||
// Even if one of 'Returns(false)' is missing, we still can argue that other condition
|
||||
// *must* be false when whole OR-functor returns false
|
||||
val conditionWhenFalse = applyWithDefault(whenLeftReturnsFalse, whenRightReturnsFalse, { l, r -> ESAnd(l, r) })
|
||||
|
||||
val result = mutableListOf<ConditionalEffect>()
|
||||
|
||||
if (conditionWhenTrue != null) {
|
||||
result.add(ConditionalEffect(conditionWhenTrue, ESReturns(true.lift())))
|
||||
}
|
||||
|
||||
if (conditionWhenFalse != null) {
|
||||
result.add(ConditionalEffect(conditionWhenFalse, ESReturns(false.lift())))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.functors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESCalls
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESReturns
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESConstant
|
||||
import org.jetbrains.kotlin.contracts.model.ESValue
|
||||
import org.jetbrains.kotlin.contracts.model.structure.ESVariable
|
||||
import org.jetbrains.kotlin.contracts.model.ConditionalEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.SimpleEffect
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
import org.jetbrains.kotlin.contracts.model.visitors.Substitutor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueDescriptor
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
class SubstitutingFunctor(private val basicEffects: List<ESEffect>, private val ownerFunction: FunctionDescriptor) : AbstractReducingFunctor() {
|
||||
override fun doInvocation(arguments: List<Computation>): List<ESEffect> {
|
||||
if (basicEffects.isEmpty()) return emptyList()
|
||||
|
||||
val receiver = listOfNotNull(ownerFunction.dispatchReceiverParameter?.toESVariable(), ownerFunction.extensionReceiverParameter?.toESVariable())
|
||||
val parameters = receiver + ownerFunction.valueParameters.map { it.toESVariable() }
|
||||
|
||||
assert(parameters.size == arguments.size) {
|
||||
"Arguments and parameters size mismatch: arguments.size = ${arguments.size}, parameters.size = ${parameters.size}"
|
||||
}
|
||||
|
||||
val substitutions = parameters.zip(arguments).toMap()
|
||||
val substitutor = Substitutor(substitutions)
|
||||
val substitutedClauses = mutableListOf<ESEffect>()
|
||||
|
||||
effectsLoop@ for (effect in basicEffects) {
|
||||
when (effect) {
|
||||
is ConditionalEffect -> effect.condition.accept(substitutor)?.effects?.forEach {
|
||||
substitutedClauses.addIfNotNull(combine(effect.simpleEffect, it))
|
||||
}
|
||||
|
||||
is ESCalls -> {
|
||||
val subsitutionForCallable = substitutions[effect.callable] as? ESValue ?: continue@effectsLoop
|
||||
substitutedClauses += ESCalls(subsitutionForCallable, effect.kind)
|
||||
}
|
||||
|
||||
else -> substitutedClauses += effect
|
||||
}
|
||||
}
|
||||
|
||||
return substitutedClauses
|
||||
}
|
||||
|
||||
private fun combine(effect: SimpleEffect, substitutedCondition: ESEffect): ESEffect? {
|
||||
if (substitutedCondition !is ConditionalEffect) return null
|
||||
|
||||
val effectFromCondition = substitutedCondition.simpleEffect
|
||||
if (effectFromCondition !is ESReturns || effectFromCondition.value == ESConstant.WILDCARD) return substitutedCondition
|
||||
|
||||
if (effectFromCondition.value != ESConstant.TRUE) return null
|
||||
|
||||
return ConditionalEffect(substitutedCondition.condition, effect)
|
||||
}
|
||||
|
||||
private fun ValueDescriptor.toESVariable(): ESVariable = ESVariable(this)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.structure
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.Computation
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class CallComputation(override val type: KotlinType?, override val effects: List<ESEffect>) : Computation
|
||||
|
||||
object UNKNOWN_COMPUTATION : Computation {
|
||||
override val type: KotlinType? = null
|
||||
override val effects: List<ESEffect> = emptyList()
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.structure
|
||||
|
||||
import org.jetbrains.kotlin.contracts.description.expressions.ConstantReference
|
||||
import org.jetbrains.kotlin.contracts.description.InvocationKind
|
||||
import org.jetbrains.kotlin.contracts.model.ESEffect
|
||||
import org.jetbrains.kotlin.contracts.model.ESValue
|
||||
import org.jetbrains.kotlin.contracts.model.SimpleEffect
|
||||
|
||||
data class ESCalls(val callable: ESValue, val kind: InvocationKind): SimpleEffect() {
|
||||
override fun isImplies(other: ESEffect): Boolean? {
|
||||
if (other !is ESCalls) return null
|
||||
|
||||
if (callable != other.callable) return null
|
||||
|
||||
return kind == other.kind
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class ESReturns(val value: ESValue): SimpleEffect() {
|
||||
override fun isImplies(other: ESEffect): Boolean? {
|
||||
if (other !is ESReturns) return null
|
||||
|
||||
if (this.value !is ESConstant || other.value !is ESConstant) return this.value == other.value
|
||||
|
||||
// ESReturns(x) implies ESReturns(?) for any 'x'
|
||||
if (other.value.constantReference == ConstantReference.WILDCARD) return true
|
||||
|
||||
return value == other.value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.structure
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.ESExpression
|
||||
import org.jetbrains.kotlin.contracts.model.ESExpressionVisitor
|
||||
import org.jetbrains.kotlin.contracts.model.ESOperator
|
||||
import org.jetbrains.kotlin.contracts.model.ESValue
|
||||
import org.jetbrains.kotlin.contracts.model.functors.*
|
||||
|
||||
class ESAnd(val left: ESExpression, val right: ESExpression): ESOperator {
|
||||
override val functor: AndFunctor = AndFunctor()
|
||||
override fun <T> accept(visitor: ESExpressionVisitor<T>): T = visitor.visitAnd(this)
|
||||
}
|
||||
|
||||
class ESOr(val left: ESExpression, val right: ESExpression): ESOperator {
|
||||
override val functor: OrFunctor = OrFunctor()
|
||||
override fun <T> accept(visitor: ESExpressionVisitor<T>): T = visitor.visitOr(this)
|
||||
}
|
||||
|
||||
class ESNot(val arg: ESExpression): ESOperator {
|
||||
override val functor = NotFunctor()
|
||||
override fun <T> accept(visitor: ESExpressionVisitor<T>): T = visitor.visitNot(this)
|
||||
|
||||
}
|
||||
|
||||
class ESIs(val left: ESValue, override val functor: IsFunctor): ESOperator {
|
||||
val type = functor.type
|
||||
override fun <T> accept(visitor: ESExpressionVisitor<T>): T = visitor.visitIs(this)
|
||||
}
|
||||
|
||||
class ESEqual(val left: ESValue, val right: ESValue, isNegated: Boolean): ESOperator {
|
||||
override val functor: EqualsFunctor = EqualsFunctor(isNegated)
|
||||
override fun <T> accept(visitor: ESExpressionVisitor<T>): T = visitor.visitEqual(this)
|
||||
}
|
||||
|
||||
fun ESExpression.and(other: ESExpression?): ESExpression = if (other == null) this else ESAnd(this, other)
|
||||
fun ESExpression.or(other: ESExpression?): ESExpression = if (other == null) this else ESOr(this, other)
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.structure
|
||||
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ValueDescriptor
|
||||
import org.jetbrains.kotlin.contracts.description.expressions.ConstantReference
|
||||
import org.jetbrains.kotlin.contracts.description.expressions.BooleanConstantReference
|
||||
import org.jetbrains.kotlin.contracts.model.ESExpressionVisitor
|
||||
import org.jetbrains.kotlin.contracts.model.ESValue
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
||||
import java.util.*
|
||||
|
||||
open class ESVariable(val descriptor: ValueDescriptor) : ESValue(descriptor.type) {
|
||||
override fun <T> accept(visitor: ESExpressionVisitor<T>): T = visitor.visitVariable(this)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
|
||||
other as ESVariable
|
||||
|
||||
if (descriptor != other.descriptor) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = descriptor.hashCode()
|
||||
|
||||
override fun toString(): String = descriptor.toString()
|
||||
}
|
||||
|
||||
open class ESConstant private constructor(open val constantReference: ConstantReference, override val type: KotlinType) : ESValue(type) {
|
||||
override fun <T> accept(visitor: ESExpressionVisitor<T>): T = visitor.visitConstant(this)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
|
||||
other as ESConstant
|
||||
|
||||
if (constantReference != other.constantReference) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hashCode(constantReference)
|
||||
|
||||
override fun toString(): String = constantReference.name
|
||||
|
||||
companion object {
|
||||
val TRUE = ESConstant(BooleanConstantReference.TRUE, DefaultBuiltIns.Instance.booleanType)
|
||||
val FALSE = ESConstant(BooleanConstantReference.FALSE, DefaultBuiltIns.Instance.booleanType)
|
||||
val NULL = ESConstant(ConstantReference.NULL, DefaultBuiltIns.Instance.nothingType.makeNullable())
|
||||
val NOT_NULL = ESConstant(ConstantReference.NOT_NULL, DefaultBuiltIns.Instance.anyType)
|
||||
val WILDCARD = ESConstant(ConstantReference.WILDCARD, DefaultBuiltIns.Instance.anyType.makeNullable())
|
||||
}
|
||||
|
||||
fun isNullConstant(): Boolean = this == NULL || this == NOT_NULL
|
||||
}
|
||||
|
||||
fun Boolean.lift(): ESConstant = if (this) ESConstant.TRUE else ESConstant.FALSE
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.visitors
|
||||
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.contracts.model.*
|
||||
import org.jetbrains.kotlin.contracts.model.structure.*
|
||||
|
||||
class InfoCollector(private val observedEffect: ESEffect) : ESExpressionVisitor<MutableContextInfo> {
|
||||
private var isInverted: Boolean = false
|
||||
|
||||
fun collectFromSchema(schema: List<ESEffect>): MutableContextInfo =
|
||||
schema.mapNotNull { collectFromEffect(it) }.fold(MutableContextInfo.EMPTY, { resultingInfo, clauseInfo -> resultingInfo.and(clauseInfo) })
|
||||
|
||||
private fun collectFromEffect(effect: ESEffect): MutableContextInfo? {
|
||||
if (effect !is ConditionalEffect) {
|
||||
return MutableContextInfo.EMPTY.fire(effect)
|
||||
}
|
||||
|
||||
// Check for information from conditional effects
|
||||
return when (observedEffect.isImplies(effect.simpleEffect)) {
|
||||
// observed effect implies clause's effect => clause's effect was fired => clause's condition is true
|
||||
true -> effect.condition.accept(this)
|
||||
|
||||
// Observed effect *may* or *doesn't* implies clause's - no useful information
|
||||
null, false -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitIs(isOperator: ESIs): MutableContextInfo = with(isOperator) {
|
||||
if (functor.isNegated != isInverted) MutableContextInfo.EMPTY.notSubtype(left, type) else MutableContextInfo.EMPTY.subtype(left, type)
|
||||
}
|
||||
|
||||
override fun visitEqual(equal: ESEqual): MutableContextInfo = with(equal) {
|
||||
if (functor.isNegated != isInverted) MutableContextInfo.EMPTY.notEqual(left, right) else MutableContextInfo.EMPTY.equal(left, right)
|
||||
}
|
||||
|
||||
override fun visitAnd(and: ESAnd): MutableContextInfo {
|
||||
val leftInfo = and.left.accept(this)
|
||||
val rightInfo = and.right.accept(this)
|
||||
|
||||
return if (isInverted) leftInfo.or(rightInfo) else leftInfo.and(rightInfo)
|
||||
}
|
||||
|
||||
override fun visitNot(not: ESNot): MutableContextInfo = inverted { not.arg.accept(this) }
|
||||
|
||||
override fun visitOr(or: ESOr): MutableContextInfo {
|
||||
val leftInfo = or.left.accept(this)
|
||||
val rightInfo = or.right.accept(this)
|
||||
return if (isInverted) leftInfo.and(rightInfo) else leftInfo.or(rightInfo)
|
||||
}
|
||||
|
||||
override fun visitVariable(esVariable: ESVariable): MutableContextInfo {
|
||||
return if (esVariable.type != DefaultBuiltIns.Instance.booleanType)
|
||||
MutableContextInfo.EMPTY
|
||||
else
|
||||
MutableContextInfo.EMPTY.equal(esVariable, isInverted.not().lift())
|
||||
}
|
||||
|
||||
override fun visitConstant(esConstant: ESConstant): MutableContextInfo = MutableContextInfo.EMPTY
|
||||
|
||||
private fun <R> inverted(block: () -> R): R {
|
||||
isInverted = isInverted.not()
|
||||
val result = block()
|
||||
isInverted = isInverted.not()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.visitors
|
||||
|
||||
import org.jetbrains.kotlin.contracts.model.*
|
||||
import org.jetbrains.kotlin.contracts.model.structure.*
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||
|
||||
/**
|
||||
* Reduces given list of effects by evaluating constant expressions,
|
||||
* throwing away senseless checks and infeasible clauses, etc.
|
||||
*/
|
||||
class Reducer : ESExpressionVisitor<ESExpression?> {
|
||||
fun reduceEffects(schema: List<ESEffect>): List<ESEffect> =
|
||||
schema.mapNotNull { reduceEffect(it) }
|
||||
|
||||
private fun reduceEffect(effect: ESEffect): ESEffect? {
|
||||
when (effect) {
|
||||
is SimpleEffect -> return effect
|
||||
is ConditionalEffect -> {
|
||||
// Reduce condition
|
||||
val reducedCondition = effect.condition.accept(this) ?: return null
|
||||
|
||||
// Filter never executed conditions
|
||||
if (reducedCondition is ESConstant && reducedCondition == ESConstant.FALSE) return null
|
||||
|
||||
// Add always firing effects
|
||||
if (reducedCondition is ESConstant && reducedCondition == ESConstant.TRUE) return effect.simpleEffect
|
||||
|
||||
// Leave everything else as is
|
||||
return effect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitIs(isOperator: ESIs): ESExpression {
|
||||
val reducedArg = isOperator.left.accept(this) as ESValue
|
||||
|
||||
val result = when (reducedArg) {
|
||||
is ESConstant -> reducedArg.type.isSubtypeOf(isOperator.functor.type)
|
||||
is ESVariable -> if (reducedArg.type?.isSubtypeOf(isOperator.functor.type) == true) true else null
|
||||
else -> throw IllegalStateException("Unknown ESValue: $reducedArg")
|
||||
}
|
||||
|
||||
// Result is unknown, do not evaluate
|
||||
result ?: return ESIs(reducedArg, isOperator.functor)
|
||||
|
||||
return result.xor(isOperator.functor.isNegated).lift()
|
||||
}
|
||||
|
||||
override fun visitEqual(equal: ESEqual): ESExpression {
|
||||
val reducedLeft = equal.left.accept(this) as ESValue
|
||||
val reducedRight = equal.right
|
||||
|
||||
if (reducedLeft is ESConstant) return (reducedLeft == reducedRight).xor(equal.functor.isNegated).lift()
|
||||
|
||||
return ESEqual(reducedLeft, reducedRight, equal.functor.isNegated)
|
||||
}
|
||||
|
||||
override fun visitAnd(and: ESAnd): ESExpression? {
|
||||
val reducedLeft = and.left.accept(this) ?: return null
|
||||
val reducedRight = and.right.accept(this) ?: return null
|
||||
|
||||
return when {
|
||||
reducedLeft == false.lift() || reducedRight == false.lift() -> false.lift()
|
||||
reducedLeft == true.lift() -> reducedRight
|
||||
reducedRight == true.lift() -> reducedLeft
|
||||
else -> ESAnd(reducedLeft, reducedRight)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitOr(or: ESOr): ESExpression? {
|
||||
val reducedLeft = or.left.accept(this) ?: return null
|
||||
val reducedRight = or.right.accept(this) ?: return null
|
||||
|
||||
return when {
|
||||
reducedLeft == true.lift() || reducedRight == true.lift() -> true.lift()
|
||||
reducedLeft == false.lift() -> reducedRight
|
||||
reducedRight == false.lift() -> reducedLeft
|
||||
else -> ESOr(reducedLeft, reducedRight)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitNot(not: ESNot): ESExpression? {
|
||||
val reducedArg = not.arg.accept(this) ?: return null
|
||||
|
||||
return when (reducedArg) {
|
||||
ESConstant.TRUE -> ESConstant.FALSE
|
||||
ESConstant.FALSE -> ESConstant.TRUE
|
||||
else -> reducedArg
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitVariable(esVariable: ESVariable): ESVariable = esVariable
|
||||
|
||||
override fun visitConstant(esConstant: ESConstant): ESConstant = esConstant
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.contracts.model.visitors
|
||||
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.contracts.model.*
|
||||
import org.jetbrains.kotlin.contracts.model.structure.*
|
||||
|
||||
/**
|
||||
* Given an [ESExpression], substitutes all variables in it using provided [substitutions] map,
|
||||
* and then flattens resulting tree, producing an [EffectSchema], which describes effects
|
||||
* of this [ESExpression] with effects of arguments taken into consideration.
|
||||
*/
|
||||
class Substitutor(private val substitutions: Map<ESVariable, Computation>) : ESExpressionVisitor<Computation?> {
|
||||
override fun visitIs(isOperator: ESIs): Computation? {
|
||||
val arg = isOperator.left.accept(this) ?: return null
|
||||
return CallComputation(DefaultBuiltIns.Instance.booleanType, isOperator.functor.invokeWithArguments(arg))
|
||||
}
|
||||
|
||||
override fun visitNot(not: ESNot): Computation? {
|
||||
val arg = not.arg.accept(this) ?: return null
|
||||
return CallComputation(DefaultBuiltIns.Instance.booleanType, not.functor.invokeWithArguments(arg))
|
||||
}
|
||||
|
||||
override fun visitEqual(equal: ESEqual): Computation? {
|
||||
val left = equal.left.accept(this) ?: return null
|
||||
val right = equal.right.accept(this) ?: return null
|
||||
return CallComputation(DefaultBuiltIns.Instance.booleanType, equal.functor.invokeWithArguments(listOf(left, right)))
|
||||
}
|
||||
|
||||
override fun visitAnd(and: ESAnd): Computation? {
|
||||
val left = and.left.accept(this) ?: return null
|
||||
val right = and.right.accept(this) ?: return null
|
||||
return CallComputation(DefaultBuiltIns.Instance.booleanType, and.functor.invokeWithArguments(left, right))
|
||||
}
|
||||
|
||||
override fun visitOr(or: ESOr): Computation? {
|
||||
val left = or.left.accept(this) ?: return null
|
||||
val right = or.right.accept(this) ?: return null
|
||||
return CallComputation(DefaultBuiltIns.Instance.booleanType, or.functor.invokeWithArguments(left, right))
|
||||
}
|
||||
|
||||
override fun visitVariable(esVariable: ESVariable): Computation?
|
||||
= substitutions[esVariable] ?: esVariable
|
||||
|
||||
override fun visitConstant(esConstant: ESConstant): Computation? = esConstant
|
||||
}
|
||||
Reference in New Issue
Block a user