package de.dhbwstuttgart.target.generate; import de.dhbwstuttgart.parser.NullToken; import de.dhbwstuttgart.syntaxtree.ClassOrInterface; import de.dhbwstuttgart.syntaxtree.Constructor; import de.dhbwstuttgart.syntaxtree.GenericTypeVar; import de.dhbwstuttgart.syntaxtree.Method; import de.dhbwstuttgart.syntaxtree.statement.*; import de.dhbwstuttgart.syntaxtree.type.*; import de.dhbwstuttgart.syntaxtree.type.Void; import de.dhbwstuttgart.target.tree.type.TargetGenericType; import de.dhbwstuttgart.target.tree.type.TargetType; import de.dhbwstuttgart.typeinference.result.PairTPHEqualTPH; import de.dhbwstuttgart.typeinference.result.PairTPHequalRefTypeOrWildcardType; import de.dhbwstuttgart.typeinference.result.PairTPHsmallerTPH; import de.dhbwstuttgart.typeinference.result.ResultSet; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class GenerateGenerics { private final ASTToTargetAST astToTargetAST; public class TPH { private final TypePlaceholder wrap; TPH(TypePlaceholder wrap) { this.wrap = wrap; } public TypePlaceholder resolve() { return equality.getOrDefault(wrap, wrap); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TPH tph = (TPH) o; return Objects.equals(resolve(), tph.resolve()); } @Override public int hashCode() { return Objects.hash(resolve()); } @Override public String toString() { return resolve().getName(); } } public abstract class Pair { public final TPH left; Pair(TPH left) { this.left = left; } public abstract RefTypeOrTPHOrWildcardOrGeneric resolveRight(); } public class PairLT extends Pair { public final TPH right; PairLT(TPH left, TPH right) { super(left); this.right = right; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PairLT pairLT = (PairLT) o; return Objects.equals(right, pairLT.right) && Objects.equals(left, pairLT.left); } @Override public int hashCode() { return Objects.hash(right, left); } @Override public RefTypeOrTPHOrWildcardOrGeneric resolveRight() { return right.resolve(); } @Override public String toString() { return "(" + left + " < " + right + ")"; } } public class PairEQ extends Pair { public final RefType right; PairEQ(TPH left, RefType right) { super(left); this.right = right; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PairEQ pairLT = (PairEQ) o; return Objects.equals(right, pairLT.right) && Objects.equals(left, pairLT.left); } @Override public int hashCode() { return Objects.hash(right, left); } @Override public RefTypeOrTPHOrWildcardOrGeneric resolveRight() { return right; } @Override public String toString() { return "(" + left + " = " + right + ")"; } } final Map> computedGenericsOfMethods = new HashMap<>(); final Map> computedGenericsOfClasses = new HashMap<>(); final Map> usedTPHsOfMethods = new HashMap<>(); final Map> familyOfMethods = new HashMap<>(); final Set simplifiedConstraints = new HashSet<>(); final Map concreteTypes = new HashMap<>(); final Map equality = new HashMap<>(); GenerateGenerics(ASTToTargetAST astToTargetAST, ResultSet constraints) { this.astToTargetAST = astToTargetAST; for (var constraint : constraints.results) { if (constraint instanceof PairTPHsmallerTPH p) { System.out.println(p.left + " " + p.left.getVariance()); simplifiedConstraints.add(new PairLT(new TPH(p.left), new TPH(p.right))); } else if (constraint instanceof PairTPHEqualTPH p) { equality.put(p.getLeft(), p.getRight()); } else if (constraint instanceof PairTPHequalRefTypeOrWildcardType p) { System.out.println(p.left + " = " + p.right); concreteTypes.put(new TPH(p.left), p.right); } } System.out.println("Simplified constraints: " + simplifiedConstraints); } Set findTypeVariables(RefTypeOrTPHOrWildcardOrGeneric type) { var result = new HashSet(); if (type instanceof TypePlaceholder tph) { var nTph = new TPH(tph); if (concreteTypes.containsKey(nTph)) { result.addAll(findTypeVariables(concreteTypes.get(nTph))); return result; } result.add(nTph); } else if (type instanceof RefType refType) { for (var t : refType.getParaList()) result.addAll(findTypeVariables(t)); } else if (type instanceof ExtendsWildcardType wildcardType) { result.addAll(findTypeVariables(wildcardType.getInnerType())); } else if (type instanceof SuperWildcardType wildcardType) { result.addAll(findTypeVariables(wildcardType.getInnerType())); } return result; } boolean containsRelation(Set result, PairLT pair) { // Check if both the right and the left are already part of a relation var containsLeft = false; for (var pair2 : result) { if (pair2.left.equals(pair.left)) { containsLeft = true; break; } } var containsRight = false; for (var pair2 : result) if (pair2 instanceof PairLT plt) { if (plt.right.equals(pair.right)) { containsRight = true; break; } } return containsLeft && containsRight; } void addToPairs(Set input, Pair pair) { if (pair instanceof PairLT plt) { input.removeIf(pair2 -> { if (pair2 instanceof PairEQ peq) { return peq.left.equals(plt.left) && peq.right.equals(ASTToTargetAST.OBJECT); } return false; }); } else if (input.stream().anyMatch(p -> p.left.equals(pair.left))) { return; } input.add(pair); } void addToEquality(TypePlaceholder from, TypePlaceholder to, Set referenced) { for (var entry : new HashSet<>(equality.entrySet())) { if (entry.getValue().equals(from)) { equality.remove(entry.getKey()); equality.put(entry.getKey(), to); } } System.out.println(from + " -> " + to + " " + from.getVariance()); //from.setVariance(to.getVariance()); equality.put(from, to); referenced.remove(new TPH(from)); referenced.add(new TPH(to)); } Set transitiveClosure(Set generics) { Set all = new HashSet<>(generics); Set toAdd = new HashSet<>(); int sizeBefore; do { sizeBefore = all.size(); toAdd.clear(); for (var g1 : all) { for (var g2 : all) { if (g1 instanceof PairLT pair && g2 instanceof PairLT pair2) { if (pair2.left.equals(pair.right)) toAdd.add(new PairLT(pair.left, pair2.right)); } } } all.addAll(toAdd); } while (sizeBefore < all.size()); return all; } private void methodFindConstraints( ClassOrInterface owner, Method method, Set typeVariables, Set typeVariablesOfClass, Set result ) { var userDefinedGenericsOfClass = astToTargetAST.userDefinedGenerics.get(owner); // Type variables with bounds that are also type variables of the method for (var typeVariable : new HashSet<>(typeVariables)) { if (classHasGeneric(userDefinedGenericsOfClass, typeVariablesOfClass, typeVariable)) continue; for (var pair : simplifiedConstraints) { if (pair.left.equals(typeVariable) && typeVariables.contains(pair.right)) { addToPairs(result, new PairLT(pair.left, pair.right)); } } } if (method.block != null) method.block.accept(new TracingStatementVisitor() { private RefTypeOrTPHOrWildcardOrGeneric superType = new de.dhbwstuttgart.syntaxtree.type.Void(new NullToken()); @Override public void visit(MethodCall methodCall) { //Anfang es werden Paare von TPHs gespeichert, die bei den Generated Generics ueber die Methodengrenzen hinweg //betrachtet werden muessen //Definition 7.2 (Family of generated generics). T1 <. R1 <.^∗ R2 <. T2 Set T1s = methodCall.getArgumentList() .getArguments() .stream() .map(TypableStatement::getType) .collect(Collectors.toCollection(HashSet::new)) .stream().filter(TypePlaceholder.class::isInstance) .map(TypePlaceholder.class::cast) .map(TPH::new) .collect(Collectors.toCollection(HashSet::new)); Set T2s = new HashSet<>(); findTphs(superType, T2s); System.out.println("T1s: " + T1s + " T2s: " + T2s); //Ende superType = methodCall.receiverType; methodCall.receiver.accept(this); for (int i = 0; i < methodCall.arglist.getArguments().size(); i++) { superType = methodCall.arglist.getArguments().get(i).getType(); methodCall.arglist.getArguments().get(i).accept(this); } if (methodCall.receiver instanceof ExpressionReceiver expressionReceiver) { if (expressionReceiver.expr instanceof This) { var optMethod = astToTargetAST.findMethod(owner, methodCall.name, methodCall.signatureArguments().stream().map(astToTargetAST::convert).toList()); if (optMethod.isEmpty()) return; var method2 = optMethod.get(); System.out.println("In: " + method.getName() + " Method: " + method2.getName()); var generics = family(owner, method2); // transitive and var all = transitiveClosure(generics); // reflexive var toAdd = new HashSet(); for (var generic : all) { toAdd.add(new PairLT(generic.left, generic.left)); } all.addAll(toAdd); HashSet newPairs = new HashSet<>(); // Loop from hell outer: for (var R1 : typeVariables) { if (typeVariablesOfClass.contains(R1)) continue; for (var generic : all) if (generic instanceof PairLT ptph) { for (var pair : simplifiedConstraints) { if (!(pair.left.equals(R1) && pair.right.equals(ptph.left))) continue; for (var R2 : typeVariables) { for (var pair2 : simplifiedConstraints) { if (!(pair2.right.equals(R2) && pair2.left.equals(ptph.right))) continue; if (R1.equals(R2)) continue; if (!T1s.contains(R1) || !T2s.contains(R2)) continue; var newPair = new PairLT(R1, R2); System.out.println("New pair: " + newPair); newPairs.add(newPair); if (!containsRelation(result, newPair)) addToPairs(result, newPair); continue outer; } } } } } simplifiedConstraints.addAll(newPairs); } } } @Override public void visit(LambdaExpression lambdaExpression) { superType = new Void(new NullToken()); lambdaExpression.methodBody.accept(this); } @Override public void visit(Assign assign) { superType = assign.rightSide.getType(); assign.rightSide.accept(this); } @Override public void visit(BinaryExpr binary) { superType = new Void(new NullToken()); binary.lexpr.accept(this); superType = new Void(new NullToken()); binary.rexpr.accept(this); } @Override public void visit(Block block) { for (var expr : block.statements) { superType = new de.dhbwstuttgart.syntaxtree.type.Void(new NullToken()); expr.accept(this); } } @Override public void visit(IfStmt ifStmt) { superType = new Void(new NullToken()); ifStmt.expr.accept(this); superType = new Void(new NullToken()); ifStmt.then_block.accept(this); superType = new Void(new NullToken()); if (ifStmt.else_block != null) ifStmt.else_block.accept(this); } @Override public void visit(Return aReturn) { superType = aReturn.getType(); aReturn.retexpr.accept(this); } @Override public void visit(WhileStmt whileStmt) { superType = new Void(new NullToken()); whileStmt.expr.accept(this); superType = new Void(new NullToken()); whileStmt.loopBlock.accept(this); } @Override public void visit(ArgumentList arglist) { for (int i = 0; i < arglist.getArguments().size(); i++) { superType = arglist.getArguments().get(i).getType(); arglist.getArguments().get(i).accept(this); } } }); var closure = transitiveClosure(simplifiedConstraints); // Type variables with bounds that are also type variables of the class for (var typeVariable : new HashSet<>(typeVariables)) { if (typeVariablesOfClass.contains(typeVariable)) continue; var pairs = new HashSet(); for (var pair : closure) { if (!(pair instanceof PairLT ptph)) continue; if (ptph.left.equals(typeVariable) && typeVariablesOfClass.contains(ptph.right)) { pairs.add(new PairLT(ptph.left, ptph.right)); } } // Find the closest pair with the minimum amount of steps PairLT minimalPair = null; var minSteps = Integer.MAX_VALUE; for (var pair : pairs) { var left = pair.left; var visited = new HashSet(); var steps = 0; while (!left.equals(pair.right)) { visited.add(left); var found = false; for (var pair2 : simplifiedConstraints) { if (left.equals(pair2.left) && !visited.contains(pair2.right)) { left = pair2.right; steps += 1; found = true; break; } } if (!found) break; } if (steps < minSteps) { minSteps = steps; minimalPair = pair; } } if (minimalPair != null) addToPairs(result, minimalPair); } // All unbounded type variables (bounds not in method) outer: for (var typeVariable : typeVariables) { if (classHasGeneric(userDefinedGenericsOfClass, typeVariablesOfClass, typeVariable)) continue; for (var pair : result) { if (pair.left.equals(typeVariable)) continue outer; } addToPairs(result, new PairEQ(typeVariable, ASTToTargetAST.OBJECT)); } // All unbounded bounds outer: for (var pair : simplifiedConstraints) { for (var pair2 : simplifiedConstraints) { if (pair.right.equals(pair2.left)) continue outer; } if (!classHasGeneric(userDefinedGenericsOfClass, typeVariablesOfClass, pair.right) && typeVariables.contains(pair.right)) { addToPairs(result, new PairEQ(pair.right, ASTToTargetAST.OBJECT)); } } } private boolean classHasGeneric(Set userDefinedGenericsOfClass, Set typeVariablesOfClass, TPH typeVariable) { return typeVariablesOfClass.contains(typeVariable) || userDefinedGenericsOfClass.stream().anyMatch(g -> g.getName().equals(typeVariable.resolve().getName())); } private void methodFindTypeVariables( Method method, Set typeVariables ) { if (!(method instanceof Constructor)) typeVariables.addAll(findTypeVariables(method.getReturnType())); for (var arg : method.getParameterList().getFormalparalist()) { typeVariables.addAll(findTypeVariables(arg.getType())); } if (method.block != null) method.block.accept(new TracingStatementVisitor() { @Override public void visit(LocalVarDecl localVarDecl) { typeVariables.addAll(findTypeVariables(localVarDecl.getType())); } @Override public void visit(MethodCall methodCall) { super.visit(methodCall); typeVariables.addAll(findTypeVariables(methodCall.getType())); } }); } abstract void generics(ClassOrInterface owner, Method method, Set result, Set javaTypeVariablesOfClass); Set family(ClassOrInterface owner, Method method) { Set result = new HashSet<>(); if (familyOfMethods.containsKey(method)) return familyOfMethods.get(method); familyOfMethods.put(method, result); var classGenerics = generics(owner); HashSet typeVariablesOfClass = new HashSet<>(); for (var pair : classGenerics) { typeVariablesOfClass.add(pair.left); } HashSet javaTypeVariables = new HashSet<>(); methodFindTypeVariables(method, javaTypeVariables); methodFindConstraints(owner, method, javaTypeVariables, typeVariablesOfClass, result); eliminateTransitives(result); return result; } Set generics(ClassOrInterface owner, Method method) { if (computedGenericsOfMethods.containsKey(method)) { var cached = computedGenericsOfMethods.get(method); System.out.println("Cached " + method.getName() + ": " + cached); return cached; } var result = new HashSet(); computedGenericsOfMethods.put(method, result); var classGenerics = generics(owner); HashSet typeVariablesOfClass = new HashSet<>(); for (var pair : classGenerics) { typeVariablesOfClass.add(pair.left); } result.addAll(family(owner, method)); var referenced = new HashSet(); var usedTphs = new HashSet(); // For eliminating inner type variables we need to figure out which ones are actually used for (var param : method.getParameterList().getFormalparalist()) { usedTphs.addAll(findTypeVariables(param.getType())); } usedTphs.addAll(findTypeVariables(method.getReturnType())); referenced.addAll(usedTphs); referenced.addAll(typeVariablesOfClass); generics(owner, method, result, referenced); usedTPHsOfMethods.put(method, usedTphs); normalize(result, classGenerics, usedTphs); System.out.println(this.getClass().getSimpleName() + " " + method.name + ": " + result); return result; } private void eliminateChain(Set result, List chain) { for (var pair : new HashSet<>(result)) { if (pair instanceof PairLT ptph && chain.get(chain.size() - 1).equals(ptph.left)) { if (chain.contains(ptph.right)) return; var copy = new ArrayList<>(chain); copy.add(ptph.right); if (copy.size() > 2) result.remove(new PairLT(chain.get(0), ptph.right)); eliminateChain(result, copy); } } } void eliminateTransitives(Set result) { for (var pair : new HashSet<>(result)) if (pair instanceof PairLT ptph) { var first = ptph.left; var chain = new ArrayList(); chain.add(ptph.left); chain.add(ptph.right); eliminateChain(result, chain); } } void findAllBounds(RefTypeOrTPHOrWildcardOrGeneric type, Set generics) { if (type instanceof TypePlaceholder tph) { var nTph = new TPH(tph); var concreteType = concreteTypes.get(nTph); if (concreteType != null) { findAllBounds(concreteType, generics); return; } var found = false; for (var rsp : simplifiedConstraints) { if (rsp.left.equals(nTph)) { var pair = new PairLT(new TPH(tph), rsp.right); if (!generics.contains(pair)) { addToPairs(generics, pair); findAllBounds(rsp.right.resolve(), generics); found = true; } } } if (!found) addToPairs(generics, new PairEQ(nTph, ASTToTargetAST.OBJECT)); } else if (type instanceof RefType refType) { refType.getParaList().forEach(t -> findAllBounds(t, generics)); } } abstract void generics(ClassOrInterface classOrInterface, Set result, Set referenced); Set generics(ClassOrInterface classOrInterface) { if (computedGenericsOfClasses.containsKey(classOrInterface)) return computedGenericsOfClasses.get(classOrInterface); Set javaResult = new HashSet<>(); computedGenericsOfClasses.put(classOrInterface, javaResult); for (var field : classOrInterface.getFieldDecl()) { findAllBounds(field.getType(), javaResult); } var referenced = new HashSet(); eliminateTransitives(javaResult); generics(classOrInterface, javaResult, referenced); var referencedByClass = new HashSet(); for (var field : classOrInterface.getFieldDecl()) { findTphs(field.getType(), referencedByClass); } normalize(javaResult, null, referencedByClass); System.out.println(this.getClass().getSimpleName() + " Class " + classOrInterface.getClassName().getClassName() + ": " + javaResult); return javaResult; } void normalize(Set result, Set classGenerics, Set usedTphs) { outer: for (var tph : usedTphs) { for (var p1 : new HashSet<>(result)) { if (p1 instanceof PairLT ptph && ptph.left.equals(ptph.right)) result.remove(p1); // TODO This is a bit strange if (p1.left.equals(tph)) continue outer; } if (classGenerics == null || classGenerics.stream().noneMatch((pair) -> pair.left.equals(tph))) addToPairs(result, new PairEQ(tph, ASTToTargetAST.OBJECT)); } } private record ToAdd(TypePlaceholder left, TypePlaceholder right) {} void equalizeTypeVariables(Set input, Set referenced) { var elementsToAddToEquality = new ArrayList(); for (var pair : new HashSet<>(input)) { if (pair instanceof PairLT ptph && referenced.contains(ptph.left)) { var chain = new ArrayList(); chain.add(ptph.left); chain.add(ptph.right); outer: while (true) { var added = false; for (var pair2 : input) { if (pair2 instanceof PairLT ptph2 && ptph2.left.equals(chain.get(chain.size() - 1))) { if (chain.contains(ptph2.right)) break outer; chain.add(ptph2.right); added = true; } } if (!added) break; } System.out.println(chain + " " + chain.stream().map(e -> e.resolve().getVariance()).toList()); var variance = chain.get(0).resolve().getVariance(); if (variance != 1) continue; var index = 0; for (var tph : chain) { if (variance == 1 && tph.resolve().getVariance() == -1) { variance = -1; } if (variance == -1 && tph.resolve().getVariance() == 1 && referenced.contains(tph)) { break; } index++; } if (variance == 1) continue; var start = chain.get(0); var prev = start; for (var i = 1; i < index; i++) { var cur = chain.get(i); if (!referenced.contains(cur)) continue; elementsToAddToEquality.add(new ToAdd(cur.resolve(), start.resolve())); //addToEquality(cur.resolve(), start.resolve(), referenced); TPH finalPrev = prev; input.removeIf(p -> p.equals(new PairLT(finalPrev, cur))); for (var pair2 : new HashSet<>(input)) { // TODO Maybe this would be unnecessary if we were to add the = constraints later on if (pair2 instanceof PairEQ peq && pair.left.equals(cur)) { input.remove(pair2); input.add(new PairEQ(start, peq.right)); } } prev = chain.get(i); } } } for (var pair : elementsToAddToEquality) { System.out.println(pair); addToEquality(pair.left, pair.right, referenced); } } void findTphs(RefTypeOrTPHOrWildcardOrGeneric type, Set tphs) { if (type instanceof RefType refType) { refType.getParaList().forEach(t -> findTphs(t, tphs)); } else if (type instanceof TypePlaceholder tph) { tph = equality.getOrDefault(tph, tph); var concreteType = concreteTypes.get(new TPH(tph)); if (concreteType != null) { findTphs(concreteType, tphs); return; } tphs.add(new TPH(tph)); } } void eliminateInnerTypeVariablesOfClass(ClassOrInterface classOrInterface, Set input, Set referenced) { for (var field : classOrInterface.getFieldDecl()) { findTphs(field.getType(), referenced); } doIterationForMethods(classOrInterface); for (var method : classOrInterface.getMethods()) { referenced.addAll(usedTPHsOfMethods.get(method)); } eliminateInnerTypeVariables(referenced, input); } void doIterationForMethods(ClassOrInterface classOrInterface) { familyOfMethods.clear(); var oldFamily = new HashMap>(); do { oldFamily.clear(); oldFamily.putAll(familyOfMethods); familyOfMethods.clear(); Stream.concat(classOrInterface.getMethods().stream(), classOrInterface.getConstructors().stream()).forEach(method -> { family(classOrInterface, method); }); } while(!oldFamily.equals(familyOfMethods)); Stream.concat(classOrInterface.getMethods().stream(), classOrInterface.getConstructors().stream()).forEach(method -> { generics(classOrInterface, method); }); } private void findChain(Set referenced, Set input, Set output, TPH start, TPH end, Set chain) { if (referenced.contains(end)) { var pair = new PairLT(start, end); output.add(pair); return; } var foundNext = false; for (var pair : input) { if (pair instanceof PairLT ptph && ptph.left.equals(end)) { if (chain.contains(ptph.right)) return; chain = new HashSet<>(chain); chain.add(ptph.right); findChain(referenced, input, output, start, ptph.right, chain); foundNext = true; } } if (!foundNext) { output.add(new PairEQ(start, ASTToTargetAST.OBJECT)); } } void eliminateInnerTypeVariables(Set referenced, Set input) { var output = new HashSet(); for (var tph : referenced) { for (var pair : input) { if (pair instanceof PairLT pthp && pthp.left.equals(tph)) { var chain = new HashSet(); chain.add(tph); findChain(referenced, input, output, tph, pthp.right, chain); } } } for (var pair : input) { if (pair instanceof PairEQ rtph) { if (referenced.contains(rtph.left)) output.add(rtph); } } input.clear(); input.addAll(output); } void eliminateCycles(Set input, Set referenced) { var cycles = CycleFinder.findCycles(input); for (var cycle : cycles) { var newTph = TypePlaceholder.fresh(new NullToken()); var variance = cycle.get(0).resolve().getVariance(); for (var tph : cycle) { if (tph.resolve().getVariance() != variance) { variance = 0; break; } } newTph.setVariance(variance); referenced.add(new TPH(newTph)); addToPairs(input, new PairEQ(new TPH(newTph), ASTToTargetAST.OBJECT)); cycle.add(cycle.get(0)); // Make it a complete cycle for (var i = 0; i < cycle.size() - 1; i++) { var left = cycle.get(i); var right = cycle.get(i + 1); var pair = new PairLT(left, right); input.remove(pair); addToEquality(left.resolve(), newTph, referenced); } } } Set findConnectionToReturnType(Set returnTypes, Set input, Set visited, TPH tph) { if (returnTypes.contains(tph)) { var res = new HashSet(); res.add(tph); return res; } else { for (var pair : input) if (pair instanceof PairLT ptph) { if (ptph.left.equals(tph) && !visited.contains(ptph.right)) { visited.add(ptph.right); var result = findConnectionToReturnType(returnTypes, input, visited, ptph.right); if (result.size() > 0) { result.add(ptph.right); return result; }; } } } return new HashSet<>(); } void eliminateInfimaConnectedToReturnType(Method method, Set input, Set referenced) { var foundInfima = false; do { foundInfima = false; for (var constraint : new HashSet<>(input)) { var left = constraint.left; Set infima = new HashSet<>(); for (var pair : input) { if (pair instanceof PairLT stph) { if (pair.left.equals(constraint.left)) infima.add(stph); } } if (infima.size() > 1) { System.out.println(infima); for (var pair : infima) { var returnTypes = findTypeVariables(method.getReturnType()); var chain = findConnectionToReturnType(returnTypes, input, new HashSet<>(), pair.left); System.out.println("Find: " + pair.left + " " + chain); chain.remove(pair.left); if (chain.size() > 0) { for (var tph : chain) addToEquality(pair.left.resolve(), tph.resolve(), referenced); foundInfima = true; } } } } } while (foundInfima); input.removeIf((i) -> (i instanceof PairLT lt) && lt.left.equals(lt.right)); } void eliminateInfima(Set input, Set referenced) { var foundInfima = false; do { foundInfima = false; for (var constraint : new HashSet<>(input)) { var left = constraint.left; Set infima = new HashSet<>(); for (var pair : input) { if (pair instanceof PairLT stph) { if (pair.left.equals(constraint.left)) infima.add(stph); } } if (infima.size() > 1) { foundInfima = true; var newTph = TypePlaceholder.fresh(new NullToken()); var variance = infima.stream().findFirst().get().right.resolve().getVariance(); for (var pair : infima) { if (pair.right.resolve().getVariance() != variance) { variance = 0; break; } } newTph.setVariance(variance); System.out.println(infima + " " + infima.stream().map(i -> i.right.resolve().getVariance()).toList()); System.out.println("Infima new TPH " + newTph + " variance " + variance); //referenced.add(newTph); addToPairs(input, new PairLT(left, new TPH(newTph))); input.removeAll(infima); for (var infimum : infima) { addToEquality(infimum.right.resolve(), newTph, referenced); new HashSet<>(input).forEach(pair -> { if (pair.left.equals(infimum.right)) { input.remove(pair); if (pair instanceof PairLT stph) { if (!newTph.equals(stph.right.resolve())) addToPairs(input, new PairLT(new TPH(newTph), stph.right)); } else if (pair instanceof PairEQ rtph) { addToPairs(input, new PairEQ(new TPH(newTph), rtph.right)); } } else if (pair instanceof PairLT stph && stph.right.equals(infimum.right)) { input.remove(pair); if (!newTph.equals(stph.left.resolve())) addToPairs(input, new PairLT(stph.left, new TPH(newTph))); } }); } } } } while (foundInfima); } RefTypeOrTPHOrWildcardOrGeneric getType(RefTypeOrTPHOrWildcardOrGeneric type) { if (type instanceof TypePlaceholder tph) { if (equality.containsKey(tph)) { return getType(equality.get(tph)); } return concreteTypes.getOrDefault(new TPH(tph), tph); } return type; } TargetType getTargetType(RefTypeOrTPHOrWildcardOrGeneric in) { if (in instanceof TypePlaceholder tph) { if (equality.containsKey(tph)) { return getTargetType(equality.get(tph)); } var type = concreteTypes.get(new TPH(tph)); if (type == null) return new TargetGenericType(tph.getName()); return astToTargetAST.convert(type, this); } return astToTargetAST.convert(in, this); } }