Simplify OverrideResolver#filterOverrides
- move source vs binary equivalent call filtering hack to
ResolutionResultsHandler (see c4778bfe5a for
the original commit introducing this behavior)
- rewrite the algorithm so that it doesn't need noDuplicates in the beginning:
modulo the hack above whish is only needed in ResolutionResultsHandler,
descriptor equivalence is checked anyway in OverrideResolver#overrides
This commit is contained in:
@@ -22,7 +22,6 @@ import com.intellij.psi.PsiElement;
|
|||||||
import com.intellij.util.SmartList;
|
import com.intellij.util.SmartList;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
import com.intellij.util.containers.SmartHashSet;
|
import com.intellij.util.containers.SmartHashSet;
|
||||||
import com.intellij.util.containers.hash.EqualityPolicy;
|
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import kotlin.collections.CollectionsKt;
|
import kotlin.collections.CollectionsKt;
|
||||||
import kotlin.jvm.functions.Function1;
|
import kotlin.jvm.functions.Function1;
|
||||||
@@ -40,11 +39,9 @@ import org.jetbrains.kotlin.resolve.dataClassUtils.DataClassUtilsKt;
|
|||||||
import org.jetbrains.kotlin.types.*;
|
import org.jetbrains.kotlin.types.*;
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
||||||
import org.jetbrains.kotlin.utils.FunctionsKt;
|
import org.jetbrains.kotlin.utils.FunctionsKt;
|
||||||
import org.jetbrains.kotlin.utils.HashSetUtil;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static kotlin.collections.CollectionsKt.sortedBy;
|
|
||||||
import static org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION;
|
import static org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION;
|
||||||
import static org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE;
|
import static org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE;
|
||||||
import static org.jetbrains.kotlin.diagnostics.Errors.*;
|
import static org.jetbrains.kotlin.diagnostics.Errors.*;
|
||||||
@@ -108,45 +105,6 @@ public class OverrideResolver {
|
|||||||
return filterOverrides(candidateSet, FunctionsKt.<CallableDescriptor>identity());
|
return filterOverrides(candidateSet, FunctionsKt.<CallableDescriptor>identity());
|
||||||
}
|
}
|
||||||
|
|
||||||
// In a multi-module project different "copies" of the same class may be present in different libraries,
|
|
||||||
// that's why we use structural equivalence for members (DescriptorEquivalenceForOverrides).
|
|
||||||
//
|
|
||||||
// Sometimes we should compare "copies" from sources and from binary files.
|
|
||||||
// But we cannot compare return types for such copies, because it may lead us to recursive problem (see KT-11995).
|
|
||||||
// Because of this we compare them without return type and choose descriptor from source if we found duplicate.
|
|
||||||
@NotNull
|
|
||||||
private static <D> Set<D> noDuplicates(
|
|
||||||
@NotNull Set<D> candidateSet,
|
|
||||||
@NotNull final Function1<? super D, ? extends CallableDescriptor> transform
|
|
||||||
) {
|
|
||||||
List<D> fromSourcesGoesFirst = sortedBy(candidateSet, new Function1<D, Integer>() {
|
|
||||||
@Override
|
|
||||||
public Integer invoke(D d) {
|
|
||||||
return DescriptorToSourceUtils.descriptorToDeclaration(transform.invoke(d)) != null ? 0 : 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return HashSetUtil.linkedHashSet(
|
|
||||||
fromSourcesGoesFirst,
|
|
||||||
new EqualityPolicy<D>() {
|
|
||||||
@Override
|
|
||||||
public int getHashCode(D d) {
|
|
||||||
return DescriptorUtils.getFqName(transform.invoke(d).getContainingDeclaration()).hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEqual(D d1, D d2) {
|
|
||||||
CallableDescriptor f = transform.invoke(d1).getOriginal();
|
|
||||||
CallableDescriptor g = transform.invoke(d2).getOriginal();
|
|
||||||
|
|
||||||
boolean ignoreReturnType = (DescriptorToSourceUtils.descriptorToDeclaration(f) == null) !=
|
|
||||||
(DescriptorToSourceUtils.descriptorToDeclaration(g) == null);
|
|
||||||
|
|
||||||
return DescriptorEquivalenceForOverrides.INSTANCE.areCallableDescriptorsEquivalent(f, g, ignoreReturnType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static <D> Set<D> filterOverrides(
|
public static <D> Set<D> filterOverrides(
|
||||||
@NotNull Set<D> candidateSet,
|
@NotNull Set<D> candidateSet,
|
||||||
@@ -154,33 +112,34 @@ public class OverrideResolver {
|
|||||||
) {
|
) {
|
||||||
if (candidateSet.size() <= 1) return candidateSet;
|
if (candidateSet.size() <= 1) return candidateSet;
|
||||||
|
|
||||||
// Here we filter out structurally equivalent descriptors before processing overrides, because such descriptors
|
Set<D> result = new LinkedHashSet<D>();
|
||||||
// "override" each other (overrides(f, g) = overrides(g, f) = true) and the code below removes them all from the
|
|
||||||
// candidates, unless we first compute noDuplicates
|
|
||||||
Set<D> noDuplicates = noDuplicates(candidateSet, transform);
|
|
||||||
|
|
||||||
Set<D> candidates = Sets.newLinkedHashSet();
|
|
||||||
outerLoop:
|
outerLoop:
|
||||||
for (D meD : noDuplicates) {
|
for (D meD : candidateSet) {
|
||||||
CallableDescriptor me = transform.invoke(meD);
|
CallableDescriptor me = transform.invoke(meD);
|
||||||
for (D otherD : noDuplicates) {
|
for (Iterator<D> iterator = result.iterator(); iterator.hasNext(); ) {
|
||||||
|
D otherD = iterator.next();
|
||||||
CallableDescriptor other = transform.invoke(otherD);
|
CallableDescriptor other = transform.invoke(otherD);
|
||||||
if (me != other && overrides(other, me)) {
|
if (overrides(me, other)) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
else if (overrides(other, me)) {
|
||||||
continue outerLoop;
|
continue outerLoop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
candidates.add(meD);
|
result.add(meD);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert !candidates.isEmpty() : "All candidates filtered out from " + candidateSet;
|
assert !result.isEmpty() : "All candidates filtered out from " + candidateSet;
|
||||||
|
|
||||||
return candidates;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether f overrides g
|
* @return whether f overrides g
|
||||||
*/
|
*/
|
||||||
public static <D extends CallableDescriptor> boolean overrides(@NotNull D f, @NotNull D g) {
|
public static <D extends CallableDescriptor> boolean overrides(@NotNull D f, @NotNull D g) {
|
||||||
|
// In a multi-module project different "copies" of the same class may be present in different libraries,
|
||||||
|
// that's why we use structural equivalence for members (DescriptorEquivalenceForOverrides).
|
||||||
// This first check cover the case of duplicate classes in different modules:
|
// This first check cover the case of duplicate classes in different modules:
|
||||||
// when B is defined in modules m1 and m2, and C (indirectly) inherits from both versions,
|
// when B is defined in modules m1 and m2, and C (indirectly) inherits from both versions,
|
||||||
// we'll be getting sets of members that do not override each other, but are structurally equivalent.
|
// we'll be getting sets of members that do not override each other, but are structurally equivalent.
|
||||||
|
|||||||
+44
-4
@@ -17,10 +17,13 @@
|
|||||||
package org.jetbrains.kotlin.resolve.calls.results;
|
package org.jetbrains.kotlin.resolve.calls.results;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import kotlin.collections.CollectionsKt;
|
||||||
import kotlin.jvm.functions.Function1;
|
import kotlin.jvm.functions.Function1;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorEquivalenceForOverrides;
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
|
||||||
import org.jetbrains.kotlin.resolve.OverrideResolver;
|
import org.jetbrains.kotlin.resolve.OverrideResolver;
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
|
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext;
|
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext;
|
||||||
@@ -29,9 +32,7 @@ import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall;
|
|||||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
|
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
|
||||||
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy;
|
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus.*;
|
import static org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus.*;
|
||||||
|
|
||||||
@@ -44,6 +45,14 @@ public class ResolutionResultsHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static final Function1<MutableResolvedCall<?>, Integer> MAP_RESOLVED_CALL_TO_SOURCE_PRESENCE =
|
||||||
|
new Function1<MutableResolvedCall<?>, Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer invoke(MutableResolvedCall<?> resolvedCall) {
|
||||||
|
return DescriptorToSourceUtils.descriptorToDeclaration(resolvedCall.getResultingDescriptor()) != null ? 0 : 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final OverloadingConflictResolver overloadingConflictResolver;
|
private final OverloadingConflictResolver overloadingConflictResolver;
|
||||||
|
|
||||||
public ResolutionResultsHandler(@NotNull OverloadingConflictResolver overloadingConflictResolver) {
|
public ResolutionResultsHandler(@NotNull OverloadingConflictResolver overloadingConflictResolver) {
|
||||||
@@ -186,6 +195,35 @@ public class ResolutionResultsHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sometimes we should compare "copies" from sources and from binary files.
|
||||||
|
// But we cannot compare return types for such copies, because it may lead us to recursive problem (see KT-11995).
|
||||||
|
// Because of this we compare them without return type and choose descriptor from source if we found duplicate.
|
||||||
|
@NotNull
|
||||||
|
private static <D extends CallableDescriptor> Set<MutableResolvedCall<D>> filterOutEquivalentCalls(
|
||||||
|
@NotNull Set<MutableResolvedCall<D>> candidates
|
||||||
|
) {
|
||||||
|
if (candidates.size() <= 1) return candidates;
|
||||||
|
|
||||||
|
List<MutableResolvedCall<D>> fromSourcesGoesFirst = CollectionsKt.sortedBy(candidates, MAP_RESOLVED_CALL_TO_SOURCE_PRESENCE);
|
||||||
|
|
||||||
|
Set<MutableResolvedCall<D>> result = new LinkedHashSet<MutableResolvedCall<D>>();
|
||||||
|
outerLoop:
|
||||||
|
for (MutableResolvedCall<D> meD : fromSourcesGoesFirst) {
|
||||||
|
for (MutableResolvedCall<D> otherD : result) {
|
||||||
|
D me = meD.getResultingDescriptor();
|
||||||
|
D other = otherD.getResultingDescriptor();
|
||||||
|
boolean ignoreReturnType = (DescriptorToSourceUtils.descriptorToDeclaration(me) == null) !=
|
||||||
|
(DescriptorToSourceUtils.descriptorToDeclaration(other) == null);
|
||||||
|
if (DescriptorEquivalenceForOverrides.INSTANCE.areCallableDescriptorsEquivalent(me, other, ignoreReturnType)) {
|
||||||
|
continue outerLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(meD);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private <D extends CallableDescriptor> OverloadResolutionResultsImpl<D> chooseAndReportMaximallySpecific(
|
private <D extends CallableDescriptor> OverloadResolutionResultsImpl<D> chooseAndReportMaximallySpecific(
|
||||||
@NotNull Set<MutableResolvedCall<D>> candidates,
|
@NotNull Set<MutableResolvedCall<D>> candidates,
|
||||||
@@ -201,7 +239,9 @@ public class ResolutionResultsHandler {
|
|||||||
candidates = overloadingConflictResolver.findMaximallySpecificVariableAsFunctionCalls(candidates);
|
candidates = overloadingConflictResolver.findMaximallySpecificVariableAsFunctionCalls(candidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<MutableResolvedCall<D>> noOverrides = OverrideResolver.filterOverrides(candidates, MAP_RESOLVED_CALL_TO_RESULTING_DESCRIPTOR);
|
Set<MutableResolvedCall<D>> noEquivalentCalls = filterOutEquivalentCalls(candidates);
|
||||||
|
Set<MutableResolvedCall<D>> noOverrides =
|
||||||
|
OverrideResolver.filterOverrides(noEquivalentCalls, MAP_RESOLVED_CALL_TO_RESULTING_DESCRIPTOR);
|
||||||
if (noOverrides.size() == 1) {
|
if (noOverrides.size() == 1) {
|
||||||
return OverloadResolutionResultsImpl.success(noOverrides.iterator().next());
|
return OverloadResolutionResultsImpl.success(noOverrides.iterator().next());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,26 +16,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.utils;
|
package org.jetbrains.kotlin.utils;
|
||||||
|
|
||||||
import com.intellij.util.containers.hash.EqualityPolicy;
|
|
||||||
import com.intellij.util.containers.hash.HashSet;
|
import com.intellij.util.containers.hash.HashSet;
|
||||||
import com.intellij.util.containers.hash.LinkedHashMap;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class HashSetUtil {
|
public class HashSetUtil {
|
||||||
@NotNull
|
|
||||||
public static <T> Set<T> linkedHashSet(@NotNull Collection<T> set, @NotNull EqualityPolicy<T> policy) {
|
|
||||||
// this implementation of LinkedHashMap doesn't admit nulls as values
|
|
||||||
Map<T, String> map = new LinkedHashMap<T, String>(policy);
|
|
||||||
for (T t : set) {
|
|
||||||
map.put(t, "");
|
|
||||||
}
|
|
||||||
return map.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static <T> Set<T> symmetricDifference(@NotNull Set<T> set1, @NotNull Set<T> set2) {
|
public static <T> Set<T> symmetricDifference(@NotNull Set<T> set1, @NotNull Set<T> set2) {
|
||||||
Set<T> intersection = new HashSet<T>();
|
Set<T> intersection = new HashSet<T>();
|
||||||
|
|||||||
Reference in New Issue
Block a user