diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java index 9bce7bf408d4..bc73faecd034 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java @@ -204,10 +204,22 @@ private BoundSet resolveSmallestSet(Set as, BoundSet boundSet) { checkNoFalse(boundSet, "on entry to resolveSmallestSet for", as); if (boundSet.containsCapture(as)) { - BoundSet resolvedBounds = resolveWithoutCapture(as, boundSet); - boundSet.getInstantiatedVariables().forEach(as::remove); - // Then resolve the capture variables - return resolveWithCapture(as, resolvedBounds, context); + // Wait to resolve variables that have an equal bound to a capture variable that has not been + // resloved. + Set deferred = new LinkedHashSet<>(); + for (Variable v : as) { + if (!v.isCaptureVariable() && hasUnresolvedEqualBoundToCaptureWithin(v, as)) { + deferred.add(v); + } + } + Set toResolveNow = new LinkedHashSet<>(as); + toResolveNow.removeAll(deferred); + + BoundSet resolvedBounds = resolveWithoutCapture(toResolveNow, boundSet); + toResolveNow.removeAll(boundSet.getInstantiatedVariables()); + // Then resolve the capture variables (and any non-captures that depend on them directly). + deferred.addAll(toResolveNow); + return resolveWithCapture(deferred, resolvedBounds, context); } else { BoundSet copy = new BoundSet(boundSet); // Save the current bounds in case the first attempt at resolution fails. @@ -227,6 +239,28 @@ private BoundSet resolveSmallestSet(Set as, BoundSet boundSet) { } } + /** + * Returns true if {@code v} has an {@code EQUAL} bound that mentions a capture variable in {@code + * as} that does not yet have an instantiation. + * + * @param v a variable + * @param as a set of variables being resolved together + * @return true if {@code v} has an unresolved {@code EQUAL} bound to a capture variable in {@code + * as} + */ + private static boolean hasUnresolvedEqualBoundToCaptureWithin(Variable v, Set as) { + for (AbstractType t : v.getBounds().bounds.get(VariableBounds.BoundKind.EQUAL)) { + for (Variable mentioned : t.getInferenceVariables()) { + if (mentioned.isCaptureVariable() + && as.contains(mentioned) + && !mentioned.getBounds().hasInstantiation()) { + return true; + } + } + } + return false; + } + /** * Apply the instantiated variables to the bounds of the variables in {@code variables}. This may * result in an instantiation being found of a variable in {@code variables}. All instantiated diff --git a/framework/tests/all-systems/Issue7678.java b/framework/tests/all-systems/Issue7678.java new file mode 100644 index 000000000000..6ff5647daaa1 --- /dev/null +++ b/framework/tests/all-systems/Issue7678.java @@ -0,0 +1,31 @@ +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; + +// Related to lambda parameters. +@SuppressWarnings("all") +public class Issue7678 { + static class MyMap { + static Collector> toMyMap( + Function keyFunction, + Function valueFunction) { + return null; + } + } + + static class BiStream { + static Collector> toBiStream( + Collector valueCollector) { + return null; + } + + BiStream mapValues(Function valueMapper) { + return null; + } + } + + private void test(Stream items) { + Function length = String::length; + items.collect(BiStream.toBiStream(MyMap.toMyMap(length, s -> s))).mapValues(byLen -> byLen); + } +} diff --git a/framework/tests/all-systems/Issue7678Smaller.java b/framework/tests/all-systems/Issue7678Smaller.java new file mode 100644 index 000000000000..bcf56e22a532 --- /dev/null +++ b/framework/tests/all-systems/Issue7678Smaller.java @@ -0,0 +1,29 @@ +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; + +// A smaller version of Issue7678.java. +@SuppressWarnings("all") +public class Issue7678Smaller { + static class MyMap { + static Collector> toMyMap(Function f) { + return null; + } + } + + static class BiStream { + static Collector> toBiStream(Collector valueCollector) { + return null; + } + + V2 mapValues(Function f) { + return null; + } + } + + void test(Stream items, Function stringStringFunction) { + items + .collect(BiStream.toBiStream(MyMap.toMyMap(stringStringFunction))) + .mapValues(byLen -> byLen); + } +}