Rewrite DelegatingDataFlowInfo using immutable collections from javaslang
It may be useful because data-flow info is mostly the same for neighbouring expressions and changes between them should be minor. Thus, it should help to avoid making copies of huge maps and reusing them instead
This commit is contained in:
@@ -16,9 +16,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.calls.smartcasts
|
package org.jetbrains.kotlin.resolve.calls.smartcasts
|
||||||
|
|
||||||
import com.google.common.collect.SetMultimap
|
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.util.javaslang.ImmutableMap
|
||||||
|
import org.jetbrains.kotlin.util.javaslang.ImmutableSet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface is intended to provide and edit information about value nullabilities and possible types.
|
* This interface is intended to provide and edit information about value nullabilities and possible types.
|
||||||
@@ -26,9 +27,9 @@ import org.jetbrains.kotlin.types.KotlinType
|
|||||||
*/
|
*/
|
||||||
interface DataFlowInfo {
|
interface DataFlowInfo {
|
||||||
|
|
||||||
val completeNullabilityInfo: Map<DataFlowValue, Nullability>
|
val completeNullabilityInfo: ImmutableMap<DataFlowValue, Nullability>
|
||||||
|
|
||||||
val completeTypeInfo: SetMultimap<DataFlowValue, KotlinType>
|
val completeTypeInfo: ImmutableMap<DataFlowValue, ImmutableSet<KotlinType>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns collected nullability for the given value, NOT taking its stability into account.
|
* Returns collected nullability for the given value, NOT taking its stability into account.
|
||||||
|
|||||||
+104
-73
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts
|
|||||||
|
|
||||||
import com.google.common.collect.LinkedHashMultimap
|
import com.google.common.collect.LinkedHashMultimap
|
||||||
import com.google.common.collect.SetMultimap
|
import com.google.common.collect.SetMultimap
|
||||||
|
import javaslang.Tuple2
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.config.LanguageFeature
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
@@ -26,25 +27,35 @@ import org.jetbrains.kotlin.types.*
|
|||||||
import org.jetbrains.kotlin.types.checker.NewCapturedTypeConstructor
|
import org.jetbrains.kotlin.types.checker.NewCapturedTypeConstructor
|
||||||
import org.jetbrains.kotlin.types.typeUtil.contains
|
import org.jetbrains.kotlin.types.typeUtil.contains
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||||
|
import org.jetbrains.kotlin.util.javaslang.*
|
||||||
import org.jetbrains.kotlin.utils.newLinkedHashSetWithExpectedSize
|
import org.jetbrains.kotlin.utils.newLinkedHashSetWithExpectedSize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
private typealias ImmutableMultimap<K, V> = ImmutableMap<K, ImmutableSet<V>>
|
||||||
|
|
||||||
|
private fun <K, V> ImmutableMultimap<K, V>.put(key: K, value: V): ImmutableMultimap<K, V> {
|
||||||
|
val oldSet = this[key].getOrElse(ImmutableLinkedHashSet.empty<V>())
|
||||||
|
if (oldSet.contains(value)) return this
|
||||||
|
|
||||||
|
return put(key, oldSet.add(value))
|
||||||
|
}
|
||||||
|
|
||||||
internal class DelegatingDataFlowInfo private constructor(
|
internal class DelegatingDataFlowInfo private constructor(
|
||||||
override val completeNullabilityInfo: Map<DataFlowValue, Nullability>,
|
override val completeNullabilityInfo: ImmutableMap<DataFlowValue, Nullability>,
|
||||||
override val completeTypeInfo: SetMultimap<DataFlowValue, KotlinType>
|
override val completeTypeInfo: ImmutableMultimap<DataFlowValue, KotlinType>
|
||||||
) : DataFlowInfo {
|
) : DataFlowInfo {
|
||||||
|
|
||||||
constructor() : this(emptyMap(), newTypeInfo())
|
constructor() : this(EMPTY_NULLABILITY_INFO, EMPTY_TYPE_INFO)
|
||||||
|
|
||||||
override fun getCollectedNullability(key: DataFlowValue) = getNullability(key, false)
|
override fun getCollectedNullability(key: DataFlowValue) = getNullability(key, false)
|
||||||
|
|
||||||
override fun getStableNullability(key: DataFlowValue) = getNullability(key, true)
|
override fun getStableNullability(key: DataFlowValue) = getNullability(key, true)
|
||||||
|
|
||||||
private fun getNullability(key: DataFlowValue, stableOnly: Boolean) =
|
private fun getNullability(key: DataFlowValue, stableOnly: Boolean): Nullability =
|
||||||
if (stableOnly && !key.isStable) {
|
if (stableOnly && !key.isStable) {
|
||||||
key.immanentNullability
|
key.immanentNullability
|
||||||
} else {
|
} else {
|
||||||
completeNullabilityInfo.getOrDefault(key, key.immanentNullability)
|
completeNullabilityInfo[key].getOrElse(key.immanentNullability)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun putNullabilityAndTypeInfo(
|
private fun putNullabilityAndTypeInfo(
|
||||||
@@ -52,14 +63,14 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
value: DataFlowValue,
|
value: DataFlowValue,
|
||||||
nullability: Nullability,
|
nullability: Nullability,
|
||||||
languageVersionSettings: LanguageVersionSettings,
|
languageVersionSettings: LanguageVersionSettings,
|
||||||
typeInfo: SetMultimap<DataFlowValue, KotlinType>? = null,
|
newTypeInfoBuilder: SetMultimap<DataFlowValue, KotlinType>? = null,
|
||||||
affectReceiver: Boolean = true,
|
affectReceiver: Boolean = true,
|
||||||
// TODO: remove me in version 1.3! I'm very dirty hack!
|
// TODO: remove me in version 1.3! I'm very dirty hack!
|
||||||
// In normal circumstances this should be always true
|
// In normal circumstances this should be always true
|
||||||
recordUnstable: Boolean = true
|
recordUnstable: Boolean = true
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (value.isStable || recordUnstable) {
|
if (value.isStable || recordUnstable) {
|
||||||
map.put(value, nullability)
|
map[value] = nullability
|
||||||
}
|
}
|
||||||
|
|
||||||
val identifierInfo = value.identifierInfo
|
val identifierInfo = value.identifierInfo
|
||||||
@@ -72,7 +83,7 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
val receiverValue = DataFlowValue(identifierInfo.receiverInfo, receiverType)
|
val receiverValue = DataFlowValue(identifierInfo.receiverInfo, receiverType)
|
||||||
putNullabilityAndTypeInfo(
|
putNullabilityAndTypeInfo(
|
||||||
map, receiverValue, nullability,
|
map, receiverValue, nullability,
|
||||||
languageVersionSettings, typeInfo, recordUnstable = recordUnstable
|
languageVersionSettings, newTypeInfoBuilder, recordUnstable = recordUnstable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,17 +96,17 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
val subjectValue = DataFlowValue(identifierInfo.subjectInfo, subjectType)
|
val subjectValue = DataFlowValue(identifierInfo.subjectInfo, subjectType)
|
||||||
putNullabilityAndTypeInfo(
|
putNullabilityAndTypeInfo(
|
||||||
map, subjectValue, nullability,
|
map, subjectValue, nullability,
|
||||||
languageVersionSettings, typeInfo, recordUnstable = false
|
languageVersionSettings, newTypeInfoBuilder, recordUnstable = false
|
||||||
)
|
)
|
||||||
if (subjectValue.isStable) {
|
if (subjectValue.isStable) {
|
||||||
typeInfo?.put(subjectValue, targetType)
|
newTypeInfoBuilder?.put(subjectValue, targetType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is IdentifierInfo.Variable -> identifierInfo.bound?.let {
|
is IdentifierInfo.Variable -> identifierInfo.bound?.let {
|
||||||
putNullabilityAndTypeInfo(
|
putNullabilityAndTypeInfo(
|
||||||
map, it, nullability,
|
map, it, nullability,
|
||||||
languageVersionSettings, typeInfo, recordUnstable = recordUnstable
|
languageVersionSettings, newTypeInfoBuilder, recordUnstable = recordUnstable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,12 +123,12 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
enrichWithNotNull: Boolean,
|
enrichWithNotNull: Boolean,
|
||||||
languageVersionSettings: LanguageVersionSettings
|
languageVersionSettings: LanguageVersionSettings
|
||||||
): Set<KotlinType> {
|
): Set<KotlinType> {
|
||||||
val types = completeTypeInfo[key]
|
val types = completeTypeInfo[key].getOrElse(ImmutableLinkedHashSet.empty())
|
||||||
if (!enrichWithNotNull || getCollectedNullability(key).canBeNull()) {
|
if (!enrichWithNotNull || getCollectedNullability(key).canBeNull()) {
|
||||||
return types
|
return types.toJavaSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
val enrichedTypes = newLinkedHashSetWithExpectedSize<KotlinType>(types.size + 1)
|
val enrichedTypes = newLinkedHashSetWithExpectedSize<KotlinType>(types.size() + 1)
|
||||||
val originalType = key.type
|
val originalType = key.type
|
||||||
types.mapTo(enrichedTypes) { type -> type.makeReallyNotNullIfNeeded(languageVersionSettings) }
|
types.mapTo(enrichedTypes) { type -> type.makeReallyNotNullIfNeeded(languageVersionSettings) }
|
||||||
if (originalType.canBeDefinitelyNotNullOrNotNull(languageVersionSettings)) {
|
if (originalType.canBeDefinitelyNotNullOrNotNull(languageVersionSettings)) {
|
||||||
@@ -164,7 +175,6 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
val nullabilityOfB = getStableNullability(b)
|
val nullabilityOfB = getStableNullability(b)
|
||||||
putNullabilityAndTypeInfo(nullability, a, nullabilityOfB, languageVersionSettings, affectReceiver = false)
|
putNullabilityAndTypeInfo(nullability, a, nullabilityOfB, languageVersionSettings, affectReceiver = false)
|
||||||
|
|
||||||
val newTypeInfo = newTypeInfo()
|
|
||||||
var typesForB = getStableTypes(b, languageVersionSettings)
|
var typesForB = getStableTypes(b, languageVersionSettings)
|
||||||
// Own type of B must be recorded separately, e.g. for a constant
|
// Own type of B must be recorded separately, e.g. for a constant
|
||||||
// But if its type is the same as A, there is no reason to do it
|
// But if its type is the same as A, there is no reason to do it
|
||||||
@@ -173,9 +183,8 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
if (!b.type.isError && a.type != b.type) {
|
if (!b.type.isError && a.type != b.type) {
|
||||||
typesForB += b.type
|
typesForB += b.type
|
||||||
}
|
}
|
||||||
newTypeInfo.putAll(a, typesForB)
|
|
||||||
|
|
||||||
return create(this, nullability, if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo, a)
|
return create(this, nullability, listOf(Tuple2(a, typesForB)), a)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equate(
|
override fun equate(
|
||||||
@@ -185,40 +194,40 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
val nullabilityOfA = getStableNullability(a)
|
val nullabilityOfA = getStableNullability(a)
|
||||||
val nullabilityOfB = getStableNullability(b)
|
val nullabilityOfB = getStableNullability(b)
|
||||||
|
|
||||||
val newTypeInfo = newTypeInfo()
|
val newTypeInfoBuilder = newTypeInfoBuilder()
|
||||||
var changed =
|
var changed =
|
||||||
putNullabilityAndTypeInfo(
|
putNullabilityAndTypeInfo(
|
||||||
resultNullabilityInfo,
|
resultNullabilityInfo,
|
||||||
a,
|
a,
|
||||||
nullabilityOfA.refine(nullabilityOfB),
|
nullabilityOfA.refine(nullabilityOfB),
|
||||||
languageVersionSettings,
|
languageVersionSettings,
|
||||||
newTypeInfo
|
newTypeInfoBuilder
|
||||||
) or
|
) or
|
||||||
putNullabilityAndTypeInfo(
|
putNullabilityAndTypeInfo(
|
||||||
resultNullabilityInfo,
|
resultNullabilityInfo,
|
||||||
b,
|
b,
|
||||||
nullabilityOfB.refine(nullabilityOfA),
|
nullabilityOfB.refine(nullabilityOfA),
|
||||||
languageVersionSettings,
|
languageVersionSettings,
|
||||||
newTypeInfo
|
newTypeInfoBuilder
|
||||||
)
|
)
|
||||||
|
|
||||||
// NB: == has no guarantees of type equality, see KT-11280 for the example
|
// NB: == has no guarantees of type equality, see KT-11280 for the example
|
||||||
if (identityEquals || !nullabilityOfA.canBeNonNull() || !nullabilityOfB.canBeNonNull()) {
|
if (identityEquals || !nullabilityOfA.canBeNonNull() || !nullabilityOfB.canBeNonNull()) {
|
||||||
newTypeInfo.putAll(a, getStableTypes(b, false, languageVersionSettings))
|
newTypeInfoBuilder.putAll(a, getStableTypes(b, false, languageVersionSettings))
|
||||||
newTypeInfo.putAll(b, getStableTypes(a, false, languageVersionSettings))
|
newTypeInfoBuilder.putAll(b, getStableTypes(a, false, languageVersionSettings))
|
||||||
if (a.type != b.type) {
|
if (a.type != b.type) {
|
||||||
// To avoid recording base types of own type
|
// To avoid recording base types of own type
|
||||||
if (!a.type.isSubtypeOf(b.type)) {
|
if (!a.type.isSubtypeOf(b.type)) {
|
||||||
newTypeInfo.put(a, b.type)
|
newTypeInfoBuilder.put(a, b.type)
|
||||||
}
|
}
|
||||||
if (!b.type.isSubtypeOf(a.type)) {
|
if (!b.type.isSubtypeOf(a.type)) {
|
||||||
newTypeInfo.put(b, a.type)
|
newTypeInfoBuilder.put(b, a.type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changed = changed or !newTypeInfo.isEmpty
|
changed = changed or !newTypeInfoBuilder.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (changed) create(this, resultNullabilityInfo, if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo) else this
|
return if (changed) create(this, resultNullabilityInfo, newTypeInfoBuilder) else this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disequate(
|
override fun disequate(
|
||||||
@@ -228,24 +237,24 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
val nullabilityOfA = getStableNullability(a)
|
val nullabilityOfA = getStableNullability(a)
|
||||||
val nullabilityOfB = getStableNullability(b)
|
val nullabilityOfB = getStableNullability(b)
|
||||||
|
|
||||||
val newTypeInfo = newTypeInfo()
|
val newTypeInfoBuilder = newTypeInfoBuilder()
|
||||||
val changed =
|
val changed =
|
||||||
putNullabilityAndTypeInfo(
|
putNullabilityAndTypeInfo(
|
||||||
resultNullabilityInfo,
|
resultNullabilityInfo,
|
||||||
a,
|
a,
|
||||||
nullabilityOfA.refine(nullabilityOfB.invert()),
|
nullabilityOfA.refine(nullabilityOfB.invert()),
|
||||||
languageVersionSettings,
|
languageVersionSettings,
|
||||||
newTypeInfo
|
newTypeInfoBuilder
|
||||||
) or
|
) or
|
||||||
putNullabilityAndTypeInfo(
|
putNullabilityAndTypeInfo(
|
||||||
resultNullabilityInfo,
|
resultNullabilityInfo,
|
||||||
b,
|
b,
|
||||||
nullabilityOfB.refine(nullabilityOfA.invert()),
|
nullabilityOfB.refine(nullabilityOfA.invert()),
|
||||||
languageVersionSettings,
|
languageVersionSettings,
|
||||||
newTypeInfo
|
newTypeInfoBuilder
|
||||||
)
|
)
|
||||||
|
|
||||||
return if (changed) create(this, resultNullabilityInfo, if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo) else this
|
return if (changed) create(this, resultNullabilityInfo, newTypeInfoBuilder) else this
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,13 +264,17 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
if (value.type == type) return this
|
if (value.type == type) return this
|
||||||
if (getCollectedTypes(value, languageVersionSettings).contains(type)) return this
|
if (getCollectedTypes(value, languageVersionSettings).contains(type)) return this
|
||||||
if (!value.type.isFlexible() && value.type.isSubtypeOf(type)) return this
|
if (!value.type.isFlexible() && value.type.isSubtypeOf(type)) return this
|
||||||
val newTypeInfo = newTypeInfo()
|
|
||||||
newTypeInfo.put(value, type)
|
|
||||||
val nullabilityInfo = hashMapOf<DataFlowValue, Nullability>()
|
val nullabilityInfo = hashMapOf<DataFlowValue, Nullability>()
|
||||||
if (!type.isMarkedNullable) {
|
if (!type.isMarkedNullable) {
|
||||||
putNullabilityAndTypeInfo(nullabilityInfo, value, NOT_NULL, languageVersionSettings)
|
putNullabilityAndTypeInfo(nullabilityInfo, value, NOT_NULL, languageVersionSettings)
|
||||||
}
|
}
|
||||||
return create(this, if (type.isMarkedNullable) emptyMap() else nullabilityInfo, newTypeInfo)
|
|
||||||
|
return create(
|
||||||
|
this,
|
||||||
|
nullabilityInfo,
|
||||||
|
listOf(Tuple2(value, listOf(type)))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun and(other: DataFlowInfo): DataFlowInfo {
|
override fun and(other: DataFlowInfo): DataFlowInfo {
|
||||||
@@ -280,24 +293,27 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val myTypeInfo = completeTypeInfo
|
|
||||||
val otherTypeInfo = other.completeTypeInfo
|
val otherTypeInfo = other.completeTypeInfo
|
||||||
if (resultNullabilityInfo.isEmpty() && containsAll(myTypeInfo, otherTypeInfo)) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
return create(this, resultNullabilityInfo, otherTypeInfo)
|
return create(this, resultNullabilityInfo, otherTypeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Set<KotlinType>.containsNothing() = any { KotlinBuiltIns.isNothing(it) }
|
private fun ImmutableSet<KotlinType>?.containsNothing() = this?.any { KotlinBuiltIns.isNothing(it) } ?: false
|
||||||
|
|
||||||
private fun Set<KotlinType>.intersectConsideringNothing(other: Set<KotlinType>) =
|
private fun ImmutableSet<KotlinType>?.intersectConsideringNothing(other: ImmutableSet<KotlinType>?) =
|
||||||
when {
|
when {
|
||||||
other.containsNothing() -> this
|
other.containsNothing() -> this
|
||||||
this.containsNothing() -> other
|
this.containsNothing() -> other
|
||||||
else -> this.intersect(other)
|
else -> this.intersect(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ImmutableSet<KotlinType>?.intersect(other: ImmutableSet<KotlinType>?): ImmutableSet<KotlinType> =
|
||||||
|
when {
|
||||||
|
this == null -> other ?: ImmutableLinkedHashSet.empty()
|
||||||
|
other == null -> this
|
||||||
|
else -> this.intersect(other)
|
||||||
|
}
|
||||||
|
|
||||||
override fun or(other: DataFlowInfo): DataFlowInfo {
|
override fun or(other: DataFlowInfo): DataFlowInfo {
|
||||||
if (other === DataFlowInfo.EMPTY) return DataFlowInfo.EMPTY
|
if (other === DataFlowInfo.EMPTY) return DataFlowInfo.EMPTY
|
||||||
if (this === DataFlowInfo.EMPTY) return DataFlowInfo.EMPTY
|
if (this === DataFlowInfo.EMPTY) return DataFlowInfo.EMPTY
|
||||||
@@ -313,62 +329,77 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
|
|
||||||
val myTypeInfo = completeTypeInfo
|
val myTypeInfo = completeTypeInfo
|
||||||
val otherTypeInfo = other.completeTypeInfo
|
val otherTypeInfo = other.completeTypeInfo
|
||||||
val newTypeInfo = newTypeInfo()
|
val newTypeInfoBuilder = newTypeInfoBuilder()
|
||||||
|
|
||||||
for (key in myTypeInfo.keySet()) {
|
for (key in myTypeInfo.keySet()) {
|
||||||
if (key in otherTypeInfo.keySet()) {
|
if (key in otherTypeInfo.keySet()) {
|
||||||
newTypeInfo.putAll(key, myTypeInfo[key].intersectConsideringNothing(otherTypeInfo[key]))
|
newTypeInfoBuilder.putAll(
|
||||||
|
key,
|
||||||
|
myTypeInfo[key].getOrNull().intersectConsideringNothing(otherTypeInfo[key].getOrNull())
|
||||||
|
?: ImmutableLinkedHashSet.empty()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return create(null, resultNullabilityInfo, newTypeInfo)
|
return create(null, resultNullabilityInfo, newTypeInfoBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = if (completeTypeInfo.isEmpty && completeNullabilityInfo.isEmpty()) "EMPTY" else "Non-trivial DataFlowInfo"
|
override fun toString() = if (completeTypeInfo.isEmpty && completeNullabilityInfo.isEmpty()) "EMPTY" else "Non-trivial DataFlowInfo"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val EMPTY_TYPE_INFO = newTypeInfo()
|
private val EMPTY_NULLABILITY_INFO: ImmutableMap<DataFlowValue, Nullability> =
|
||||||
|
ImmutableHashMap.empty()
|
||||||
|
|
||||||
private fun containsAll(first: SetMultimap<DataFlowValue, KotlinType>, second: SetMultimap<DataFlowValue, KotlinType>) =
|
private val EMPTY_TYPE_INFO: ImmutableMultimap<DataFlowValue, KotlinType> =
|
||||||
first.entries().containsAll(second.entries())
|
ImmutableHashMap.empty()
|
||||||
|
|
||||||
fun newTypeInfo(): SetMultimap<DataFlowValue, KotlinType> = LinkedHashMultimap.create<DataFlowValue, KotlinType>()
|
private fun newTypeInfoBuilder(): SetMultimap<DataFlowValue, KotlinType> =
|
||||||
|
LinkedHashMultimap.create()
|
||||||
|
|
||||||
|
private fun create(
|
||||||
|
parent: DataFlowInfo?,
|
||||||
|
updatedNullabilityInfo: Map<DataFlowValue, Nullability>,
|
||||||
|
updatedTypeInfo: SetMultimap<DataFlowValue, KotlinType>
|
||||||
|
): DataFlowInfo =
|
||||||
|
create(
|
||||||
|
parent,
|
||||||
|
updatedNullabilityInfo,
|
||||||
|
updatedTypeInfo.asMap().entries.map { Tuple2(it.key, it.value) }
|
||||||
|
)
|
||||||
|
|
||||||
private fun create(
|
private fun create(
|
||||||
parent: DataFlowInfo?,
|
parent: DataFlowInfo?,
|
||||||
updatedNullabilityInfo: Map<DataFlowValue, Nullability>,
|
updatedNullabilityInfo: Map<DataFlowValue, Nullability>,
|
||||||
// NB: typeInfo must be mutable here!
|
// NB: typeInfo must be mutable here!
|
||||||
updatedTypeInfo: SetMultimap<DataFlowValue, KotlinType>,
|
updatedTypeInfo: Iterable<Tuple2<DataFlowValue, out Iterable<KotlinType>>>,
|
||||||
valueToClearPreviousTypeInfo: DataFlowValue? = null
|
valueToClearPreviousTypeInfo: DataFlowValue? = null
|
||||||
): DataFlowInfo {
|
): DataFlowInfo {
|
||||||
val toDelete = newTypeInfo()
|
if (updatedNullabilityInfo.isEmpty() && updatedTypeInfo.none() && valueToClearPreviousTypeInfo == null) {
|
||||||
for (value in updatedTypeInfo.keys()) {
|
return parent ?: DataFlowInfo.EMPTY
|
||||||
for (type in updatedTypeInfo[value]) {
|
}
|
||||||
// Remove original type (see also KT-10666)
|
|
||||||
if (value.type == type || type.contains { it.constructor is NewCapturedTypeConstructor }) {
|
val resultingNullabilityInfo =
|
||||||
toDelete.put(value, type)
|
updatedNullabilityInfo.entries.fold(
|
||||||
}
|
parent?.completeNullabilityInfo ?: EMPTY_NULLABILITY_INFO
|
||||||
|
) { result, (dataFlowValue, nullability) ->
|
||||||
|
result.put(dataFlowValue, nullability)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultingTypeInfo = parent?.completeTypeInfo ?: EMPTY_TYPE_INFO
|
||||||
|
|
||||||
|
valueToClearPreviousTypeInfo?.let {
|
||||||
|
resultingTypeInfo = resultingTypeInfo.put(it, ImmutableLinkedHashSet.empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((value, types) in updatedTypeInfo) {
|
||||||
|
for (type in types) {
|
||||||
|
if (value.type == type || type.contains { it.constructor is NewCapturedTypeConstructor }) continue
|
||||||
|
resultingTypeInfo = resultingTypeInfo.put(value, type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ((value, type) in toDelete.entries()) {
|
|
||||||
updatedTypeInfo.remove(value, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedNullabilityInfo.isEmpty() && updatedTypeInfo.isEmpty && valueToClearPreviousTypeInfo == null) {
|
if (resultingNullabilityInfo.isEmpty && resultingTypeInfo.isEmpty) return DataFlowInfo.EMPTY
|
||||||
return parent ?: DataFlowInfoFactory.EMPTY
|
if (resultingNullabilityInfo === parent?.completeNullabilityInfo && resultingTypeInfo === parent.completeTypeInfo) {
|
||||||
}
|
return parent
|
||||||
|
|
||||||
val resultingNullabilityInfo = parent?.completeNullabilityInfo?.toMutableMap() ?: hashMapOf()
|
|
||||||
resultingNullabilityInfo.putAll(updatedNullabilityInfo)
|
|
||||||
|
|
||||||
val resultingTypeInfo =
|
|
||||||
LinkedHashMultimap.create<DataFlowValue, KotlinType>(
|
|
||||||
parent?.completeTypeInfo ?: newTypeInfo()
|
|
||||||
)
|
|
||||||
|
|
||||||
valueToClearPreviousTypeInfo?.let(resultingTypeInfo::removeAll)
|
|
||||||
|
|
||||||
for ((value, types) in updatedTypeInfo.asMap().entries) {
|
|
||||||
resultingTypeInfo.putAll(value, types)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DelegatingDataFlowInfo(resultingNullabilityInfo, resultingTypeInfo)
|
return DelegatingDataFlowInfo(resultingNullabilityInfo, resultingTypeInfo)
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ class PreliminaryLoopVisitor private constructor() : AssignedVariablesSearcher()
|
|||||||
var resultFlowInfo = dataFlowInfo
|
var resultFlowInfo = dataFlowInfo
|
||||||
val nullabilityMap = resultFlowInfo.completeNullabilityInfo
|
val nullabilityMap = resultFlowInfo.completeNullabilityInfo
|
||||||
val valueSetToClear = LinkedHashSet<DataFlowValue>()
|
val valueSetToClear = LinkedHashSet<DataFlowValue>()
|
||||||
for (value in nullabilityMap.keys) {
|
for (value in nullabilityMap.keySet()) {
|
||||||
// Only stable variables are under interest here
|
// Only stable variables are under interest here
|
||||||
val identifierInfo = value.identifierInfo
|
val identifierInfo = value.identifierInfo
|
||||||
if (value.kind == DataFlowValue.Kind.STABLE_VARIABLE && identifierInfo is IdentifierInfo.Variable) {
|
if (value.kind == DataFlowValue.Kind.STABLE_VARIABLE && identifierInfo is IdentifierInfo.Variable) {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
|
|||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter
|
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.*
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.*
|
||||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||||
@@ -35,6 +34,8 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
|||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||||
|
import org.jetbrains.kotlin.util.javaslang.component1
|
||||||
|
import org.jetbrains.kotlin.util.javaslang.component2
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class SmartCastCalculator(
|
class SmartCastCalculator(
|
||||||
@@ -119,10 +120,10 @@ class SmartCastCalculator(
|
|||||||
|
|
||||||
val entityToInfo = HashMap<Any, SmartCastInfo>()
|
val entityToInfo = HashMap<Any, SmartCastInfo>()
|
||||||
|
|
||||||
for ((dataFlowValue, types) in dataFlowInfo.completeTypeInfo.asMap().entries) {
|
for ((dataFlowValue, types) in dataFlowInfo.completeTypeInfo) {
|
||||||
val entity = dataFlowValueToEntity.invoke(dataFlowValue)
|
val entity = dataFlowValueToEntity.invoke(dataFlowValue)
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
entityToInfo[entity] = SmartCastInfo(types, false)
|
entityToInfo[entity] = SmartCastInfo(types.toJavaList(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,4 +142,4 @@ class SmartCastCalculator(
|
|||||||
val type = classifier.defaultType
|
val type = classifier.defaultType
|
||||||
return getImplicitReceiversWithInstance().firstOrNull { it.type.isSubtypeOf(type) }
|
return getImplicitReceiversWithInstance().firstOrNull { it.type.isSubtypeOf(type) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ abstract class AbstractDataFlowValueRenderingTest: KotlinLightCodeInsightFixture
|
|||||||
val expression = element.getStrictParentOfType<KtExpression>()!!
|
val expression = element.getStrictParentOfType<KtExpression>()!!
|
||||||
val info = expression.analyze().getDataFlowInfoAfter(expression)
|
val info = expression.analyze().getDataFlowInfoAfter(expression)
|
||||||
|
|
||||||
val allValues = (info.completeTypeInfo.keySet() + info.completeNullabilityInfo.keys).toSet()
|
val allValues = (info.completeTypeInfo.keySet() + info.completeNullabilityInfo.keySet()).toSet()
|
||||||
val actual = allValues.mapNotNull { it.render() }.sorted().joinToString("\n")
|
val actual = allValues.mapNotNull { it.render() }.sorted().joinToString("\n")
|
||||||
|
|
||||||
KotlinTestUtils.assertEqualsToFile(File(FileUtil.getNameWithoutExtension(fileName) + ".txt"), actual)
|
KotlinTestUtils.assertEqualsToFile(File(FileUtil.getNameWithoutExtension(fileName) + ".txt"), actual)
|
||||||
|
|||||||
Reference in New Issue
Block a user