1package com.github.javaparser.symbolsolver.resolution.typeinference;
2
3import com.github.javaparser.resolution.types.ResolvedReferenceType;
4import com.github.javaparser.resolution.types.ResolvedType;
5import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
6import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl;
7import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.*;
8import com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas.TypeSameAsType;
9import com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas.TypeSubtypeOfType;
10import com.github.javaparser.utils.Pair;
11
12import java.util.*;
13import java.util.function.Predicate;
14import java.util.stream.Collectors;
15
16import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.*;
17
18/**
19 * @author Federico Tomassetti
20 */
21public class BoundSet {
22
23    private List<Bound> bounds = new LinkedList<>();
24
25    private static final BoundSet EMPTY = new BoundSet();
26
27    @Override
28    public boolean equals(Object o) {
29        if (this == o) return true;
30        if (o == null || getClass() != o.getClass()) return false;
31
32        BoundSet boundSet = (BoundSet) o;
33
34        return new HashSet<>(bounds).equals(new HashSet<>(boundSet.bounds));
35    }
36
37    @Override
38    public int hashCode() {
39        return bounds.hashCode();
40    }
41
42    @Override
43    public String toString() {
44        return "BoundSet{" +
45                "bounds=" + bounds +
46                '}';
47    }
48
49    /**
50
51     * It is sometimes convenient to refer to an empty bound set with the symbol true; this is merely out of
52     * convenience, and the two are interchangeable.
53     */
54    public boolean isTrue() {
55        return bounds.isEmpty();
56    }
57
58    public static BoundSet empty() {
59        return EMPTY;
60    }
61
62    public BoundSet withBound(Bound bound) {
63        if (this.bounds.contains(bound)) {
64            return this;
65        }
66        BoundSet boundSet = new BoundSet();
67        boundSet.bounds.addAll(this.bounds);
68        boundSet.bounds.add(bound);
69        return boundSet;
70    }
71
72    private Optional<Pair<SameAsBound, SameAsBound>> findPairSameAs(Predicate<Pair<SameAsBound, SameAsBound>> condition) {
73        for (int i=0;i<bounds.size();i++) {
74            Bound bi = bounds.get(i);
75            if (bi instanceof SameAsBound) {
76                SameAsBound si = (SameAsBound)bi;
77                for (int j = i + 1; j < bounds.size(); j++) {
78                    Bound bj = bounds.get(j);
79                    if (bj instanceof SameAsBound) {
80                        SameAsBound sj = (SameAsBound)bj;
81                        Pair<SameAsBound, SameAsBound> pair = new Pair<SameAsBound, SameAsBound>(si, sj);
82                        if (condition.test(pair)) {
83                            return Optional.of(pair);
84                        }
85                    }
86                }
87            }
88        }
89        return Optional.empty();
90    }
91
92    public boolean isEmpty() {
93        return bounds.isEmpty();
94    }
95
96    interface Processor<B1 extends Bound, B2 extends Bound, R> {
97        R process(B1 a, B2 b, R initialValue);
98    }
99
100    private <T> T forEachPairSameAs(Processor<SameAsBound, SameAsBound, T> processor, T initialValue) {
101        T currentValue = initialValue;
102        for (int i=0;i<bounds.size();i++) {
103            Bound bi = bounds.get(i);
104            if (bi instanceof SameAsBound) {
105                SameAsBound si = (SameAsBound)bi;
106                for (int j = i + 1; j < bounds.size(); j++) {
107                    Bound bj = bounds.get(j);
108                    if (bj instanceof SameAsBound) {
109                        SameAsBound sj = (SameAsBound)bj;
110                        currentValue = processor.process(si, sj, currentValue);
111                    }
112                }
113            }
114        }
115        return currentValue;
116    }
117
118    private <T> T forEachPairSameAndSubtype(Processor<SameAsBound, SubtypeOfBound, T> processor, T initialValue) {
119        T currentValue = initialValue;
120        for (int i=0;i<bounds.size();i++) {
121            Bound bi = bounds.get(i);
122            if (bi instanceof SameAsBound) {
123                SameAsBound si = (SameAsBound)bi;
124                for (int j = i + 1; j < bounds.size(); j++) {
125                    Bound bj = bounds.get(j);
126                    if (bj instanceof SubtypeOfBound) {
127                        SubtypeOfBound sj = (SubtypeOfBound)bj;
128                        currentValue = processor.process(si, sj, currentValue);
129                    }
130                }
131            }
132        }
133        return currentValue;
134    }
135
136    private <T> T forEachPairSubtypeAndSubtype(Processor<SubtypeOfBound, SubtypeOfBound, T> processor, T initialValue) {
137        T currentValue = initialValue;
138        for (int i=0;i<bounds.size();i++) {
139            Bound bi = bounds.get(i);
140            if (bi instanceof SubtypeOfBound) {
141                SubtypeOfBound si = (SubtypeOfBound)bi;
142                for (int j = i + 1; j < bounds.size(); j++) {
143                    Bound bj = bounds.get(j);
144                    if (bj instanceof SubtypeOfBound) {
145                        SubtypeOfBound sj = (SubtypeOfBound)bj;
146                        currentValue = processor.process(si, sj, currentValue);
147                    }
148                }
149            }
150        }
151        return currentValue;
152    }
153
154    private boolean areSameTypeInference(ResolvedType a, ResolvedType b) {
155        return isInferenceVariable(a) && isInferenceVariable(b) && a.equals(b);
156    }
157
158    private List<Pair<ResolvedReferenceType, ResolvedReferenceType>> findPairsOfCommonAncestors(ResolvedReferenceType r1, ResolvedReferenceType r2) {
159        List<ResolvedReferenceType> set1 = new LinkedList<>();
160        set1.add(r1);
161        set1.addAll(r1.getAllAncestors());
162        List<ResolvedReferenceType> set2 = new LinkedList<>();
163        set2.add(r2);
164        set2.addAll(r2.getAllAncestors());
165        List<Pair<ResolvedReferenceType, ResolvedReferenceType>> pairs = new LinkedList<>();
166        for (ResolvedReferenceType rtFrom1 : set1) {
167            for (ResolvedReferenceType rtFrom2 : set2) {
168                if (rtFrom1.getTypeDeclaration().equals(rtFrom2.getTypeDeclaration())) {
169                    pairs.add(new Pair<>(rtFrom1, rtFrom2));
170                }
171            }
172        }
173        return pairs;
174    }
175
176    /**
177     * Maintains a set of inference variable bounds, ensuring that these are consistent as new bounds are added.
178     * Because the bounds on one variable can sometimes impact the possible choices for another variable, this process
179     * propagates bounds between such interdependent variables.
180     */
181    public BoundSet incorporate(BoundSet otherBounds, TypeSolver typeSolver) {
182        BoundSet newBoundSet = this;
183        for (Bound b : otherBounds.bounds) {
184            newBoundSet = newBoundSet.withBound(b);
185        }
186        return newBoundSet.deriveImpliedBounds(typeSolver);
187    }
188
189    public BoundSet deriveImpliedBounds(TypeSolver typeSolver) {
190        // As bound sets are constructed and grown during inference, it is possible that new bounds can be inferred
191        // based on the assertions of the original bounds. The process of incorporation identifies these new bounds
192        // and adds them to the bound set.
193        //
194        // Incorporation can happen in two scenarios. One scenario is that the bound set contains complementary pairs
195        // of bounds; this implies new constraint formulas, as specified in §18.3.1. The other scenario is that the
196        // bound set contains a bound involving capture conversion; this implies new bounds and may imply new
197        // constraint formulas, as specified in §18.3.2. In both scenarios, any new constraint formulas are reduced,
198        // and any new bounds are added to the bound set. This may trigger further incorporation; ultimately, the set
199        // will reach a fixed point and no further bounds can be inferred.
200        //
201        // If incorporation of a bound set has reached a fixed point, and the set does not contain the bound false,
202        // then the bound set has the following properties:
203        //
204        // - For each combination of a proper lower bound L and a proper upper bound U of an inference variable, L <: U.
205        //
206        // - If every inference variable mentioned by a bound has an instantiation, the bound is satisfied by the
207        //   corresponding substitution.
208        //
209        // - Given a dependency α = β, every bound of α matches a bound of β, and vice versa.
210        //
211        // - Given a dependency α <: β, every lower bound of α is a lower bound of β, and every upper bound of β is an
212        //   upper bound of α.
213
214        ConstraintFormulaSet newConstraintsSet = ConstraintFormulaSet.empty();
215
216        // SECTION Complementary Pairs of Bounds
217        // (In this section, S and T are inference variables or types, and U is a proper type. For conciseness, a bound
218        // of the form α = T may also match a bound of the form T = α.)
219        //
220        // When a bound set contains a pair of bounds that match one of the following rules, a new constraint formula
221        // is implied:
222        //
223        // - α = S and α = T imply ‹S = T›
224
225        newConstraintsSet = forEachPairSameAs((a, b, currentConstraintSet) -> {
226            if (areSameTypeInference(a.getS(), b.getS())) {
227                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getT(), b.getT()));
228            }
229            if (areSameTypeInference(a.getS(), b.getT())) {
230                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getS(), b.getT()));
231            }
232            if (areSameTypeInference(a.getT(), b.getS())) {
233                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getT(), b.getS()));
234            }
235            if (areSameTypeInference(a.getT(), b.getT())) {
236                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getS(), b.getS()));
237            }
238            return currentConstraintSet;
239        }, newConstraintsSet);
240
241        // - α = S and α <: T imply ‹S <: T›
242
243        newConstraintsSet = forEachPairSameAndSubtype((a, b, currentConstraintSet) -> {
244            if (areSameTypeInference(a.getS(), b.getS())) {
245                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, a.getT(), b.getT()));
246            }
247            if (areSameTypeInference(a.getT(), b.getS())) {
248                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, a.getS(), b.getT()));
249            }
250            return currentConstraintSet;
251        }, newConstraintsSet);
252
253        // - α = S and T <: α imply ‹T <: S›
254
255        newConstraintsSet = forEachPairSameAndSubtype((a, b, currentConstraintSet) -> {
256            if (areSameTypeInference(a.getS(), b.getT())) {
257                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, b.getS(), a.getT()));
258            }
259            if (areSameTypeInference(a.getT(), b.getT())) {
260                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, b.getS(), a.getS()));
261            }
262            return currentConstraintSet;
263        }, newConstraintsSet);
264
265        // - S <: α and α <: T imply ‹S <: T›
266
267        newConstraintsSet = forEachPairSubtypeAndSubtype((a, b, currentConstraintSet) -> {
268            if (areSameTypeInference(a.getT(), b.getS())) {
269                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, b.getS(), a.getT()));
270            }
271            return currentConstraintSet;
272        }, newConstraintsSet);
273
274        // - α = U and S = T imply ‹S[α:=U] = T[α:=U]›
275
276        newConstraintsSet = forEachPairSameAs((a, b, currentConstraintSet) -> {
277            if (isInferenceVariable(a.getS()) && isProperType(a.getT())) {
278                InferenceVariable alpha = (InferenceVariable)a.getS();
279                ResolvedType U = a.getT();
280                ResolvedType S = b.getS();
281                ResolvedType T = b.getT();
282                Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U);
283                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T)));
284            }
285            if (isInferenceVariable(a.getT()) && isProperType(a.getS())) {
286                InferenceVariable alpha = (InferenceVariable)a.getT();
287                ResolvedType U = a.getS();
288                ResolvedType S = b.getS();
289                ResolvedType T = b.getT();
290                Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U);
291                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T)));
292            }
293            if (isInferenceVariable(b.getS()) && isProperType(b.getT())) {
294                InferenceVariable alpha = (InferenceVariable)b.getS();
295                ResolvedType U = b.getT();
296                ResolvedType S = a.getS();
297                ResolvedType T = a.getT();
298                Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U);
299                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T)));
300            }
301            if (isInferenceVariable(b.getT()) && isProperType(b.getS())) {
302                InferenceVariable alpha = (InferenceVariable)b.getT();
303                ResolvedType U = b.getS();
304                ResolvedType S = a.getS();
305                ResolvedType T = a.getT();
306                Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U);
307                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T)));
308            }
309            return currentConstraintSet;
310        }, newConstraintsSet);
311
312        // - α = U and S <: T imply ‹S[α:=U] <: T[α:=U]›
313
314        newConstraintsSet = forEachPairSameAndSubtype((a, b, currentConstraintSet) -> {
315            if (isInferenceVariable(a.getS()) && isProperType(a.getT())) {
316                InferenceVariable alpha = (InferenceVariable)a.getS();
317                ResolvedType U = a.getT();
318                ResolvedType S = b.getS();
319                ResolvedType T = b.getT();
320                Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U);
321                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, sub.apply(S), sub.apply(T)));
322            }
323            if (isInferenceVariable(a.getT()) && isProperType(a.getS())) {
324                InferenceVariable alpha = (InferenceVariable)a.getT();
325                ResolvedType U = a.getS();
326                ResolvedType S = b.getS();
327                ResolvedType T = b.getT();
328                Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U);
329                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, sub.apply(S), sub.apply(T)));
330            }
331            return currentConstraintSet;
332        }, newConstraintsSet);
333
334        // When a bound set contains a pair of bounds α <: S and α <: T, and there exists a supertype of S of the
335        // form G<S1, ..., Sn> and a supertype of T of the form G<T1, ..., Tn> (for some generic class or interface, G),
336        // then for all i (1 ≤ i ≤ n), if Si and Ti are types (not wildcards), the constraint formula ‹Si = Ti› is
337        // implied.
338
339        newConstraintsSet = forEachPairSubtypeAndSubtype((a, b, currentConstraintSet) -> {
340            if (isInferenceVariable(a.getS()) && isInferenceVariable(b.getS())) {
341                if (a.getT().isReferenceType() && b.getT().isReferenceType()) {
342                    ResolvedReferenceType S = a.getT().asReferenceType();
343                    ResolvedReferenceType T = b.getT().asReferenceType();
344                    List<Pair<ResolvedReferenceType, ResolvedReferenceType>> pairs = findPairsOfCommonAncestors(S, T);
345                    for (Pair<ResolvedReferenceType, ResolvedReferenceType> pair : pairs) {
346                        for (int i=0;i<Math.min(pair.a.typeParametersValues().size(), pair.b.typeParametersValues().size()); i++) {
347                            ResolvedType si = pair.a.typeParametersValues().get(i);
348                            ResolvedType ti = pair.b.typeParametersValues().get(i);
349                            if (!si.isWildcard() && !ti.isWildcard()) {
350                                currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(si, ti));
351                            }
352                        }
353                    }
354                }
355            }
356            return currentConstraintSet;
357        }, newConstraintsSet);
358
359        // SECTION Bounds Involving Capture Conversion
360        //
361        // When a bound set contains a bound of the form G<α1, ..., αn> = capture(G<A1, ..., An>), new bounds are
362        // implied and new constraint formulas may be implied, as follows.
363
364        for (Bound b : this.bounds.stream().filter(b -> b instanceof CapturesBound).collect(Collectors.toList())) {
365            CapturesBound capturesBound = (CapturesBound)b;
366
367            throw new UnsupportedOperationException();
368
369            // Let P1, ..., Pn represent the type parameters of G and let B1, ..., Bn represent the bounds of these type
370            // parameters. Let θ represent the substitution [P1:=α1, ..., Pn:=αn]. Let R be a type that is not an inference
371            // variable (but is not necessarily a proper type).
372            //
373            // A set of bounds on α1, ..., αn is implied, constructed from the declared bounds of P1, ..., Pn as specified
374            // in §18.1.3.
375            //
376            // In addition, for all i (1 ≤ i ≤ n):
377            //
378            // - If Ai is not a wildcard, then the bound αi = Ai is implied.
379            //
380            // - If Ai is a wildcard of the form ?:
381            //
382            //   - αi = R implies the bound false
383            //
384            //   - αi <: R implies the constraint formula ‹Bi θ <: R›
385            //
386            //   - R <: αi implies the bound false
387            //
388            // - If Ai is a wildcard of the form ? extends T:
389            //
390            //   - αi = R implies the bound false
391            //
392            //   - If Bi is Object, then αi <: R implies the constraint formula ‹T <: R›
393            //
394            //   - If T is Object, then αi <: R implies the constraint formula ‹Bi θ <: R›
395            //
396            //   - R <: αi implies the bound false
397            //
398            // - If Ai is a wildcard of the form ? super T:
399            //
400            //   - αi = R implies the bound false
401            //
402            //   - αi <: R implies the constraint formula ‹Bi θ <: R›
403            //
404            //   - R <: αi implies the constraint formula ‹R <: T›
405        }
406
407        if (newConstraintsSet.isEmpty()) {
408            return this;
409        } else {
410            BoundSet newBounds = newConstraintsSet.reduce(typeSolver);
411            if (newBounds.isEmpty()) {
412                return this;
413            }
414            return this.incorporate(newBounds, typeSolver);
415        }
416    }
417
418    public boolean containsFalse() {
419        return bounds.stream().anyMatch(it -> it instanceof FalseBound);
420    }
421
422    private class VariableDependency {
423        private InferenceVariable depending;
424        private InferenceVariable dependedOn;
425
426        public VariableDependency(InferenceVariable depending, InferenceVariable dependedOn) {
427            this.depending = depending;
428            this.dependedOn = dependedOn;
429        }
430
431        public InferenceVariable getDepending() {
432            return depending;
433        }
434
435        public InferenceVariable getDependedOn() {
436            return dependedOn;
437        }
438
439        public boolean isReflexive() {
440            return dependedOn.equals(depending);
441        }
442    }
443
444    private Set<InferenceVariable> allInferenceVariables() {
445        Set<InferenceVariable> variables = new HashSet<>();
446        for (Bound b : bounds) {
447            variables.addAll(b.usedInferenceVariables());
448        }
449        return variables;
450    }
451
452    private boolean hasInstantiationFor(InferenceVariable v) {
453        for (Bound b : bounds) {
454            if (b.isAnInstantiationFor(v)) {
455                return true;
456            }
457        }
458        return false;
459    }
460
461    private Instantiation getInstantiationFor(InferenceVariable v) {
462        for (Bound b : bounds) {
463            if (b.isAnInstantiationFor(v)) {
464                return b.isAnInstantiation().get();
465            }
466        }
467        throw new IllegalArgumentException();
468    }
469
470    private boolean thereIsSomeJSuchThatβequalAlphaJ(Set<InferenceVariable> alphas, InferenceVariable beta) {
471        for (InferenceVariable alphaJ : alphas) {
472            for (Bound b : bounds) {
473                if (b instanceof SameAsBound) {
474                    SameAsBound sameAsBound = (SameAsBound)b;
475                    if (sameAsBound.getS().equals(alphaJ) && sameAsBound.getT().equals(beta)) {
476                        return true;
477                    }
478                    if (sameAsBound.getT().equals(alphaJ) && sameAsBound.getS().equals(beta)) {
479                        return true;
480                    }
481                }
482            }
483        }
484        return false;
485    }
486
487    private <T> List<Set<T>> buildAllSubsetsOfSize(Set<T> allElements, int desiredSize) {
488        if (desiredSize == allElements.size()) {
489            return Arrays.asList(allElements);
490        } else {
491            List<Set<T>> res = new LinkedList<>();
492            for (T element : allElements) {
493                Set<T> subset = allButOne(allElements, element);
494                res.addAll(buildAllSubsetsOfSize(subset, desiredSize));
495            }
496            return res;
497        }
498    }
499
500    private <T> Set<T> allButOne(Set<T> elements, T element) {
501        Set<T> set = new HashSet<T>(elements);
502        set.remove(element);
503        return set;
504    }
505
506    /**
507     * there exists no non-empty proper subset of { α1, ..., αn } with this property.
508     */
509    private Optional<Set<InferenceVariable>> smallestSetWithProperty(Set<InferenceVariable> uninstantiatedVariables,
510                                                                     List<VariableDependency> dependencies) {
511        for (int i=1;i<=uninstantiatedVariables.size();i++) {
512            for (Set<InferenceVariable> aSubSet : buildAllSubsetsOfSize(uninstantiatedVariables, i)){
513                if (hasProperty(aSubSet, dependencies)) {
514                    return Optional.of(aSubSet);
515                }
516            }
517        }
518        return Optional.empty();
519    }
520
521    /**
522     * if αi depends on the resolution of a variable β, then either β has an instantiation
523     * or there is some j such that β = αj
524     * @return
525     */
526    private boolean hasProperty(Set<InferenceVariable> alphas, List<VariableDependency> dependencies) {
527        for (InferenceVariable alphaI: alphas) {
528            for (InferenceVariable beta: dependencies.stream()
529                    .filter(d -> d.depending.equals(alphaI))
530                    .filter(d -> !d.isReflexive())
531                    .map(d -> d.dependedOn)
532                    .collect(Collectors.toList())) {
533                if (!hasInstantiationFor(beta) && !thereIsSomeJSuchThatβequalAlphaJ(alphas, beta)) {
534                    return false;
535                }
536            }
537        }
538        return true;
539    }
540
541    /**
542     * Examines the bounds on an inference variable and determines an instantiation that is compatible with those
543     * bounds. It also decides the order in which interdependent inference variables are to be resolved.
544     */
545    public Optional<InstantiationSet> performResolution(List<InferenceVariable> variablesToResolve, TypeSolver typeSolver) {
546
547        if (this.containsFalse()) {
548            return Optional.empty();
549        }
550
551        List<VariableDependency> dependencies = new LinkedList<>();
552
553        // Given a bound set that does not contain the bound false, a subset of the inference variables mentioned by
554        // the bound set may be resolved. This means that a satisfactory instantiation may be added to the set for each
555        // inference variable, until all the requested variables have instantiations.
556        //
557        // Dependencies in the bound set may require that the variables be resolved in a particular order, or that
558        // additional variables be resolved. Dependencies are specified as follows:
559        //
560        // - Given a bound of one of the following forms, where T is either an inference variable β or a type that
561        // mentions β:
562        //
563        //   - α = T
564        //
565        //   - α <: T
566        //
567        //   - T = α
568        //
569        //   - T <: α
570        //
571        //   If α appears on the left-hand side of another bound of the form G<..., α, ...> = capture(G<...>), then β
572        //   depends on the resolution of α. Otherwise, α depends on the resolution of β.
573
574        for (Bound b : bounds) {
575            if (b instanceof CapturesBound) {
576                throw new UnsupportedOperationException();
577            }
578        }
579
580        // - An inference variable α appearing on the left-hand side of a bound of the form
581        //   G<..., α, ...> = capture(G<...>) depends on the resolution of every other inference variable mentioned in
582        //   this bound (on both sides of the = sign).
583
584        for (Bound b : bounds) {
585            if (b instanceof CapturesBound) {
586                throw new UnsupportedOperationException();
587            }
588        }
589
590        // - An inference variable α depends on the resolution of an inference variable β if there exists an inference
591        //   variable γ such that α depends on the resolution of γ and γ depends on the resolution of β.
592
593        for (int i=0;i<dependencies.size();i++) {
594            VariableDependency di = dependencies.get(i);
595            for (int j=i+1;j<dependencies.size();j++) {
596                VariableDependency dj = dependencies.get(j);
597                if (di.dependedOn.equals(dj.depending)) {
598                    dependencies.add(new VariableDependency(di.getDepending(), dj.getDependedOn()));
599                }
600            }
601        }
602
603        // - An inference variable α depends on the resolution of itself.
604
605        for (InferenceVariable v : allInferenceVariables()) {
606            dependencies.add(new VariableDependency(v, v));
607        }
608
609        // Given a set of inference variables to resolve, let V be the union of this set and all variables upon which
610        // the resolution of at least one variable in this set depends.
611
612        Set<InferenceVariable> V = new HashSet<>();
613        V.addAll(variablesToResolve);
614        for (VariableDependency dependency : dependencies) {
615            if (variablesToResolve.contains(dependency.depending)) {
616                V.add(dependency.dependedOn);
617            }
618        }
619
620        // If every variable in V has an instantiation, then resolution succeeds and this procedure terminates.
621
622        boolean ok = true;
623        for (InferenceVariable v : V) {
624            if (!hasInstantiationFor(v)) {
625                ok = false;
626            }
627        }
628        if (ok) {
629            InstantiationSet instantiationSet = InstantiationSet.empty();
630            for (InferenceVariable v : V) {
631                instantiationSet = instantiationSet.withInstantiation(getInstantiationFor(v));
632            }
633            return Optional.of(instantiationSet);
634        }
635
636        // Otherwise, let { α1, ..., αn } be a non-empty subset of uninstantiated variables in V such that i)
637        // for all i (1 ≤ i ≤ n), if αi depends on the resolution of a variable β, then either β has an instantiation
638        // or there is some j such that β = αj; and ii) there exists no non-empty proper subset of { α1, ..., αn }
639        // with this property.
640
641        Set<InferenceVariable> uninstantiatedPortionOfV = new HashSet<>();
642        for (InferenceVariable v : V) {
643            if (!hasInstantiationFor(v)) {
644                uninstantiatedPortionOfV.add(v);
645            }
646        }
647        for (Set<InferenceVariable> alphas: allSetsWithProperty(uninstantiatedPortionOfV, dependencies)) {
648
649            // Resolution proceeds by generating an instantiation for each of α1, ..., αn based on the
650            // bounds in the bound set:
651
652            boolean hasSomeCaptureForAlphas = alphas.stream().anyMatch(
653                    alphaI -> appearInLeftPartOfCapture(alphaI)
654            );
655
656            // - If the bound set does not contain a bound of the form G<..., αi, ...> = capture(G<...>)
657            //   for all i (1 ≤ i ≤ n), then a candidate instantiation Ti is defined for each αi:
658
659            if (!hasSomeCaptureForAlphas) {
660                BoundSet newBounds = BoundSet.empty();
661                for (InferenceVariable alphaI : alphas) {
662                    Set<ResolvedType> properLowerBounds = bounds.stream()
663                            .filter(b -> b.isProperLowerBoundFor(alphaI).isPresent())
664                            .map(b -> b.isProperLowerBoundFor(alphaI).get().getProperType())
665                            .collect(Collectors.toSet());
666
667                    ResolvedType Ti = null;
668
669                    //   - If αi has one or more proper lower bounds, L1, ..., Lk, then Ti = lub(L1, ..., Lk) (§4.10.4).
670
671                    if (properLowerBounds.size() > 0) {
672                        Ti = leastUpperBound(properLowerBounds);
673                    }
674
675                    //   - Otherwise, if the bound set contains throws αi, and the proper upper bounds of αi are, at most,
676                    //     Exception, Throwable, and Object, then Ti = RuntimeException.
677
678                    boolean throwsBound = bounds.stream().anyMatch(b -> b.isThrowsBoundOn(alphaI));
679                    if (Ti == null && throwsBound && properUpperBoundsAreAtMostExceptionThrowableAndObject(alphaI)) {
680                        Ti = new ReferenceTypeImpl(typeSolver.solveType(RuntimeException.class.getCanonicalName()), typeSolver);
681                    }
682
683                    //   - Otherwise, where αi has proper upper bounds U1, ..., Uk, Ti = glb(U1, ..., Uk) (§5.1.10).
684
685                    if (Ti == null) {
686                        Set<ResolvedType> properUpperBounds = bounds.stream()
687                                .filter(b -> b.isProperUpperBoundFor(alphaI).isPresent())
688                                .map(b -> b.isProperUpperBoundFor(alphaI).get().getProperType())
689                                .collect(Collectors.toSet());
690                        if (properUpperBounds.size() == 0) {
691                            throw new IllegalStateException();
692                        }
693                        Ti = glb(properUpperBounds);
694                    }
695
696                    newBounds = newBounds.withBound(new SameAsBound(alphaI, Ti));
697                }
698
699                //   The bounds α1 = T1, ..., αn = Tn are incorporated with the current bound set.
700
701                BoundSet incorporatedBoundSet = this.incorporate(newBounds, typeSolver);
702
703                //   If the result does not contain the bound false, then the result becomes the new bound set, and resolution
704                //   proceeds by selecting a new set of variables to instantiate (if necessary), as described above.
705
706                if (!incorporatedBoundSet.containsFalse()) {
707                    return incorporatedBoundSet.performResolution(variablesToResolve, typeSolver);
708                }
709
710                //   Otherwise, the result contains the bound false, so a second attempt is made to instantiate { α1, ..., αn }
711                //   by performing the step below.
712
713                throw new UnsupportedOperationException();
714            }
715
716            // - If the bound set contains a bound of the form G<..., αi, ...> = capture(G<...>) for some i (1 ≤ i ≤ n), or;
717
718            else {
719
720                //   If the bound set produced in the step above contains the bound false;
721                //
722                //   then let Y1, ..., Yn be fresh type variables whose bounds are as follows:
723                //
724                //   - For all i (1 ≤ i ≤ n), if αi has one or more proper lower bounds L1, ..., Lk, then let the lower bound
725                //     of Yi be lub(L1, ..., Lk); if not, then Yi has no lower bound.
726                //
727                //   - For all i (1 ≤ i ≤ n), where αi has upper bounds U1, ..., Uk, let the upper bound of Yi be
728                //     glb(U1 θ, ..., Uk θ), where θ is the substitution [α1:=Y1, ..., αn:=Yn].
729                //
730                //   If the type variables Y1, ..., Yn do not have well-formed bounds (that is, a lower bound is not a subtype
731                //   of an upper bound, or an intersection type is inconsistent), then resolution fails.
732                //
733                //   Otherwise, for all i (1 ≤ i ≤ n), all bounds of the form G<..., αi, ...> = capture(G<...>) are removed
734                //   from the current bound set, and the bounds α1 = Y1, ..., αn = Yn are incorporated.
735                //
736                //   If the result does not contain the bound false, then the result becomes the new bound set, and resolution
737                //   proceeds by selecting a new set of variables to instantiate (if necessary), as described above.
738                //
739                //   Otherwise, the result contains the bound false, and resolution fails.
740
741                throw new UnsupportedOperationException();
742            }
743        }
744        return Optional.empty();
745    }
746
747    private Set<Set<InferenceVariable>> allPossibleSetsWithProperty(Set<InferenceVariable> allElements, List<VariableDependency> dependencies) {
748        Set<Set<InferenceVariable>> result = new HashSet<>();
749        for (int i=1;i<=allElements.size();i++) {
750            for (Set<InferenceVariable> aSubSet : buildAllSubsetsOfSize(allElements, i)){
751                if (hasProperty(aSubSet, dependencies)) {
752                    result.add(aSubSet);
753                }
754            }
755        }
756        return result;
757    }
758
759    private boolean thereAreProperSubsets(Set<InferenceVariable> aSet, Set<Set<InferenceVariable>> allPossibleSets) {
760        for (Set<InferenceVariable> anotherSet : allPossibleSets) {
761            if (!anotherSet.equals(aSet)) {
762                if (isTheFirstAProperSubsetOfTheSecond(anotherSet, aSet)) {
763                    return true;
764                }
765            }
766        }
767        return false;
768    }
769
770    private boolean isTheFirstAProperSubsetOfTheSecond(Set<InferenceVariable> subset, Set<InferenceVariable> originalSet) {
771        return originalSet.containsAll(subset) && originalSet.size() > subset.size();
772    }
773
774    private Set<Set<InferenceVariable>> allSetsWithProperty(Set<InferenceVariable> allElements, List<VariableDependency> dependencies) {
775        Set<Set<InferenceVariable>> allPossibleSets = allPossibleSetsWithProperty(allElements, dependencies);
776        Set<Set<InferenceVariable>> selected = new HashSet<>();
777        for (Set<InferenceVariable> aSet : allPossibleSets) {
778            if (!thereAreProperSubsets(aSet, allPossibleSets)) {
779                selected.add(aSet);
780            }
781        }
782        return selected;
783    }
784
785    private boolean properUpperBoundsAreAtMostExceptionThrowableAndObject(InferenceVariable inferenceVariable) {
786        throw new UnsupportedOperationException();
787    }
788
789    private boolean appearInLeftPartOfCapture(InferenceVariable inferenceVariable) {
790        for (Bound b : bounds) {
791            if (b instanceof CapturesBound) {
792                CapturesBound capturesBound = (CapturesBound)b;
793                if (capturesBound.getInferenceVariables().contains(inferenceVariable)) {
794                    return true;
795                }
796            }
797        }
798        return false;
799    }
800
801    public List<Bound> getProperUpperBoundsFor(InferenceVariable inferenceVariable) {
802        return bounds.stream().filter(b -> b.isProperUpperBoundFor(inferenceVariable).isPresent()).collect(Collectors.toList());
803    }
804}
805