diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt index 9436db538b3..d9fb6d5a810 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt @@ -16,9 +16,10 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts -import com.google.common.collect.SetMultimap import org.jetbrains.kotlin.config.LanguageVersionSettings 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. @@ -26,9 +27,9 @@ import org.jetbrains.kotlin.types.KotlinType */ interface DataFlowInfo { - val completeNullabilityInfo: Map + val completeNullabilityInfo: ImmutableMap - val completeTypeInfo: SetMultimap + val completeTypeInfo: ImmutableMap> /** * Returns collected nullability for the given value, NOT taking its stability into account. diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt index dd0360ee674..d90fddec13b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts import com.google.common.collect.LinkedHashMultimap import com.google.common.collect.SetMultimap +import javaslang.Tuple2 import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.config.LanguageFeature 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.typeUtil.contains import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf +import org.jetbrains.kotlin.util.javaslang.* import org.jetbrains.kotlin.utils.newLinkedHashSetWithExpectedSize import java.util.* +private typealias ImmutableMultimap = ImmutableMap> + +private fun ImmutableMultimap.put(key: K, value: V): ImmutableMultimap { + val oldSet = this[key].getOrElse(ImmutableLinkedHashSet.empty()) + if (oldSet.contains(value)) return this + + return put(key, oldSet.add(value)) +} + internal class DelegatingDataFlowInfo private constructor( - override val completeNullabilityInfo: Map, - override val completeTypeInfo: SetMultimap + override val completeNullabilityInfo: ImmutableMap, + override val completeTypeInfo: ImmutableMultimap ) : DataFlowInfo { - constructor() : this(emptyMap(), newTypeInfo()) + constructor() : this(EMPTY_NULLABILITY_INFO, EMPTY_TYPE_INFO) override fun getCollectedNullability(key: DataFlowValue) = getNullability(key, false) 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) { key.immanentNullability } else { - completeNullabilityInfo.getOrDefault(key, key.immanentNullability) + completeNullabilityInfo[key].getOrElse(key.immanentNullability) } private fun putNullabilityAndTypeInfo( @@ -52,14 +63,14 @@ internal class DelegatingDataFlowInfo private constructor( value: DataFlowValue, nullability: Nullability, languageVersionSettings: LanguageVersionSettings, - typeInfo: SetMultimap? = null, + newTypeInfoBuilder: SetMultimap? = null, affectReceiver: Boolean = true, // TODO: remove me in version 1.3! I'm very dirty hack! // In normal circumstances this should be always true recordUnstable: Boolean = true ): Boolean { if (value.isStable || recordUnstable) { - map.put(value, nullability) + map[value] = nullability } val identifierInfo = value.identifierInfo @@ -72,7 +83,7 @@ internal class DelegatingDataFlowInfo private constructor( val receiverValue = DataFlowValue(identifierInfo.receiverInfo, receiverType) putNullabilityAndTypeInfo( 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) putNullabilityAndTypeInfo( map, subjectValue, nullability, - languageVersionSettings, typeInfo, recordUnstable = false + languageVersionSettings, newTypeInfoBuilder, recordUnstable = false ) if (subjectValue.isStable) { - typeInfo?.put(subjectValue, targetType) + newTypeInfoBuilder?.put(subjectValue, targetType) } } } is IdentifierInfo.Variable -> identifierInfo.bound?.let { putNullabilityAndTypeInfo( map, it, nullability, - languageVersionSettings, typeInfo, recordUnstable = recordUnstable + languageVersionSettings, newTypeInfoBuilder, recordUnstable = recordUnstable ) } } @@ -112,12 +123,12 @@ internal class DelegatingDataFlowInfo private constructor( enrichWithNotNull: Boolean, languageVersionSettings: LanguageVersionSettings ): Set { - val types = completeTypeInfo[key] + val types = completeTypeInfo[key].getOrElse(ImmutableLinkedHashSet.empty()) if (!enrichWithNotNull || getCollectedNullability(key).canBeNull()) { - return types + return types.toJavaSet() } - val enrichedTypes = newLinkedHashSetWithExpectedSize(types.size + 1) + val enrichedTypes = newLinkedHashSetWithExpectedSize(types.size() + 1) val originalType = key.type types.mapTo(enrichedTypes) { type -> type.makeReallyNotNullIfNeeded(languageVersionSettings) } if (originalType.canBeDefinitelyNotNullOrNotNull(languageVersionSettings)) { @@ -164,7 +175,6 @@ internal class DelegatingDataFlowInfo private constructor( val nullabilityOfB = getStableNullability(b) putNullabilityAndTypeInfo(nullability, a, nullabilityOfB, languageVersionSettings, affectReceiver = false) - val newTypeInfo = newTypeInfo() var typesForB = getStableTypes(b, languageVersionSettings) // 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 @@ -173,9 +183,8 @@ internal class DelegatingDataFlowInfo private constructor( if (!b.type.isError && a.type != 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( @@ -185,40 +194,40 @@ internal class DelegatingDataFlowInfo private constructor( val nullabilityOfA = getStableNullability(a) val nullabilityOfB = getStableNullability(b) - val newTypeInfo = newTypeInfo() + val newTypeInfoBuilder = newTypeInfoBuilder() var changed = putNullabilityAndTypeInfo( resultNullabilityInfo, a, nullabilityOfA.refine(nullabilityOfB), languageVersionSettings, - newTypeInfo + newTypeInfoBuilder ) or putNullabilityAndTypeInfo( resultNullabilityInfo, b, nullabilityOfB.refine(nullabilityOfA), languageVersionSettings, - newTypeInfo + newTypeInfoBuilder ) // NB: == has no guarantees of type equality, see KT-11280 for the example if (identityEquals || !nullabilityOfA.canBeNonNull() || !nullabilityOfB.canBeNonNull()) { - newTypeInfo.putAll(a, getStableTypes(b, false, languageVersionSettings)) - newTypeInfo.putAll(b, getStableTypes(a, false, languageVersionSettings)) + newTypeInfoBuilder.putAll(a, getStableTypes(b, false, languageVersionSettings)) + newTypeInfoBuilder.putAll(b, getStableTypes(a, false, languageVersionSettings)) if (a.type != b.type) { // To avoid recording base types of own type if (!a.type.isSubtypeOf(b.type)) { - newTypeInfo.put(a, b.type) + newTypeInfoBuilder.put(a, b.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( @@ -228,24 +237,24 @@ internal class DelegatingDataFlowInfo private constructor( val nullabilityOfA = getStableNullability(a) val nullabilityOfB = getStableNullability(b) - val newTypeInfo = newTypeInfo() + val newTypeInfoBuilder = newTypeInfoBuilder() val changed = putNullabilityAndTypeInfo( resultNullabilityInfo, a, nullabilityOfA.refine(nullabilityOfB.invert()), languageVersionSettings, - newTypeInfo + newTypeInfoBuilder ) or putNullabilityAndTypeInfo( resultNullabilityInfo, b, nullabilityOfB.refine(nullabilityOfA.invert()), 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 (getCollectedTypes(value, languageVersionSettings).contains(type)) return this if (!value.type.isFlexible() && value.type.isSubtypeOf(type)) return this - val newTypeInfo = newTypeInfo() - newTypeInfo.put(value, type) + val nullabilityInfo = hashMapOf() if (!type.isMarkedNullable) { 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 { @@ -280,24 +293,27 @@ internal class DelegatingDataFlowInfo private constructor( } } - val myTypeInfo = completeTypeInfo val otherTypeInfo = other.completeTypeInfo - if (resultNullabilityInfo.isEmpty() && containsAll(myTypeInfo, otherTypeInfo)) { - return this - } return create(this, resultNullabilityInfo, otherTypeInfo) } - private fun Set.containsNothing() = any { KotlinBuiltIns.isNothing(it) } + private fun ImmutableSet?.containsNothing() = this?.any { KotlinBuiltIns.isNothing(it) } ?: false - private fun Set.intersectConsideringNothing(other: Set) = + private fun ImmutableSet?.intersectConsideringNothing(other: ImmutableSet?) = when { other.containsNothing() -> this this.containsNothing() -> other else -> this.intersect(other) } + private fun ImmutableSet?.intersect(other: ImmutableSet?): ImmutableSet = + when { + this == null -> other ?: ImmutableLinkedHashSet.empty() + other == null -> this + else -> this.intersect(other) + } + override fun or(other: DataFlowInfo): DataFlowInfo { if (other === 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 otherTypeInfo = other.completeTypeInfo - val newTypeInfo = newTypeInfo() + val newTypeInfoBuilder = newTypeInfoBuilder() for (key in myTypeInfo.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" companion object { - private val EMPTY_TYPE_INFO = newTypeInfo() + private val EMPTY_NULLABILITY_INFO: ImmutableMap = + ImmutableHashMap.empty() - private fun containsAll(first: SetMultimap, second: SetMultimap) = - first.entries().containsAll(second.entries()) + private val EMPTY_TYPE_INFO: ImmutableMultimap = + ImmutableHashMap.empty() - fun newTypeInfo(): SetMultimap = LinkedHashMultimap.create() + private fun newTypeInfoBuilder(): SetMultimap = + LinkedHashMultimap.create() + + private fun create( + parent: DataFlowInfo?, + updatedNullabilityInfo: Map, + updatedTypeInfo: SetMultimap + ): DataFlowInfo = + create( + parent, + updatedNullabilityInfo, + updatedTypeInfo.asMap().entries.map { Tuple2(it.key, it.value) } + ) private fun create( parent: DataFlowInfo?, updatedNullabilityInfo: Map, // NB: typeInfo must be mutable here! - updatedTypeInfo: SetMultimap, + updatedTypeInfo: Iterable>>, valueToClearPreviousTypeInfo: DataFlowValue? = null ): DataFlowInfo { - val toDelete = newTypeInfo() - for (value in updatedTypeInfo.keys()) { - for (type in updatedTypeInfo[value]) { - // Remove original type (see also KT-10666) - if (value.type == type || type.contains { it.constructor is NewCapturedTypeConstructor }) { - toDelete.put(value, type) - } + if (updatedNullabilityInfo.isEmpty() && updatedTypeInfo.none() && valueToClearPreviousTypeInfo == null) { + return parent ?: DataFlowInfo.EMPTY + } + + val resultingNullabilityInfo = + 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) { - return parent ?: DataFlowInfoFactory.EMPTY - } - - val resultingNullabilityInfo = parent?.completeNullabilityInfo?.toMutableMap() ?: hashMapOf() - resultingNullabilityInfo.putAll(updatedNullabilityInfo) - - val resultingTypeInfo = - LinkedHashMultimap.create( - parent?.completeTypeInfo ?: newTypeInfo() - ) - - valueToClearPreviousTypeInfo?.let(resultingTypeInfo::removeAll) - - for ((value, types) in updatedTypeInfo.asMap().entries) { - resultingTypeInfo.putAll(value, types) + if (resultingNullabilityInfo.isEmpty && resultingTypeInfo.isEmpty) return DataFlowInfo.EMPTY + if (resultingNullabilityInfo === parent?.completeNullabilityInfo && resultingTypeInfo === parent.completeTypeInfo) { + return parent } return DelegatingDataFlowInfo(resultingNullabilityInfo, resultingTypeInfo) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt index a5600f945a3..1e6061297b9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt @@ -38,7 +38,7 @@ class PreliminaryLoopVisitor private constructor() : AssignedVariablesSearcher() var resultFlowInfo = dataFlowInfo val nullabilityMap = resultFlowInfo.completeNullabilityInfo val valueSetToClear = LinkedHashSet() - for (value in nullabilityMap.keys) { + for (value in nullabilityMap.keySet()) { // Only stable variables are under interest here val identifierInfo = value.identifierInfo if (value.kind == DataFlowValue.Kind.STABLE_VARIABLE && identifierInfo is IdentifierInfo.Variable) { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/SmartCastCalculator.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/SmartCastCalculator.kt index 099d56ae544..6e8ec39b4e4 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/SmartCastCalculator.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/SmartCastCalculator.kt @@ -27,7 +27,6 @@ import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.psi.KtExpression 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.calls.smartcasts.* 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.typeUtil.isSubtypeOf import org.jetbrains.kotlin.types.typeUtil.makeNotNullable +import org.jetbrains.kotlin.util.javaslang.component1 +import org.jetbrains.kotlin.util.javaslang.component2 import java.util.* class SmartCastCalculator( @@ -119,10 +120,10 @@ class SmartCastCalculator( val entityToInfo = HashMap() - for ((dataFlowValue, types) in dataFlowInfo.completeTypeInfo.asMap().entries) { + for ((dataFlowValue, types) in dataFlowInfo.completeTypeInfo) { val entity = dataFlowValueToEntity.invoke(dataFlowValue) if (entity != null) { - entityToInfo[entity] = SmartCastInfo(types, false) + entityToInfo[entity] = SmartCastInfo(types.toJavaList(), false) } } @@ -141,4 +142,4 @@ class SmartCastCalculator( val type = classifier.defaultType return getImplicitReceiversWithInstance().firstOrNull { it.type.isSubtypeOf(type) } } -} \ No newline at end of file +} diff --git a/idea/tests/org/jetbrains/kotlin/DataFlowValueRenderingTest.kt b/idea/tests/org/jetbrains/kotlin/DataFlowValueRenderingTest.kt index 8ef7b334723..52f56343d3c 100644 --- a/idea/tests/org/jetbrains/kotlin/DataFlowValueRenderingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/DataFlowValueRenderingTest.kt @@ -66,7 +66,7 @@ abstract class AbstractDataFlowValueRenderingTest: KotlinLightCodeInsightFixture val expression = element.getStrictParentOfType()!! 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") KotlinTestUtils.assertEqualsToFile(File(FileUtil.getNameWithoutExtension(fileName) + ".txt"), actual)