DelegatingDataFlowInfo: converted to Kotlin

This commit is contained in:
Mikhail Glukhikh
2016-01-18 15:33:35 +03:00
parent 4cc2450b37
commit 5d3186ac49
@@ -14,375 +14,284 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.resolve.calls.smartcasts; package org.jetbrains.kotlin.resolve.calls.smartcasts
import com.google.common.collect.*; import com.google.common.collect.*
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.types.*
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.FlexibleTypesKt; import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
import java.util.*; import java.util.*
import static org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL; import org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability.NOT_NULL
/* package */ class DelegatingDataFlowInfo implements DataFlowInfo { internal class DelegatingDataFlowInfo @JvmOverloads constructor(
private static final ImmutableMap<DataFlowValue, Nullability> EMPTY_NULLABILITY_INFO = ImmutableMap.of(); private val parent: DataFlowInfo?,
private static final SetMultimap<DataFlowValue, KotlinType> EMPTY_TYPE_INFO = newTypeInfo(); private val nullabilityInfo: ImmutableMap<DataFlowValue, Nullability>,
// Also immutable
private val typeInfo: SetMultimap<DataFlowValue, KotlinType>,
/**
* Value for which type info was cleared or reassigned at this point
* so parent type info should not be in use
*/
private val valueWithGivenTypeInfo: DataFlowValue? = null
) : DataFlowInfo {
@Nullable override fun getCompleteNullabilityInfo(): Map<DataFlowValue, Nullability> {
private final DataFlowInfo parent; val result = Maps.newHashMap<DataFlowValue, Nullability>()
var info: DelegatingDataFlowInfo? = this
@NotNull
private final ImmutableMap<DataFlowValue, Nullability> nullabilityInfo;
// Also immutable
@NotNull
private final SetMultimap<DataFlowValue, KotlinType> typeInfo;
/**
* Value for which type info was cleared or reassigned at this point
* so parent type info should not be in use
*/
@Nullable
private final DataFlowValue valueWithGivenTypeInfo;
/* package */ DelegatingDataFlowInfo(
@Nullable DataFlowInfo parent,
@NotNull ImmutableMap<DataFlowValue, Nullability> nullabilityInfo,
@NotNull SetMultimap<DataFlowValue, KotlinType> typeInfo
) {
this(parent, nullabilityInfo, typeInfo, null);
}
/* package */ DelegatingDataFlowInfo(
@Nullable DataFlowInfo parent,
@NotNull ImmutableMap<DataFlowValue, Nullability> nullabilityInfo,
@NotNull SetMultimap<DataFlowValue, KotlinType> typeInfo,
@Nullable DataFlowValue valueWithGivenTypeInfo
) {
this.parent = parent;
this.nullabilityInfo = nullabilityInfo;
this.typeInfo = typeInfo;
this.valueWithGivenTypeInfo = valueWithGivenTypeInfo;
}
@Override
@NotNull
public Map<DataFlowValue, Nullability> getCompleteNullabilityInfo() {
Map<DataFlowValue, Nullability> result = Maps.newHashMap();
DelegatingDataFlowInfo info = this;
while (info != null) { while (info != null) {
for (Map.Entry<DataFlowValue, Nullability> entry : info.nullabilityInfo.entrySet()) { for ((key, value) in info.nullabilityInfo) {
DataFlowValue key = entry.getKey();
Nullability value = entry.getValue();
if (!result.containsKey(key)) { if (!result.containsKey(key)) {
result.put(key, value); result.put(key, value)
} }
} }
info = (DelegatingDataFlowInfo) info.parent; info = info.parent as DelegatingDataFlowInfo?
} }
return result; return result
} }
@Override override fun getCompleteTypeInfo(): SetMultimap<DataFlowValue, KotlinType> {
@NotNull val result = newTypeInfo()
public SetMultimap<DataFlowValue, KotlinType> getCompleteTypeInfo() { val withGivenTypeInfo = HashSet<DataFlowValue>()
SetMultimap<DataFlowValue, KotlinType> result = newTypeInfo(); var info: DelegatingDataFlowInfo? = this
Set<DataFlowValue> withGivenTypeInfo = new HashSet<DataFlowValue>();
DelegatingDataFlowInfo info = this;
while (info != null) { while (info != null) {
for (DataFlowValue key : info.typeInfo.keySet()) { for (key in info.typeInfo.keySet()) {
if (!withGivenTypeInfo.contains(key)) { if (!withGivenTypeInfo.contains(key)) {
result.putAll(key, info.typeInfo.get(key)); result.putAll(key, info.typeInfo.get(key))
} }
} }
if (info.valueWithGivenTypeInfo != null) { info.valueWithGivenTypeInfo?.let { withGivenTypeInfo.add(it) }
withGivenTypeInfo.add(info.valueWithGivenTypeInfo); info = info.parent as DelegatingDataFlowInfo?
}
return result
}
override fun getCollectedNullability(key: DataFlowValue) = getNullability(key, false)
override fun getPredictableNullability(key: DataFlowValue) = getNullability(key, true)
private fun getNullability(key: DataFlowValue, predictableOnly: Boolean) =
if (predictableOnly && !key.isPredictable) {
key.immanentNullability
} }
info = (DelegatingDataFlowInfo) info.parent; else {
} nullabilityInfo[key] ?: if (parent != null) {
return result; parent.getCollectedNullability(key)
}
else {
key.immanentNullability
}
}
private fun putNullability(map: MutableMap<DataFlowValue, Nullability>, value: DataFlowValue, nullability: Nullability): Boolean {
map.put(value, nullability)
return nullability != getCollectedNullability(value)
} }
@Override override fun getCollectedTypes(key: DataFlowValue) = getCollectedTypes(key, true)
@NotNull
public Nullability getCollectedNullability(@NotNull DataFlowValue key) {
return getNullability(key, false);
}
@Override private fun getCollectedTypes(key: DataFlowValue, enrichWithNotNull: Boolean): Set<KotlinType> {
@NotNull val types = collectTypesFromMeAndParents(key)
public Nullability getPredictableNullability(@NotNull DataFlowValue key) {
return getNullability(key, true);
}
@NotNull
private Nullability getNullability(@NotNull DataFlowValue key, boolean predictableOnly) {
if (predictableOnly && !key.isPredictable()) return key.getImmanentNullability();
Nullability nullability = nullabilityInfo.get(key);
return nullability != null ? nullability :
parent != null ? parent.getCollectedNullability(key) :
key.getImmanentNullability();
}
private boolean putNullability(
@NotNull Map<DataFlowValue, Nullability> map,
@NotNull DataFlowValue value,
@NotNull Nullability nullability
) {
map.put(value, nullability);
return nullability != getCollectedNullability(value);
}
@Override
@NotNull
public Set<KotlinType> getCollectedTypes(@NotNull DataFlowValue key) {
return getCollectedTypes(key, true);
}
@NotNull
private Set<KotlinType> getCollectedTypes(@NotNull DataFlowValue key, boolean enrichWithNotNull) {
Set<KotlinType> types = collectTypesFromMeAndParents(key);
if (!enrichWithNotNull || getCollectedNullability(key).canBeNull()) { if (!enrichWithNotNull || getCollectedNullability(key).canBeNull()) {
return types; return types
} }
Set<KotlinType> enrichedTypes = Sets.newHashSetWithExpectedSize(types.size() + 1); val enrichedTypes = Sets.newHashSetWithExpectedSize<KotlinType>(types.size + 1)
KotlinType originalType = key.getType(); val originalType = key.type
if (originalType.isMarkedNullable()) { if (originalType.isMarkedNullable) {
enrichedTypes.add(TypeUtils.makeNotNullable(originalType)); enrichedTypes.add(TypeUtils.makeNotNullable(originalType))
} }
for (KotlinType type : types) { for (type in types) {
enrichedTypes.add(TypeUtils.makeNotNullable(type)); enrichedTypes.add(TypeUtils.makeNotNullable(type))
} }
return enrichedTypes; return enrichedTypes
} }
@Override override fun getPredictableTypes(key: DataFlowValue) = getPredictableTypes(key, true)
@NotNull
public Set<KotlinType> getPredictableTypes(@NotNull DataFlowValue key) {
return getPredictableTypes(key, true);
}
@NotNull private fun getPredictableTypes(key: DataFlowValue, enrichWithNotNull: Boolean) =
private Set<KotlinType> getPredictableTypes(@NotNull DataFlowValue key, boolean enrichWithNotNull) { if (!key.isPredictable) LinkedHashSet() else getCollectedTypes(key, enrichWithNotNull)
if (!key.isPredictable()) {
return new LinkedHashSet<KotlinType>();
}
return getCollectedTypes(key, enrichWithNotNull);
}
/** /**
* Call this function to clear all data flow information about * Call this function to clear all data flow information about
* the given data flow value. * the given data flow value.
*
* @param value * @param value
*/ */
@Override override fun clearValueInfo(value: DataFlowValue): DataFlowInfo {
@NotNull val builder = Maps.newHashMap<DataFlowValue, Nullability>()
public DataFlowInfo clearValueInfo(@NotNull DataFlowValue value) { putNullability(builder, value, Nullability.UNKNOWN)
Map<DataFlowValue, Nullability> builder = Maps.newHashMap(); return DelegatingDataFlowInfo(
putNullability(builder, value, Nullability.UNKNOWN); this,
return new DelegatingDataFlowInfo( ImmutableMap.copyOf(builder),
this, EMPTY_TYPE_INFO,
ImmutableMap.copyOf(builder), value)
EMPTY_TYPE_INFO,
value
);
} }
@Override override fun assign(a: DataFlowValue, b: DataFlowValue): DataFlowInfo {
@NotNull val nullability = Maps.newHashMap<DataFlowValue, Nullability>()
public DataFlowInfo assign(@NotNull DataFlowValue a, @NotNull DataFlowValue b) { val nullabilityOfB = getPredictableNullability(b)
Map<DataFlowValue, Nullability> nullability = Maps.newHashMap(); putNullability(nullability, a, nullabilityOfB)
Nullability nullabilityOfB = getPredictableNullability(b);
putNullability(nullability, a, nullabilityOfB);
SetMultimap<DataFlowValue, KotlinType> newTypeInfo = newTypeInfo(); val newTypeInfo = newTypeInfo()
Set<KotlinType> typesForB = getPredictableTypes(b); var typesForB = getPredictableTypes(b)
// 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 or it's null, there is no reason to do it // But if its type is the same as A or it's null, there is no reason to do it
// because usually null type or own type are not saved in this set // because usually null type or own type are not saved in this set
if (nullabilityOfB.canBeNonNull() && !a.getType().equals(b.getType())) { if (nullabilityOfB.canBeNonNull() && a.type != b.type) {
typesForB.add(b.getType()); typesForB += b.type
} }
newTypeInfo.putAll(a, typesForB); newTypeInfo.putAll(a, typesForB)
return new DelegatingDataFlowInfo( return DelegatingDataFlowInfo(
this, this,
ImmutableMap.copyOf(nullability), ImmutableMap.copyOf(nullability),
newTypeInfo.isEmpty() ? EMPTY_TYPE_INFO : newTypeInfo, if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo,
a a)
);
} }
@Override override fun equate(a: DataFlowValue, b: DataFlowValue): DataFlowInfo {
@NotNull val builder = Maps.newHashMap<DataFlowValue, Nullability>()
public DataFlowInfo equate(@NotNull DataFlowValue a, @NotNull DataFlowValue b) { val nullabilityOfA = getPredictableNullability(a)
Map<DataFlowValue, Nullability> builder = Maps.newHashMap(); val nullabilityOfB = getPredictableNullability(b)
Nullability nullabilityOfA = getPredictableNullability(a);
Nullability nullabilityOfB = getPredictableNullability(b);
boolean changed = false; var changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB)) or
changed |= putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB)); putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA))
changed |= putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA));
SetMultimap<DataFlowValue, KotlinType> newTypeInfo = newTypeInfo(); val newTypeInfo = newTypeInfo()
newTypeInfo.putAll(a, getPredictableTypes(b, false)); newTypeInfo.putAll(a, getPredictableTypes(b, false))
newTypeInfo.putAll(b, getPredictableTypes(a, false)); newTypeInfo.putAll(b, getPredictableTypes(a, false))
if (!a.getType().equals(b.getType())) { if (a.type != b.type) {
// To avoid recording base types of own type // To avoid recording base types of own type
if (!TypeUtilsKt.isSubtypeOf(a.getType(), b.getType())) { if (!a.type.isSubtypeOf(b.type)) {
newTypeInfo.put(a, b.getType()); newTypeInfo.put(a, b.type)
} }
if (!TypeUtilsKt.isSubtypeOf(b.getType(), a.getType())) { if (!b.type.isSubtypeOf(a.type)) {
newTypeInfo.put(b, a.getType()); newTypeInfo.put(b, a.type)
} }
} }
changed |= !newTypeInfo.isEmpty(); changed = changed or !newTypeInfo.isEmpty
return !changed return if (!changed) {
? this this
: new DelegatingDataFlowInfo( }
this, else {
ImmutableMap.copyOf(builder), DelegatingDataFlowInfo(this, ImmutableMap.copyOf(builder), if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo)
newTypeInfo.isEmpty() ? EMPTY_TYPE_INFO : newTypeInfo }
);
} }
@NotNull private fun collectTypesFromMeAndParents(value: DataFlowValue): Set<KotlinType> {
private Set<KotlinType> collectTypesFromMeAndParents(@NotNull DataFlowValue value) { val types = LinkedHashSet<KotlinType>()
Set<KotlinType> types = new LinkedHashSet<KotlinType>();
DataFlowInfo current = this; var current: DataFlowInfo? = this
while (current != null) { while (current != null) {
if (current instanceof DelegatingDataFlowInfo) { if (current is DelegatingDataFlowInfo) {
DelegatingDataFlowInfo delegatingInfo = (DelegatingDataFlowInfo) current; types.addAll(current.typeInfo.get(value))
types.addAll(delegatingInfo.typeInfo.get(value)); if (value == current.valueWithGivenTypeInfo) {
if (value.equals(delegatingInfo.valueWithGivenTypeInfo)) { current = null
current = null;
} }
else { else {
current = delegatingInfo.parent; current = current.parent
} }
} }
else { else {
types.addAll(current.getCollectedTypes(value)); types.addAll(current.getCollectedTypes(value))
break; break
} }
} }
return types; return types
} }
@Override override fun disequate(a: DataFlowValue, b: DataFlowValue): DataFlowInfo {
@NotNull val builder = Maps.newHashMap<DataFlowValue, Nullability>()
public DataFlowInfo disequate(@NotNull DataFlowValue a, @NotNull DataFlowValue b) { val nullabilityOfA = getPredictableNullability(a)
Map<DataFlowValue, Nullability> builder = Maps.newHashMap(); val nullabilityOfB = getPredictableNullability(b)
Nullability nullabilityOfA = getPredictableNullability(a);
Nullability nullabilityOfB = getPredictableNullability(b);
boolean changed = false; var changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB.invert())) or
changed |= putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB.invert())); putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA.invert()))
changed |= putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA.invert())); return if (changed) DelegatingDataFlowInfo(this, ImmutableMap.copyOf(builder), EMPTY_TYPE_INFO) else this
return changed ? new DelegatingDataFlowInfo(this, ImmutableMap.copyOf(builder), EMPTY_TYPE_INFO) : this;
} }
@Override override fun establishSubtyping(value: DataFlowValue, type: KotlinType): DataFlowInfo {
@NotNull if (value.type == type) return this
public DataFlowInfo establishSubtyping(@NotNull DataFlowValue value, @NotNull KotlinType type) { if (getCollectedTypes(value).contains(type)) return this
if (value.getType().equals(type)) return this; if (!value.type.isFlexible() && value.type.isSubtypeOf(type)) return this
if (getCollectedTypes(value).contains(type)) return this; val newNullabilityInfo = if (type.isMarkedNullable) EMPTY_NULLABILITY_INFO else ImmutableMap.of(value, NOT_NULL)
if (!FlexibleTypesKt.isFlexible(value.getType()) && TypeUtilsKt.isSubtypeOf(value.getType(), type)) return this; val newTypeInfo = ImmutableSetMultimap.of(value, type)
ImmutableMap<DataFlowValue, Nullability> newNullabilityInfo = return DelegatingDataFlowInfo(this, newNullabilityInfo, newTypeInfo)
type.isMarkedNullable() ? EMPTY_NULLABILITY_INFO : ImmutableMap.of(value, NOT_NULL);
SetMultimap<DataFlowValue, KotlinType> newTypeInfo = ImmutableSetMultimap.of(value, type);
return new DelegatingDataFlowInfo(this, newNullabilityInfo, newTypeInfo);
} }
@NotNull override fun and(otherInfo: DataFlowInfo): DataFlowInfo {
@Override if (otherInfo === DataFlowInfo.EMPTY) return this
public DataFlowInfo and(@NotNull DataFlowInfo otherInfo) { if (this === DataFlowInfo.EMPTY) return otherInfo
if (otherInfo == EMPTY) return this; if (this === otherInfo) return this
if (this == EMPTY) return otherInfo;
if (this == otherInfo) return this;
assert otherInfo instanceof DelegatingDataFlowInfo : "Unknown DataFlowInfo type: " + otherInfo; assert(otherInfo is DelegatingDataFlowInfo) { "Unknown DataFlowInfo type: " + otherInfo }
DelegatingDataFlowInfo other = (DelegatingDataFlowInfo) otherInfo; val other = otherInfo as DelegatingDataFlowInfo
Map<DataFlowValue, Nullability> nullabilityMapBuilder = Maps.newHashMap(); val nullabilityMapBuilder = Maps.newHashMap<DataFlowValue, Nullability>()
for (Map.Entry<DataFlowValue, Nullability> entry : other.getCompleteNullabilityInfo().entrySet()) { for ((key, otherFlags) in other.completeNullabilityInfo) {
DataFlowValue key = entry.getKey(); val thisFlags = getCollectedNullability(key)
Nullability otherFlags = entry.getValue(); val flags = thisFlags.and(otherFlags)
Nullability thisFlags = getCollectedNullability(key);
Nullability flags = thisFlags.and(otherFlags);
if (flags != thisFlags) { if (flags != thisFlags) {
nullabilityMapBuilder.put(key, flags); nullabilityMapBuilder.put(key, flags)
} }
} }
SetMultimap<DataFlowValue, KotlinType> myTypeInfo = getCompleteTypeInfo(); val myTypeInfo = completeTypeInfo
SetMultimap<DataFlowValue, KotlinType> otherTypeInfo = other.getCompleteTypeInfo(); val otherTypeInfo = other.completeTypeInfo
if (nullabilityMapBuilder.isEmpty() && containsAll(myTypeInfo, otherTypeInfo)) { if (nullabilityMapBuilder.isEmpty() && containsAll(myTypeInfo, otherTypeInfo)) {
return this; return this
} }
return new DelegatingDataFlowInfo(this, ImmutableMap.copyOf(nullabilityMapBuilder), otherTypeInfo); return DelegatingDataFlowInfo(this, ImmutableMap.copyOf(nullabilityMapBuilder), otherTypeInfo)
} }
private static boolean containsAll(SetMultimap<DataFlowValue, KotlinType> first, SetMultimap<DataFlowValue, KotlinType> second) { override fun or(otherInfo: DataFlowInfo): DataFlowInfo {
return first.entries().containsAll(second.entries()); if (otherInfo === DataFlowInfo.EMPTY) return DataFlowInfo.EMPTY
if (this === DataFlowInfo.EMPTY) return DataFlowInfo.EMPTY
if (this === otherInfo) return this
assert(otherInfo is DelegatingDataFlowInfo) { "Unknown DataFlowInfo type: " + otherInfo }
val other = otherInfo as DelegatingDataFlowInfo
val nullabilityMapBuilder = Maps.newHashMap<DataFlowValue, Nullability>()
for ((key, otherFlags) in other.completeNullabilityInfo) {
val thisFlags = getCollectedNullability(key)
nullabilityMapBuilder.put(key, thisFlags.or(otherFlags))
}
val myTypeInfo = completeTypeInfo
val otherTypeInfo = other.completeTypeInfo
val newTypeInfo = newTypeInfo()
for (key in Sets.intersection(myTypeInfo.keySet(), otherTypeInfo.keySet())) {
val thisTypes = myTypeInfo.get(key)
val otherTypes = otherTypeInfo.get(key)
newTypeInfo.putAll(key, Sets.intersection(thisTypes, otherTypes))
}
if (nullabilityMapBuilder.isEmpty() && newTypeInfo.isEmpty) {
return DataFlowInfo.EMPTY
}
return DelegatingDataFlowInfo(null, ImmutableMap.copyOf(nullabilityMapBuilder), newTypeInfo)
} }
@NotNull override fun toString() = if (typeInfo.isEmpty && nullabilityInfo.isEmpty()) "EMPTY" else "Non-trivial DataFlowInfo"
@Override
public DataFlowInfo or(@NotNull DataFlowInfo otherInfo) {
if (otherInfo == EMPTY) return EMPTY;
if (this == EMPTY) return EMPTY;
if (this == otherInfo) return this;
assert otherInfo instanceof DelegatingDataFlowInfo : "Unknown DataFlowInfo type: " + otherInfo; companion object {
DelegatingDataFlowInfo other = (DelegatingDataFlowInfo) otherInfo; private val EMPTY_NULLABILITY_INFO = ImmutableMap.of<DataFlowValue, Nullability>()
private val EMPTY_TYPE_INFO = newTypeInfo()
Map<DataFlowValue, Nullability> nullabilityMapBuilder = Maps.newHashMap(); private fun containsAll(first: SetMultimap<DataFlowValue, KotlinType>, second: SetMultimap<DataFlowValue, KotlinType>) =
for (Map.Entry<DataFlowValue, Nullability> entry : other.getCompleteNullabilityInfo().entrySet()) { first.entries().containsAll(second.entries())
DataFlowValue key = entry.getKey();
Nullability otherFlags = entry.getValue();
Nullability thisFlags = getCollectedNullability(key);
nullabilityMapBuilder.put(key, thisFlags.or(otherFlags));
}
SetMultimap<DataFlowValue, KotlinType> myTypeInfo = getCompleteTypeInfo(); @JvmStatic
SetMultimap<DataFlowValue, KotlinType> otherTypeInfo = other.getCompleteTypeInfo(); fun newTypeInfo(): SetMultimap<DataFlowValue, KotlinType> = LinkedHashMultimap.create<DataFlowValue, KotlinType>()
SetMultimap<DataFlowValue, KotlinType> newTypeInfo = newTypeInfo();
for (DataFlowValue key : Sets.intersection(myTypeInfo.keySet(), otherTypeInfo.keySet())) {
Set<KotlinType> thisTypes = myTypeInfo.get(key);
Set<KotlinType> otherTypes = otherTypeInfo.get(key);
newTypeInfo.putAll(key, Sets.intersection(thisTypes, otherTypes));
}
if (nullabilityMapBuilder.isEmpty() && newTypeInfo.isEmpty()) {
return EMPTY;
}
return new DelegatingDataFlowInfo(null, ImmutableMap.copyOf(nullabilityMapBuilder), newTypeInfo);
}
@NotNull
/* package */ static SetMultimap<DataFlowValue, KotlinType> newTypeInfo() {
return LinkedHashMultimap.create();
}
@Override
public String toString() {
if (typeInfo.isEmpty() && nullabilityInfo.isEmpty()) {
return "EMPTY";
}
return "Non-trivial DataFlowInfo";
} }
} }