diff --git a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java index 7c4a6b453..2a554e1c6 100644 --- a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java +++ b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java @@ -852,6 +852,13 @@ public class JavaTXCompiler { generateBytecode(path, typeinferenceResult); } + private Map> generatedGenerics = new HashMap<>(); + + // TODO This is a temporary solution, we should integrate with the old API for getting Generics + public Map> getGeneratedGenerics() { + return generatedGenerics; + } + /** * @param outputPath - can be null, then class file output is in the same directory as the parsed source files * @param typeinferenceResult @@ -878,6 +885,7 @@ public class JavaTXCompiler { generatedClasses.put(new JavaClassName(name), source); }); } + generatedGenerics.put(f, converter.computedGenerics()); writeClassFile(generatedClasses, path); } } diff --git a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java index 8d20b8f82..551fb020f 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java @@ -24,7 +24,116 @@ public class ASTToTargetAST { protected List all; protected Sigma sigma; - protected ClassOrInterface currentClass; // TODO This is only needed because of SuperCall, maybe there's a better way? + protected ClassOrInterface currentClass; // TODO This is only needed because of SuperCall, maybe there's + + private record Bound(boolean isOnMethod, RefTypeOrTPHOrWildcardOrGeneric bound) {} + + private static boolean boundChainEquals(List left, List right) { + if (left.size() != right.size()) return false; + for (var i = 0; i < left.size(); i++) { + var l = left.get(i); + var r = right.get(i); + if (l.isOnMethod != r.isOnMethod) return false; + if (i == left.size() - 1) { + if (!(l.bound instanceof TypePlaceholder)) { + if (!(l.bound.equals(r.bound))) return false; + } + } + } + return true; + } + + public static class GenericsResult { + + final Map>> computedGenericsOfClasses; + final Map>> computedGenericsOfMethods; + + final Sigma sigma; + + public GenericsResult( + Map>> computedGenericsOfClasses, + Map>> computedGenericsOfMethods) { + this.computedGenericsOfMethods = computedGenericsOfMethods; + this.computedGenericsOfClasses = computedGenericsOfClasses; + this.sigma = null; + } + + private GenericsResult(Sigma sigma) { + this.computedGenericsOfMethods = sigma.computedGenericsOfMethods; + this.computedGenericsOfClasses = sigma.computedGenericsOfClasses; + this.sigma = sigma; + } + + public Set> get(ClassOrInterface clazz) { + return computedGenericsOfClasses.get(clazz); + } + + public Set> get(Method method) { + return computedGenericsOfMethods.get(method); + } + + private TypePlaceholder equality(TypePlaceholder tph) { + if (this.sigma == null) return tph; + else return this.sigma.equality.getOrDefault(tph, tph); + } + + private List boundChain(ClassOrInterface clazz, Method method, TypePlaceholder tph) { + var result = new ArrayList(); + + RefTypeOrTPHOrWildcardOrGeneric bound = equality(tph); + var constraintsForMethod = get(method); + var constraintsForClass = get(clazz); + Optional> newBound; + do { + RefTypeOrTPHOrWildcardOrGeneric finalBound1 = bound; + newBound = constraintsForMethod.stream().filter(pair -> pair.getLeft().equals(finalBound1)).findFirst(); + if (newBound.isPresent()) { + bound = newBound.get().getRight(); + result.add(new Bound(true, bound)); + } else { + RefTypeOrTPHOrWildcardOrGeneric finalBound = bound; + newBound = constraintsForClass.stream().filter(pair -> pair.getLeft().equals(finalBound)).findFirst(); + if (newBound.isPresent()) { + bound = newBound.get().getRight(); + result.add(new Bound(false, bound)); + } + } + } while (newBound.isPresent()); + + return result; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof GenericsResult other)) return false; + if (this.computedGenericsOfClasses.size() != other.computedGenericsOfClasses.size()) + return false; + + for (var clazz : this.computedGenericsOfClasses.keySet()) { + if (!other.computedGenericsOfClasses.containsKey(clazz)) return false; + + for (var method : clazz.getMethods()) { + for (var param : method.getParameterList()) { + if (param.getType() instanceof TypePlaceholder tph) { + var leftBound = boundChain(clazz, method, tph); + var rightBound = other.boundChain(clazz, method, tph); + if (!boundChainEquals(leftBound, rightBound)) return false; + } + } + if (method.getReturnType() instanceof TypePlaceholder tph) { + var leftBound = boundChain(clazz, method, tph); + var rightBound = other.boundChain(clazz, method, tph); + if (!boundChainEquals(leftBound, rightBound)) return false; + } + } + } + return true; + } + } + + public List computedGenerics() { + return all.stream().map(GenericsResult::new).toList(); + } private class Sigma { Map>> computedGenericsOfMethods = new HashMap<>(); @@ -66,7 +175,8 @@ public class ASTToTargetAST { } boolean hasBound(TypePlaceholder name, Set> generics) { - return generics.stream().anyMatch(generic -> generic.getLeft().equals(name)); + TypePlaceholder finalName = equality.getOrDefault(name, name); + return generics.stream().anyMatch(generic -> generic.getLeft().equals(finalName)); } boolean containsRelation(Set> result, PairTPHsmallerTPH pair) { @@ -141,6 +251,7 @@ public class ASTToTargetAST { public void visit(Assign assign) {} }); + // Type variables with bounds that are also type variables of the method for (var typeVariable : new HashSet<>(typeVariables)) { for (var pair : simplifiedConstraints) { @@ -151,6 +262,7 @@ public class ASTToTargetAST { } } + var visitedMethods = new HashSet(); method.block.accept(new TracingStatementVisitor() { @Override @@ -188,8 +300,10 @@ public class ASTToTargetAST { toAdd.add(new PairTPHsmallerTPH((TypePlaceholder) generic.getLeft(), (TypePlaceholder) generic.getLeft())); } all.addAll(toAdd); - HashSet newPairs = new HashSet<>(); + + System.out.println("Result: " + result); + // Loop from hell outer: for (var tph : typeVariables) { @@ -234,7 +348,7 @@ public class ASTToTargetAST { } } - // All unbounded type variables + // All unbounded type variables (bounds not in method) outer: for (var typeVariable : typeVariables) { for (var pair : simplifiedConstraints) { @@ -277,6 +391,7 @@ public class ASTToTargetAST { return; } + var found = false; for (var rsp : simplifiedConstraints) { var left = equality.getOrDefault(rsp.left, rsp.left); var right = equality.getOrDefault(rsp.right, rsp.right); @@ -285,11 +400,12 @@ public class ASTToTargetAST { if (!generics.contains(pair)) { generics.add(pair); findAllBounds(right, generics); + found = true; } - return; } } - generics.add(new PairTPHequalRefTypeOrWildcardType(tph, OBJECT)); + if (!found) + generics.add(new PairTPHequalRefTypeOrWildcardType(tph, OBJECT)); } else if (type instanceof RefType refType) { refType.getParaList().forEach(t -> findAllBounds(t, generics)); } @@ -308,6 +424,7 @@ public class ASTToTargetAST { eliminateInnerTypeVariables(classOrInterface, result); equalizeTypeVariables(result); + System.out.println("Class " + classOrInterface.getClassName().getClassName() + ": " + result); return result; } @@ -410,14 +527,16 @@ public class ASTToTargetAST { if (pair.getLeft().equals(infimum.right)) { input.remove(pair); if (pair instanceof PairTPHsmallerTPH stph) { - addToPairs(input, new PairTPHsmallerTPH(newTph, stph.right)); + if (!newTph.equals(stph.right)) + addToPairs(input, new PairTPHsmallerTPH(newTph, stph.right)); } else if (pair instanceof PairTPHequalRefTypeOrWildcardType rtph) { addToPairs(input, new PairTPHequalRefTypeOrWildcardType(newTph, rtph.getRight())); } } else if (pair.getRight().equals(infimum.right)) { input.remove(pair); if (pair instanceof PairTPHsmallerTPH stph) { - addToPairs(input, new PairTPHsmallerTPH(stph.left, newTph)); + if (!newTph.equals(stph.left)) + addToPairs(input, new PairTPHsmallerTPH(stph.left, newTph)); } } }); diff --git a/src/test/java/targetast/TestGenerics.java b/src/test/java/targetast/TestGenerics.java new file mode 100644 index 000000000..ce4aa53ad --- /dev/null +++ b/src/test/java/targetast/TestGenerics.java @@ -0,0 +1,34 @@ +package targetast; + +import de.dhbwstuttgart.core.JavaTXCompiler; +import de.dhbwstuttgart.syntaxtree.ClassOrInterface; +import de.dhbwstuttgart.target.generate.ASTToTargetAST.GenericsResult; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +public class TestGenerics { + + private static final String rootDirectory = System.getProperty("user.dir") + "/src/test/resources/insertGenericsJav/"; + + private record Result(List genericsResults, ClassOrInterface clazz) {} + + private static Result computeGenerics(String filename) throws IOException, ClassNotFoundException { + var file = Path.of(rootDirectory + filename).toFile(); + var compiler = new JavaTXCompiler(List.of(file), List.of(file.getParentFile())); + var inference = compiler.typeInference(); + compiler.generateBytecode(null, inference); + var clazz = compiler.sourceFiles.get(file).getClasses().get(0); + return new Result(compiler.getGeneratedGenerics().get(file), clazz); + } + + @Test + public void testAny() throws Exception { + var generics = computeGenerics("TestAny.jav"); + for (var result : generics.genericsResults) { + System.out.println(result.get(generics.clazz)); + } + } +}