From bf1f94c185f324f87ef722c36ce2b1900b249815 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Fri, 20 Oct 2017 14:50:22 +0300 Subject: [PATCH] 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 --- .../resolve/calls/smartcasts/DataFlowInfo.kt | 7 +- .../smartcasts/DelegatingDataFlowInfo.kt | 177 ++++++++++-------- .../expressions/PreliminaryLoopVisitor.kt | 2 +- .../kotlin/idea/core/SmartCastCalculator.kt | 9 +- .../kotlin/DataFlowValueRenderingTest.kt | 2 +- 5 files changed, 115 insertions(+), 82 deletions(-) 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)