From 1bc58573c7ebe32e319a76762f8776ec384883b6 Mon Sep 17 00:00:00 2001 From: "pl@gohorb.ba-horb.de" Date: Mon, 27 May 2024 17:35:59 +0200 Subject: [PATCH 01/13] modified: src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java modified: src/test/java/AllgemeinTest.java --- src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java | 2 +- src/test/java/AllgemeinTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java index 068f1784..d2f5ed5e 100644 --- a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java +++ b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java @@ -66,7 +66,7 @@ public class JavaTXCompiler { Boolean resultmodel = true; public final Map sourceFiles = new HashMap<>(); - Boolean log = true; //gibt an ob ein Log-File nach System.getProperty("user.dir")+""/logFiles/"" geschrieben werden soll? + Boolean log = false; //gibt an ob ein Log-File nach System.getProperty("user.dir")+""/logFiles/"" geschrieben werden soll? public volatile UnifyTaskModel usedTasks = new UnifyTaskModel(); public final DirectoryClassLoader classLoader; diff --git a/src/test/java/AllgemeinTest.java b/src/test/java/AllgemeinTest.java index 2d89ec83..9c92dd0c 100644 --- a/src/test/java/AllgemeinTest.java +++ b/src/test/java/AllgemeinTest.java @@ -62,7 +62,8 @@ public class AllgemeinTest { //String className = "Cycle"; //String className = "TripleTest"; //String className = "WildcardList"; - String className = "List"; + //String className = "List"; + String className = "Foo"; //PL 2019-10-24: genutzt fuer unterschiedliche Tests path = System.getProperty("user.dir")+"/resources/AllgemeinTest/" + className + ".jav"; //path = System.getProperty("user.dir")+"/src/test/resources/AllgemeinTest/Overloading_Generics.jav"; From 7e6aeaf728d521c27c40b14e2800202f817170ff Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Fri, 7 Jun 2024 12:03:16 +0200 Subject: [PATCH 02/13] Make Function Types implement others to allow Subtyping, fixes #337 --- pom.xml | 4 +- resources/bytecode/javFiles/Bug337.jav | 10 ++++ .../dhbwstuttgart/bytecode/FunNGenerator.java | 42 ++++++++++--- .../de/dhbwstuttgart/core/JavaTXCompiler.java | 1 + .../target/generate/ASTToTargetAST.java | 60 +++++++++++++++---- src/test/java/TestComplete.java | 8 +++ src/test/java/targetast/TestCodegen.java | 4 ++ 7 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 resources/bytecode/javFiles/Bug337.jav diff --git a/pom.xml b/pom.xml index 120f4204..d9dbc114 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,8 @@ http://maven.apache.org/maven-v4_0_0.xsd"> 3.11.0 --enable-preview - 21 - 21 + 22 + 22 diff --git a/resources/bytecode/javFiles/Bug337.jav b/resources/bytecode/javFiles/Bug337.jav new file mode 100644 index 00000000..92bb6fa8 --- /dev/null +++ b/resources/bytecode/javFiles/Bug337.jav @@ -0,0 +1,10 @@ +import java.lang.Integer; +import java.lang.Number; +import java.lang.Object; + +public class Bug337 { + public void main() { + Fun1$$ fun1 = x -> x.hashCode() + 1; + Fun1$$ fun2 = fun1; + } +} \ No newline at end of file diff --git a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java index 411d9bf2..819f5a3e 100644 --- a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java +++ b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java @@ -33,6 +33,23 @@ public class FunNGenerator { public static class GenericParameters { int start; public List parameters = new ArrayList<>(); + final String descriptor; + final List inParams; + + public GenericParameters(List params) { + this.inParams = params; + var type = new TargetRefType(FunNGenerator.getSuperClassName(params.size() - 1), params); + descriptor = applyDescriptor(type, this); + System.out.println(this.parameters); + } + + public TargetType getReturnType() { + return FunNGenerator.getReturnType(inParams); + } + + public List getArguments() { + return FunNGenerator.getArguments(inParams); + } } private static String applyDescriptor(TargetType type, GenericParameters gep) { @@ -98,12 +115,11 @@ public class FunNGenerator { return String.format("Fun%d$$", numberArguments); } - public static byte[] generateSpecializedBytecode(List argumentTypes, TargetType returnType, GenericParameters gep) { - List parameters = Stream - .concat(argumentTypes.stream(), Stream.of(returnType)) - .toList(); + public static byte[] generateSpecializedBytecode(GenericParameters gep, List superInterfaces) { + var argumentTypes = gep.getArguments(); + var returnType = gep.getReturnType(); - StringBuilder funNClassSignature = new StringBuilder(objectSignature + applyDescriptor(new TargetRefType(getSuperClassName(argumentTypes.size()), parameters), gep)); + StringBuilder funNClassSignature = new StringBuilder(objectSignature + gep.descriptor); boolean containsGeneric = false; String genericSignature = "<"; @@ -114,10 +130,18 @@ public class FunNGenerator { genericSignature += ">"; if (containsGeneric) funNClassSignature.insert(0, genericSignature); - System.out.println(funNClassSignature.toString()); + + for (var superInterface : superInterfaces) { + funNClassSignature.append('L'); + funNClassSignature.append(superInterface); + funNClassSignature.append(';'); + } + + var interfaces = new ArrayList<>(superInterfaces); + interfaces.add(getSuperClassName(argumentTypes.size())); ClassWriter classWriter = new ClassWriter(0); - classWriter.visit(bytecodeVersion, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, getSpecializedClassName(argumentTypes, returnType), funNClassSignature.toString(), objectSuperType, new String[]{getSuperClassName(argumentTypes.size())}); + classWriter.visit(bytecodeVersion, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, getSpecializedClassName(argumentTypes, returnType), funNClassSignature.toString(), objectSuperType, interfaces.toArray(String[]::new)); classWriter.visitEnd(); return classWriter.toByteArray(); } @@ -133,6 +157,10 @@ public class FunNGenerator { } } + public static String getSpecializedClassName(GenericParameters gep) { + return getSpecializedClassName(getArguments(gep.inParams), getReturnType(gep.inParams)); + } + public static String getSpecializedClassName(List argumentTypes, TargetType returnType) { return String.format("Fun%d$$%s%s", argumentTypes.size(), diff --git a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java index 62d69b16..ed11515c 100644 --- a/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java +++ b/src/main/java/de/dhbwstuttgart/core/JavaTXCompiler.java @@ -783,6 +783,7 @@ public class JavaTXCompiler { }); } generatedGenerics.put(sf, converter.javaGenerics()); + converter.generateFunNTypes(); return generatedClasses; } diff --git a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java index 1c485d7e..3908af06 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java @@ -479,6 +479,55 @@ public class ASTToTargetAST { return TargetFunNType.fromParams(params, filteredParams); } + private boolean isSubtype(TargetType test, TargetType other) { + var testClass = compiler.getClass(new JavaClassName(test.name())); + var otherClass = compiler.getClass(new JavaClassName(other.name())); + if (testClass == null) return false; + while (testClass != null) { + if (testClass.equals(otherClass)) return true; + if (testClass.getClassName().equals(new JavaClassName("java.lang.Object"))) break; + testClass = compiler.getClass(testClass.getSuperClass().getName()); + } + return false; + } + + private boolean isSupertype(TargetType test, TargetType other) { + return isSubtype(other, test); + } + + private boolean isSubtype(FunNGenerator.GenericParameters test, FunNGenerator.GenericParameters other) { + if (test.getArguments().size() != other.getArguments().size()) return false; + if (!isSubtype(test.getReturnType(), other.getReturnType())) return false; + for (int i = 0; i < test.getArguments().size(); i++) { + var arg1 = test.getArguments().get(i); + var arg2 = other.getArguments().get(i); + if (!isSupertype(arg1, arg2)) return false; + } + return true; + } + + public void generateFunNTypes() { + for (var entry : usedFunN.entrySet()) { + var gep = entry.getValue(); + var superInterfaces = usedFunN.values().stream() + .filter(g -> !g.equals(gep)) + .filter(genericParameters -> isSubtype(gep, genericParameters)) + .map(FunNGenerator::getSpecializedClassName) + .toList(); + + var code = FunNGenerator.generateSpecializedBytecode(gep, superInterfaces); + + try { + classLoader.findClass(entry.getKey()); + } catch (ClassNotFoundException e) { + try { + classLoader.loadClass(code); + } catch (LinkageError ignored) {} + } + auxiliaries.put(entry.getKey(), code); + } + } + protected TargetType convert(RefTypeOrTPHOrWildcardOrGeneric input, GenerateGenerics generics) { return input.acceptTV(new TypeVisitor<>() { @Override @@ -513,17 +562,8 @@ public class ASTToTargetAST { } FunNGenerator.GenericParameters gep = null; if (!usedFunN.containsKey(className)) { - gep = new FunNGenerator.GenericParameters(); - var code = FunNGenerator.generateSpecializedBytecode(FunNGenerator.getArguments(params), FunNGenerator.getReturnType(params), gep); - try { - classLoader.findClass(className); - } catch (ClassNotFoundException e) { - try { - classLoader.loadClass(code); - } catch (LinkageError ignored) {} - } + gep = new FunNGenerator.GenericParameters(params); usedFunN.put(className, gep); - auxiliaries.put(className, code); } else { gep = usedFunN.get(className); } diff --git a/src/test/java/TestComplete.java b/src/test/java/TestComplete.java index 87c6f2aa..2b95681e 100644 --- a/src/test/java/TestComplete.java +++ b/src/test/java/TestComplete.java @@ -1131,4 +1131,12 @@ public class TestComplete { var clazz = classFiles.get("Bug333"); var instance = clazz.getDeclaredConstructor().newInstance(); } + + @Test + public void testBug337() throws Exception { + var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Bug337.jav"); + var clazz = classFiles.get("Bug337"); + var instance = clazz.getDeclaredConstructor().newInstance(); + clazz.getDeclaredMethod("main").invoke(instance); + } } diff --git a/src/test/java/targetast/TestCodegen.java b/src/test/java/targetast/TestCodegen.java index 3d698169..fb901776 100644 --- a/src/test/java/targetast/TestCodegen.java +++ b/src/test/java/targetast/TestCodegen.java @@ -60,6 +60,8 @@ public class TestCodegen { } }).collect(Collectors.toMap(Class::getName, Function.identity()))); + converter.generateFunNTypes(); + for (var entry : converter.auxiliaries.entrySet()) { writeClassFile(entry.getKey(), entry.getValue()); } @@ -104,6 +106,8 @@ public class TestCodegen { } }).collect(Collectors.toMap(Class::getName, Function.identity())); + converter.generateFunNTypes(); + for (var entry : converter.auxiliaries.entrySet()) { writeClassFile(entry.getKey(), entry.getValue()); } From 091a6b8f1f192b2270eb2088ee69f4e1d11d462c Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Fri, 7 Jun 2024 12:16:03 +0200 Subject: [PATCH 03/13] Fix merge conflict --- src/test/java/TestComplete.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/TestComplete.java b/src/test/java/TestComplete.java index 4076fede..7ca70857 100644 --- a/src/test/java/TestComplete.java +++ b/src/test/java/TestComplete.java @@ -1134,17 +1134,17 @@ public class TestComplete { } @Test -<<<<<<< HEAD public void testBug337() throws Exception { var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Bug337.jav"); var clazz = classFiles.get("Bug337"); var instance = clazz.getDeclaredConstructor().newInstance(); clazz.getDeclaredMethod("main").invoke(instance); -======= + } + + @Test public void testBug338() throws Exception { var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Bug338.jav"); var clazz = classFiles.get("Bug338"); var instance = clazz.getDeclaredConstructor().newInstance(); ->>>>>>> ea217d16d5a1d635ac7fe961b98c2ad993c14ac5 } } From ec92b5d5e137a7e30c46088a9cdf38bd5df4dc25 Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Thu, 13 Jun 2024 17:23:19 +0200 Subject: [PATCH 04/13] Work on Bug #332 --- resources/bytecode/javFiles/Bug332.jav | 15 ++ .../target/generate/ASTToTargetAST.java | 144 +++++++++--------- .../generate/StatementToTargetExpression.java | 5 + src/test/java/TestComplete.java | 7 + 4 files changed, 96 insertions(+), 75 deletions(-) create mode 100644 resources/bytecode/javFiles/Bug332.jav diff --git a/resources/bytecode/javFiles/Bug332.jav b/resources/bytecode/javFiles/Bug332.jav new file mode 100644 index 00000000..240dba0c --- /dev/null +++ b/resources/bytecode/javFiles/Bug332.jav @@ -0,0 +1,15 @@ +import java.lang.Object; + +interface Visitor { + public void visit(Object obj); + public void visit(ClassA a); +} + +class ClassA { + void accept(Visitor v) { + v.visit(this); + } +} + +public class Bug332 { +} \ No newline at end of file diff --git a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java index 3280f295..d6e73ba7 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java @@ -5,7 +5,6 @@ import de.dhbwstuttgart.bytecode.FunNGenerator; import de.dhbwstuttgart.core.JavaTXCompiler; import de.dhbwstuttgart.environment.ByteArrayClassLoader; import de.dhbwstuttgart.environment.IByteArrayClassLoader; -import de.dhbwstuttgart.exceptions.NotImplementedException; import de.dhbwstuttgart.parser.NullToken; import de.dhbwstuttgart.parser.scope.JavaClassName; import de.dhbwstuttgart.syntaxtree.*; @@ -18,23 +17,25 @@ import de.dhbwstuttgart.target.tree.expression.*; import de.dhbwstuttgart.target.tree.type.*; import de.dhbwstuttgart.typeinference.result.*; -import javax.sql.rowset.RowSetWarning; -import java.lang.annotation.Target; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.stream.StreamSupport; /** * @author dholle */ public class ASTToTargetAST { + record SignaturePair(TypePlaceholder signature, RefTypeOrTPHOrWildcardOrGeneric parameter) {}; + record SignaturePairTarget(TargetType signature, TargetType parameter) {} + public static RefType OBJECT = ASTFactory.createObjectType(); // TODO It would be better if I could call this directly but the hashcode seems to change protected List all; public Generics generics; final Map> userDefinedGenerics = new HashMap<>(); + final Map> tphsInMethods = new HashMap<>(); + private Method currentMethod; public final JavaTXCompiler compiler; @@ -78,6 +79,12 @@ public class ASTToTargetAST { this.generics = all.get(0); } + public void addSignaturePair(TypePlaceholder signature, RefTypeOrTPHOrWildcardOrGeneric parameter) { + var set = tphsInMethods.getOrDefault(currentMethod, new HashSet<>()); + set.add(new SignaturePair(signature, parameter)); + tphsInMethods.put(currentMethod, set); + } + Optional findMethod(ClassOrInterface owner, String name, List argumentList) { Optional method = Optional.empty(); while (method.isEmpty()) { @@ -131,6 +138,40 @@ public class ASTToTargetAST { return ret; } + public List> groupOverloads(ClassOrInterface input, List methods) { + var res = new ArrayList>(); + for (var method : methods) { + // Convert all methods + var methodsWithTphs = convert(input, method); + // Then check for methods with the same signature + var mapOfSignatures = new HashMap>(); + for (var m : methodsWithTphs) { + var methodsWithSameSignature = mapOfSignatures.getOrDefault(m.method.signature(), new ArrayList<>()); + methodsWithSameSignature.add(m); + mapOfSignatures.put(m.method.signature(), methodsWithSameSignature); + } + + var resMethods = new HashSet(); + for (var methodsWithSignature : mapOfSignatures.values()) { + outer: for (var m1 : methodsWithSignature) { + for (var m2 : methodsWithSignature) { + for (var i = 0; i < m1.args.size(); i++) { + var arg1 = m1.args.get(i); + var arg2 = m2.args.get(i); + if (arg1.parameter.equals(arg2.parameter)) { + if (isSupertype(arg1.signature, arg2.signature) && + !arg1.signature.equals(arg2.signature)) continue outer; + } + } + } + resMethods.add(m1.method); + } + } + res.add(resMethods.stream().toList()); + } + return res; + } + public TargetStructure convert(ClassOrInterface input) { Set javaGenerics = new HashSet<>(); Set txGenerics = new HashSet<>(); @@ -161,11 +202,11 @@ public class ASTToTargetAST { var superInterfaces = input.getSuperInterfaces().stream().map(clazz -> convert(clazz, generics.javaGenerics)).toList(); var constructors = input.getConstructors().stream().map(constructor -> this.convert(input, constructor, finalFieldInitializer)).flatMap(List::stream).toList(); var fields = input.getFieldDecl().stream().map(this::convert).toList(); - var methods = groupOverloads(input.getMethods()).stream().map(m -> convert(input, m)).flatMap(Set::stream).toList(); + var methods = groupOverloads(input, input.getMethods()).stream().flatMap(List::stream).toList(); TargetMethod staticConstructor = null; if (input.getStaticInitializer().isPresent()) - staticConstructor = this.convert(input, input.getStaticInitializer().get()).stream().findFirst().orElseThrow(); + staticConstructor = this.convert(input, input.getStaticInitializer().get()).stream().findFirst().orElseThrow().method; if (input instanceof Record) return new TargetRecord(input.getModifiers(), input.getClassName(), javaGenerics, txGenerics, superInterfaces, constructors, staticConstructor, fields, methods); @@ -206,6 +247,7 @@ public class ASTToTargetAST { generics = all.get(0); List result = new ArrayList<>(); Set> parameterSet = new HashSet<>(); + this.currentMethod = input; for (var s : all) { generics = s; @@ -225,56 +267,6 @@ public class ASTToTargetAST { return result; } - /** - * This only considers type patterns, all other methods aren't grouped together - * @param a - * @param b - * @return - */ - private boolean signatureEquals(Method a, Method b) { - if (!a.name.equals(b.name)) return false; - var para = a.getParameterList().getFormalparalist(); - var parb = b.getParameterList().getFormalparalist(); - if (para.size() != parb.size()) return false; - - for (var i = 0; i < para.size(); i++) { - var pa = para.get(i); - var pb = parb.get(i); - - if (pa instanceof RecordPattern rpa) { - if (pb instanceof RecordPattern rpb) { - if (rpa.getType().equals(rpb.getType())) continue; - } - return false; - } else if (pa.getType().equals(pb.getType())) { - continue; - } - return false; - } - - return true; - } - - // TODO Nested patterns - private List> groupOverloads(List input) { - var done = new HashSet(); - var res = new ArrayList>(); - for (var method : input) { - if (done.contains(method)) continue; - var overloads = new ArrayList(); - overloads.add(method); - done.add(method); - for (var method2 : input) { - if (!done.contains(method2) && signatureEquals(method, method2)) { - done.add(method2); - overloads.add(method2); - } - } - res.add(overloads); - } - return res; - } - private String encodeName(String name, ParameterList params) { var res = new StringBuilder(); res.append(name); @@ -290,9 +282,9 @@ public class ASTToTargetAST { return res.toString(); } - private Set convert(ClassOrInterface clazz, List overloadedMethods) { + private List convert(ClassOrInterface clazz, List overloadedMethods) { if (overloadedMethods.size() == 1) { - return convert(clazz, overloadedMethods.getFirst()); + return convert(clazz, overloadedMethods.getFirst()).stream().map(m -> m.method()).toList(); } var methods = new ArrayList(); for (var method : overloadedMethods) { @@ -329,10 +321,11 @@ public class ASTToTargetAST { var entryPoint = new Method(template.modifier, template.name, template.getReturnType(), params, block, template.getGenerics(), new NullToken()); res.add(entryPoint); // TODO*/ - var res = new HashSet(); + var res = new ArrayList(); for (var method : methods) { var overloads = convert(clazz, method); - for (var overload : overloads) { + for (var m : overloads) { + var overload = m.method; if (res.contains(overload)) throw new CodeGenException("Duplicate method found: " + overload.name() + " with signature " + overload.signature().getSignature()); res.add(overload); } @@ -381,10 +374,12 @@ public class ASTToTargetAST { }).findFirst(); } - private Set convert(ClassOrInterface currentClass, Method method) { - generics = all.get(0); - Set result = new HashSet<>(); - Set> parameterSet = new HashSet<>(); + record MethodWithTphs(TargetMethod method, List args) {} + + private List convert(ClassOrInterface currentClass, Method method) { + generics = all.getFirst(); + List result = new ArrayList<>(); + this.currentMethod = method; for (var s : all) { generics = s; @@ -402,19 +397,18 @@ public class ASTToTargetAST { } } - List finalParams = params; - if (parameterSet.stream().noneMatch(p -> p.equals(finalParams))) { - List txParams = convert(method.getParameterList(), this.generics.txGenerics); + List txParams = convert(method.getParameterList(), this.generics.txGenerics); - var javaMethodGenerics = collectMethodGenerics(currentClass, generics.javaGenerics(), javaGenerics, method); - var txMethodGenerics = collectMethodGenerics(currentClass, generics.txGenerics(), txGenerics, method); + var javaMethodGenerics = collectMethodGenerics(currentClass, generics.javaGenerics(), javaGenerics, method); + var txMethodGenerics = collectMethodGenerics(currentClass, generics.txGenerics(), txGenerics, method); - var javaSignature = new TargetMethod.Signature(javaMethodGenerics, params, returnType); - var txSignature = new TargetMethod.Signature(txMethodGenerics, txParams, convert(method.getReturnType(), this.generics.txGenerics)); - var newMethod = new TargetMethod(method.modifier, method.name, convert(method.block), javaSignature, txSignature); - result.add(newMethod); - parameterSet.add(params); - } + var javaSignature = new TargetMethod.Signature(javaMethodGenerics, params, returnType); + var txSignature = new TargetMethod.Signature(txMethodGenerics, txParams, convert(method.getReturnType(), this.generics.txGenerics)); + var newMethod = new TargetMethod(method.modifier, method.name, convert(method.block), javaSignature, txSignature); + + var concreteParams = tphsInMethods.getOrDefault(method, new HashSet<>()).stream().map(sig -> new SignaturePairTarget(convert(sig.signature), convert(sig.parameter))).toList(); + + result.add(new MethodWithTphs(newMethod, concreteParams)); } return result; diff --git a/src/main/java/de/dhbwstuttgart/target/generate/StatementToTargetExpression.java b/src/main/java/de/dhbwstuttgart/target/generate/StatementToTargetExpression.java index 7e50585a..b94c37cd 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/StatementToTargetExpression.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/StatementToTargetExpression.java @@ -208,6 +208,11 @@ public class StatementToTargetExpression implements ASTVisitor { var isPrivate = false; var signature = methodCall.signatureArguments().stream().map(converter::convert).toList(); + // Add used TPHs to containing method + for (var i = 0; i < methodCall.signatureArguments().size(); i++) { + converter.addSignaturePair(methodCall.signatureArguments().get(i), methodCall.arglist.getArguments().get(i).getType()); + } + var receiverClass = converter.compiler.getClass(receiverName); if (methodCall.receiver instanceof ExpressionReceiver expressionReceiver && expressionReceiver.expr instanceof This) { if (receiverClass == null) throw new DebugException("Class " + receiverName + " does not exist!"); diff --git a/src/test/java/TestComplete.java b/src/test/java/TestComplete.java index 7ca70857..5638e64a 100644 --- a/src/test/java/TestComplete.java +++ b/src/test/java/TestComplete.java @@ -1126,6 +1126,13 @@ public class TestComplete { var instance = clazz.getDeclaredConstructor().newInstance(); } + @Test + public void testBug332() throws Exception { + var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Bug332.jav"); + var clazz = classFiles.get("Bug332"); + var instance = clazz.getDeclaredConstructor().newInstance(); + } + @Test public void testBug333() throws Exception { var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Bug333.jav"); From 63493ed0f7ba8f836b1c4a210040fe1d69be9416 Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Fri, 19 Jul 2024 17:26:39 +0200 Subject: [PATCH 05/13] Make lambdas castable --- resources/bytecode/javFiles/LamRunnable.jav | 16 -- .../de/dhbwstuttgart/bytecode/Codegen.java | 230 ++++++++++++------ .../target/generate/ASTToTargetAST.java | 4 +- .../target/tree/type/TargetFunNType.java | 24 +- .../target/tree/type/TargetType.java | 24 +- src/test/java/TestComplete.java | 7 - src/test/java/targetast/TestCodegen.java | 2 +- 7 files changed, 193 insertions(+), 114 deletions(-) delete mode 100644 resources/bytecode/javFiles/LamRunnable.jav diff --git a/resources/bytecode/javFiles/LamRunnable.jav b/resources/bytecode/javFiles/LamRunnable.jav deleted file mode 100644 index d0da84cc..00000000 --- a/resources/bytecode/javFiles/LamRunnable.jav +++ /dev/null @@ -1,16 +0,0 @@ -import java.lang.Runnable; -import java.lang.System; -import java.lang.String; -import java.io.PrintStream; - -public class LamRunnable { - - public LamRunnable() { - Runnable lam = () -> { - System.out.println("lambda"); - }; - - lam.run(); - } -} - \ No newline at end of file diff --git a/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java b/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java index 8df566b9..77d1460c 100644 --- a/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java +++ b/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java @@ -5,6 +5,8 @@ import de.dhbwstuttgart.exceptions.NotImplementedException; import de.dhbwstuttgart.parser.NullToken; import de.dhbwstuttgart.parser.scope.JavaClassName; import de.dhbwstuttgart.syntaxtree.ClassOrInterface; +import de.dhbwstuttgart.syntaxtree.Method; +import de.dhbwstuttgart.syntaxtree.Pattern; import de.dhbwstuttgart.syntaxtree.type.RefType; import de.dhbwstuttgart.target.generate.ASTToTargetAST; import de.dhbwstuttgart.target.generate.StatementToTargetExpression; @@ -31,15 +33,21 @@ public class Codegen { private final JavaTXCompiler compiler; private final ASTToTargetAST converter; + private class CustomClassWriter extends ClassWriter { + public CustomClassWriter() { + super(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + } + + @Override + protected ClassLoader getClassLoader() { + return compiler.getClassLoader(); + } + } + public Codegen(TargetStructure clazz, JavaTXCompiler compiler, ASTToTargetAST converter) { this.clazz = clazz; this.className = clazz.qualifiedName().getClassName(); - this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) { - @Override - protected ClassLoader getClassLoader() { - return compiler.getClassLoader(); - } - }; + this.cw = new CustomClassWriter(); this.compiler = compiler; this.converter = converter; } @@ -82,8 +90,6 @@ public class Codegen { int localCounter; MethodVisitor mv; TargetType returnType; - // This is used to remember the type from lambda expressions - TargetType contextType; Stack breakStack = new Stack<>(); Stack switchResultValue = new Stack<>(); @@ -270,13 +276,40 @@ public class Codegen { mv.visitInsn(I2F); else if (dest.equals(TargetType.Double)) mv.visitInsn(I2D); + } else if (isFunctionalInterface(source) && isFunctionalInterface(dest) && + !(source instanceof TargetFunNType && dest instanceof TargetFunNType)) { + boxFunctionalInterface(state, source, dest); } else if (!(dest instanceof TargetGenericType)) { - boxPrimitive(state, source); + //boxPrimitive(state, source); mv.visitTypeInsn(CHECKCAST, dest.getInternalName()); unboxPrimitive(state, dest); } } + record TypePair(TargetType from, TargetType to) {} + private Map funWrapperClasses = new HashMap<>(); + + private void boxFunctionalInterface(State state, TargetType source, TargetType dest) { + var mv = state.mv; + var className = "FunWrapper$$" + + source.name().replaceAll("\\.", "\\$") + + "$_$" + + dest.name().replaceAll("\\.", "\\$"); + + funWrapperClasses.put(new TypePair(source, dest), className); + mv.visitTypeInsn(NEW, className); + mv.visitInsn(DUP_X1); + mv.visitInsn(SWAP); + mv.visitMethodInsn(INVOKESPECIAL, className, "", "(" + source.toDescriptor() + ")V", false); + } + + private boolean isFunctionalInterface(TargetType type) { + if (type instanceof TargetFunNType) return true; + if (type instanceof TargetRefType) + return compiler.getClass(new JavaClassName(type.name())).isFunctionalInterface(); + return false; + } + private TargetType largerType(TargetType left, TargetType right) { if (left.equals(TargetType.String) || right.equals(TargetType.String)) { return TargetType.String; @@ -727,41 +760,15 @@ public class Codegen { var mv = state.mv; String methodName = "apply"; - TargetMethod.Signature signature = null; - - if (!(state.contextType instanceof TargetFunNType ctx)) { - var intf = compiler.getClass(new JavaClassName(state.contextType.name())); - if (intf != null) { - var method = intf.getMethods().stream().filter(m -> Modifier.isAbstract(m.modifier)).findFirst().orElseThrow(); - methodName = method.getName(); - var methodParams = new ArrayList(); - for (var i = 0; i < lambda.signature().parameters().size(); i++) { - var param = lambda.signature().parameters().get(i); - var tpe = converter.convert(method.getParameterList().getParameterAt(i).getType()); - methodParams.add(param.withType(tpe)); - } - var retType = converter.convert(method.getReturnType()); - signature = new TargetMethod.Signature(Set.of(), methodParams, retType); - } - } - if (signature == null) { - signature = new TargetMethod.Signature(Set.of(), lambda.signature().parameters().stream().map(par -> par.withType(TargetType.Object)).toList(), TargetType.Object); - } - - signature = new TargetMethod.Signature( - signature.generics(), - signature.parameters().stream().map(par -> - par.withType(par.pattern().type() instanceof TargetGenericType ? TargetType.Object : par.pattern().type()) - ).toList(), - signature.returnType() instanceof TargetGenericType ? TargetType.Object : signature.returnType() - ); + TargetMethod.Signature signature = new TargetMethod.Signature(Set.of(), + lambda.signature().parameters().stream().map( + par -> par.withType(TargetType.Object)).toList(), + lambda.signature().returnType() != null ? TargetType.Object : null); var parameters = new ArrayList<>(lambda.captures()); parameters.addAll(signature.parameters()); var implSignature = new TargetMethod.Signature(Set.of(), parameters, lambda.signature().returnType()); - // Normalize - TargetMethod impl; if (lambdas.containsKey(lambda)) { impl = lambdas.get(lambda); @@ -782,7 +789,6 @@ public class Codegen { params.add(new TargetRefType(clazz.qualifiedName().getClassName())); params.addAll(lambda.captures().stream().map(mp -> mp.pattern().type()).toList()); - var descriptor = TargetMethod.getDescriptor(state.contextType, params.toArray(TargetType[]::new)); mv.visitVarInsn(ALOAD, 0); for (var index = 0; index < lambda.captures().size(); index++) { var capture = lambda.captures().get(index); @@ -792,9 +798,42 @@ public class Codegen { mv.visitTypeInsn(CHECKCAST, capture.pattern().type().getInternalName()); } + var descriptor = TargetMethod.getDescriptor(lambda.type(), params.toArray(TargetType[]::new)); mv.visitInvokeDynamicInsn(methodName, descriptor, bootstrap, Type.getType(signature.getSignature()), handle, Type.getType(signature.getDescriptor())); } + private int findReturnCode(TargetType returnType) { + if (returnType.equals(TargetType.boolean_) + || returnType.equals(TargetType.char_) + || returnType.equals(TargetType.int_) + || returnType.equals(TargetType.short_) + || returnType.equals(TargetType.byte_)) + return IRETURN; + else if (returnType.equals(TargetType.long_)) + return LRETURN; + else if (returnType.equals(TargetType.float_)) + return FRETURN; + else if (returnType.equals(TargetType.double_)) + return DRETURN; + return ARETURN; + } + + private int findLoadCode(TargetType loadType) { + if (loadType.equals(TargetType.boolean_) + || loadType.equals(TargetType.char_) + || loadType.equals(TargetType.int_) + || loadType.equals(TargetType.short_) + || loadType.equals(TargetType.byte_)) + return ILOAD; + else if (loadType.equals(TargetType.long_)) + return LLOAD; + else if (loadType.equals(TargetType.float_)) + return FLOAD; + else if (loadType.equals(TargetType.double_)) + return DLOAD; + return ALOAD; + } + private void generate(State state, TargetExpression expr) { var mv = state.mv; switch (expr) { @@ -819,10 +858,7 @@ public class Codegen { break; } case TargetCast cast: - var ctx = state.contextType; - state.contextType = cast.type(); generate(state, cast.expr()); - state.contextType = ctx; convertTo(state, cast.expr().type(), cast.type()); break; case TargetInstanceOf instanceOf: @@ -867,10 +903,7 @@ public class Codegen { case TargetAssign assign: { switch (assign.left()) { case TargetLocalVar localVar -> { - var ctype = state.contextType; - state.contextType = localVar.type(); generate(state, assign.right()); - state.contextType = ctype; convertTo(state, assign.right().type(), localVar.type()); boxPrimitive(state, localVar.type()); @@ -883,10 +916,7 @@ public class Codegen { if (!(dot.left() instanceof TargetThis && dot.isStatic())) generate(state, dot.left()); - var ctype = state.contextType; - state.contextType = fieldType; generate(state, assign.right()); - state.contextType = ctype; convertTo(state, assign.right().type(), fieldType); boxPrimitive(state, fieldType); @@ -1016,29 +1046,12 @@ public class Codegen { case TargetReturn ret: { if (ret.expression() != null && state.returnType != null) { if (state.returnType instanceof TargetPrimitiveType) { - var ctype = state.contextType; - state.contextType = state.returnType; generate(state, ret.expression()); - state.contextType = ctype; unboxPrimitive(state, state.returnType); - if (state.returnType.equals(TargetType.boolean_) - || state.returnType.equals(TargetType.char_) - || state.returnType.equals(TargetType.int_) - || state.returnType.equals(TargetType.short_) - || state.returnType.equals(TargetType.byte_)) - mv.visitInsn(IRETURN); - else if (state.returnType.equals(TargetType.long_)) - mv.visitInsn(LRETURN); - else if (state.returnType.equals(TargetType.float_)) - mv.visitInsn(FRETURN); - else if (state.returnType.equals(TargetType.double_)) - mv.visitInsn(DRETURN); + mv.visitInsn(findReturnCode(state.returnType)); } else { - var ctype = state.contextType; - state.contextType = state.returnType; generate(state, ret.expression()); - state.contextType = ctype; boxPrimitive(state, ret.expression().type()); convertTo(state, ret.expression().type(), state.returnType); mv.visitInsn(ARETURN); @@ -1089,12 +1102,10 @@ public class Codegen { for (var i = 0; i < call.args().size(); i++) { var e = call.args().get(i); var arg = call.parameterTypes().get(i); - var ctype = state.contextType; - state.contextType = arg; generate(state, e); + convertTo(state, e.type(), arg); if (!(arg instanceof TargetPrimitiveType)) boxPrimitive(state, e.type()); - state.contextType = ctype; } var descriptor = call.getDescriptor(); if (call.owner() instanceof TargetFunNType) // Decay FunN @@ -1597,6 +1608,85 @@ public class Codegen { if (clazz instanceof TargetRecord) generateRecordMethods(); + // Generate wrapper classes for function types + for (var pair : funWrapperClasses.keySet()) { + var className = funWrapperClasses.get(pair); + ClassWriter cw2 = new CustomClassWriter(); + cw2.visit(V1_8, ACC_PUBLIC, className, null, "java/lang/Object", new String[] { pair.to.getInternalName() }); + cw2.visitField(ACC_PRIVATE, "wrapped", pair.from.toDescriptor(), null, null).visitEnd(); + + // Generate constructor + var ctor = cw2.visitMethod(ACC_PUBLIC, "", "(" + pair.from.toDescriptor() + ")V", null, null); + ctor.visitVarInsn(ALOAD, 0); + ctor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + ctor.visitVarInsn(ALOAD, 0); + ctor.visitVarInsn(ALOAD, 1); + ctor.visitFieldInsn(PUTFIELD, className, "wrapped", pair.from.toDescriptor()); + ctor.visitInsn(RETURN); + ctor.visitMaxs(0, 0); + ctor.visitEnd(); + + String methodName = "apply"; + String fromDescriptor = null; + TargetType fromReturn = null; + if (!(pair.from instanceof TargetFunNType funNType)) { + var fromClass = compiler.getClass(new JavaClassName(pair.from.name())); + var fromMethod = fromClass.getMethods().stream().filter(m -> (m.modifier & ACC_ABSTRACT) != 0).findFirst().orElseThrow(); + methodName = fromMethod.name; + + fromReturn = converter.convert(fromMethod.getReturnType()); + var fromParams = converter.convert(fromMethod.getParameterList(), converter.generics.javaGenerics()).stream().map(m -> m.pattern().type()).toArray(TargetType[]::new); + fromDescriptor = TargetMethod.getDescriptor(fromReturn, fromParams); + } else { + fromReturn = funNType.arity() > 1 ? TargetType.Object : null; + fromDescriptor = funNType.toMethodDescriptor(); + } + + var toClass = compiler.getClass(new JavaClassName(pair.to.name())); + var toMethod = toClass.getMethods().stream().filter(m -> (m.modifier & ACC_ABSTRACT) != 0).findFirst().orElseThrow(); + var toReturn = converter.convert(toMethod.getReturnType()); + var toParams = converter.convert(toMethod.getParameterList(), converter.generics.javaGenerics()).stream().map(m -> m.pattern().type()).toArray(TargetType[]::new); + var toDescriptor = TargetMethod.getDescriptor(toReturn, toParams); + + // Generate wrapper method + var mv = cw2.visitMethod(ACC_PUBLIC, toMethod.name, toDescriptor, null, null); + var state = new State(null, mv, 0); + + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, className, "wrapped", pair.from.toDescriptor()); + for (var i = 0; i < toParams.length; i++) { + var arg = toParams[i]; + mv.visitVarInsn(findLoadCode(arg), i + 1); + } + mv.visitMethodInsn(INVOKEINTERFACE, pair.from.getInternalName(), methodName, fromDescriptor, true); + if (fromReturn != null) { + if (toReturn instanceof TargetPrimitiveType) { + convertTo(state, fromReturn, TargetType.toWrapper(toReturn)); + } else convertTo(state, fromReturn, toReturn); + } + + if (toReturn != null) + mv.visitInsn(findReturnCode(toReturn)); + + else mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw2.visitEnd(); + var bytes = cw2.toByteArray(); + converter.auxiliaries.put(className, bytes); + + // TODO These class loading shenanigans happen in a few places, the tests load the classes individually. + // Instead we should just look at the folder. + try { + converter.classLoader.findClass(className); + } catch (ClassNotFoundException e) { + try { + converter.classLoader.loadClass(bytes); + } catch (LinkageError ignored) {} + } + } + cw.visitEnd(); return cw.toByteArray(); } diff --git a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java index d6e73ba7..f80ec109 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java @@ -57,7 +57,7 @@ public class ASTToTargetAST { } - protected IByteArrayClassLoader classLoader; + public IByteArrayClassLoader classLoader; protected SourceFile sourceFile; public ASTToTargetAST(List resultSets) { @@ -483,7 +483,7 @@ public class ASTToTargetAST { if (gep.parameters.get(i) != null) filteredParams.add(newParams.get(i)); } - return TargetFunNType.fromParams(params, filteredParams); + return TargetFunNType.fromParams(params, filteredParams, params.size()); } private boolean isSubtype(TargetType test, TargetType other) { diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java index f35f1043..1c998662 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java @@ -4,15 +4,29 @@ import de.dhbwstuttgart.bytecode.FunNGenerator; import java.util.List; -public record TargetFunNType(String name, List params) implements TargetSpecializedType { +public record TargetFunNType(String name, List params, int arity) implements TargetSpecializedType { - public static TargetFunNType fromParams(List params) { - return fromParams(params, params); + public static TargetFunNType fromParams(List params, int arity) { + return fromParams(params, params, arity); } - public static TargetFunNType fromParams(List params, List realParams) { + public static TargetFunNType fromParams(List params, List realParams, int arity) { var name = FunNGenerator.getSpecializedClassName(FunNGenerator.getArguments(params), FunNGenerator.getReturnType(params)); - return new TargetFunNType(name, realParams); + return new TargetFunNType(name, realParams, arity); + } + + public String toMethodDescriptor() { + var res = "("; + for (var i = 0; i < arity - 1; i++) { + res += "Ljava/lang/Object;"; + } + res += ")"; + if (arity > 0) { + res += "Ljava/lang/Object;"; + } else { + res += "V"; + } + return res; } @Override diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java index 1cb57f90..f34e84ef 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java @@ -53,19 +53,17 @@ public sealed interface TargetType }; } - static TargetType toTargetType(Class clazz) { - if (clazz.isPrimitive()) { - if (clazz.equals(boolean.class)) return boolean_; - if (clazz.equals(char.class)) return char_; - if (clazz.equals(byte.class)) return byte_; - if (clazz.equals(short.class)) return short_; - if (clazz.equals(int.class)) return int_; - if (clazz.equals(long.class)) return long_; - if (clazz.equals(float.class)) return float_; - if (clazz.equals(double.class)) return double_; - } - if (clazz.equals(void.class)) return null; - return new TargetRefType(clazz.getName()); + static TargetType toWrapper(TargetType f) { + if (f.equals(boolean_)) return Boolean; + if (f.equals(char_)) return Char; + if (f.equals(byte_)) return Byte; + if (f.equals(short_)) return Short; + if (f.equals(int_)) return Integer; + if (f.equals(long_)) return Long; + if (f.equals(float_)) return Float; + if (f.equals(double_)) return Double; + + return f; } String toSignature(); diff --git a/src/test/java/TestComplete.java b/src/test/java/TestComplete.java index 5638e64a..c4599e02 100644 --- a/src/test/java/TestComplete.java +++ b/src/test/java/TestComplete.java @@ -860,13 +860,6 @@ public class TestComplete { var instance = clazz.getDeclaredConstructor().newInstance(); } - @Test - public void testLamRunnable() throws Exception { - var classFiles = generateClassFiles(new ByteArrayClassLoader(), "LamRunnable.jav"); - var clazz = classFiles.get("LamRunnable"); - var instance = clazz.getDeclaredConstructor().newInstance(); - } - @Test public void testAccess() throws Exception { var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Access.jav"); diff --git a/src/test/java/targetast/TestCodegen.java b/src/test/java/targetast/TestCodegen.java index fb901776..e83c2f95 100644 --- a/src/test/java/targetast/TestCodegen.java +++ b/src/test/java/targetast/TestCodegen.java @@ -277,7 +277,7 @@ public class TestCodegen { public void testLambda() throws Exception { var classLoader = new ByteArrayClassLoader(); // var fun = classLoader.loadClass(Path.of(System.getProperty("user.dir"), "src/test/java/targetast/Fun1$$.class")); - var interfaceType = TargetFunNType.fromParams(List.of(TargetType.Integer)); + var interfaceType = TargetFunNType.fromParams(List.of(TargetType.Integer), 1); var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, new JavaClassName("CGLambda")); targetClass.addConstructor(Opcodes.ACC_PUBLIC, List.of(), new TargetBlock(List.of(new TargetMethodCall(null, new TargetSuper(TargetType.Object), List.of(), TargetType.Object, "", false, false, false)))); From ba8810e5df31a8d418ccb426c51be03ef2120743 Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Fri, 19 Jul 2024 18:04:33 +0200 Subject: [PATCH 06/13] I don't know why isFunctionalInterface returns true on things that aren't even interfaces but here we go --- src/main/java/de/dhbwstuttgart/bytecode/Codegen.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java b/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java index 77d1460c..d5b11caa 100644 --- a/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java +++ b/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java @@ -305,8 +305,11 @@ public class Codegen { private boolean isFunctionalInterface(TargetType type) { if (type instanceof TargetFunNType) return true; - if (type instanceof TargetRefType) - return compiler.getClass(new JavaClassName(type.name())).isFunctionalInterface(); + if (type instanceof TargetRefType) { + var clazz = compiler.getClass(new JavaClassName(type.name())); + return (clazz.getModifiers() & Modifier.INTERFACE) != 0 && clazz.isFunctionalInterface(); + } + return false; } From edafbbc5a0005bfcf23eff033cb2035e0faa03af Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Tue, 23 Jul 2024 15:33:09 +0200 Subject: [PATCH 07/13] Fix #340 --- .../de/dhbwstuttgart/bytecode/Codegen.java | 2 +- .../dhbwstuttgart/bytecode/FunNGenerator.java | 48 +++++++++++-------- .../target/generate/ASTToTargetAST.java | 17 ++++--- .../target/tree/type/TargetFunNType.java | 14 +++--- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java b/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java index d5b11caa..578e2821 100644 --- a/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java +++ b/src/main/java/de/dhbwstuttgart/bytecode/Codegen.java @@ -1641,7 +1641,7 @@ public class Codegen { var fromParams = converter.convert(fromMethod.getParameterList(), converter.generics.javaGenerics()).stream().map(m -> m.pattern().type()).toArray(TargetType[]::new); fromDescriptor = TargetMethod.getDescriptor(fromReturn, fromParams); } else { - fromReturn = funNType.arity() > 1 ? TargetType.Object : null; + fromReturn = funNType.returnArguments() > 0 ? TargetType.Object : null; fromDescriptor = funNType.toMethodDescriptor(); } diff --git a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java index 819f5a3e..9b6bd15f 100644 --- a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java +++ b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java @@ -34,13 +34,12 @@ public class FunNGenerator { int start; public List parameters = new ArrayList<>(); final String descriptor; - final List inParams; + public final List inParams; - public GenericParameters(List params) { + public GenericParameters(List params, int numReturns) { this.inParams = params; - var type = new TargetRefType(FunNGenerator.getSuperClassName(params.size() - 1), params); + var type = new TargetRefType(FunNGenerator.getSuperClassName(params.size() - 1, numReturns), params); descriptor = applyDescriptor(type, this); - System.out.println(this.parameters); } public TargetType getReturnType() { @@ -86,7 +85,7 @@ public class FunNGenerator { return applyNameDescriptor(type).replace("/", "$").replace(";", "$_$"); } - public static byte[] generateSuperBytecode(int numberArguments) { + public static byte[] generateSuperBytecode(int numberArguments, int numReturnTypes) { StringBuilder superFunNClassSignature = new StringBuilder("<"); StringBuilder superFunNMethodSignature = new StringBuilder("("); StringBuilder superFunNMethodDescriptor = new StringBuilder("("); @@ -97,22 +96,27 @@ public class FunNGenerator { superFunNMethodDescriptor.append(objectSignature); } superFunNClassSignature.append(String.format("%s:%s>%s", returnGeneric, objectSignature, objectSignature)); - superFunNMethodSignature.append(String.format(")T%s;", returnGeneric)); - superFunNMethodDescriptor.append(String.format(")%s", objectSignature)); + if (numReturnTypes > 0) { + superFunNMethodSignature.append(String.format(")T%s;", returnGeneric)); + superFunNMethodDescriptor.append(String.format(")%s", objectSignature)); + } else { + superFunNMethodSignature.append(")V"); + superFunNMethodDescriptor.append(")V"); + } System.out.println(superFunNMethodSignature); ClassWriter classWriter = new ClassWriter(0); MethodVisitor methodVisitor; - classWriter.visit(bytecodeVersion, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, getSuperClassName(numberArguments), superFunNClassSignature.toString(), objectSuperType, null); + classWriter.visit(bytecodeVersion, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, getSuperClassName(numberArguments, numReturnTypes), superFunNClassSignature.toString(), objectSuperType, null); methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, methodName, superFunNMethodDescriptor.toString(), superFunNMethodSignature.toString(), null); methodVisitor.visitEnd(); classWriter.visitEnd(); return classWriter.toByteArray(); } - public static String getSuperClassName(int numberArguments) { - return String.format("Fun%d$$", numberArguments); + public static String getSuperClassName(int numberArguments, int returnArguments) { + return returnArguments > 0 ? String.format("Fun%d$$", numberArguments) : String.format("FunVoid%d$$", numberArguments); } public static byte[] generateSpecializedBytecode(GenericParameters gep, List superInterfaces) { @@ -138,7 +142,7 @@ public class FunNGenerator { } var interfaces = new ArrayList<>(superInterfaces); - interfaces.add(getSuperClassName(argumentTypes.size())); + interfaces.add(getSuperClassName(argumentTypes.size(), returnType != null ? 1 : 0)); ClassWriter classWriter = new ClassWriter(0); classWriter.visit(bytecodeVersion, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, getSpecializedClassName(argumentTypes, returnType), funNClassSignature.toString(), objectSuperType, interfaces.toArray(String[]::new)); @@ -162,13 +166,19 @@ public class FunNGenerator { } public static String getSpecializedClassName(List argumentTypes, TargetType returnType) { - return String.format("Fun%d$$%s%s", + var arguments = argumentTypes + .stream() + .map(FunNGenerator::encodeType) + .collect(Collectors.joining()); + + if (returnType != null) + return String.format("Fun%d$$%s%s", + argumentTypes.size(), + arguments, + encodeType(returnType)); + else return String.format("FunVoidImpl%d$$%s", argumentTypes.size(), - argumentTypes - .stream() - .map(FunNGenerator::encodeType) - .collect(Collectors.joining()), - encodeType(returnType)); + arguments); } public static List getArguments(List list) { @@ -179,8 +189,8 @@ public class FunNGenerator { } public static TargetType getReturnType(List list) { - if(list.size() == 0) + if(list.isEmpty()) throw new IndexOutOfBoundsException(); - return list.get(list.size() - 1); + return list.getLast(); } } diff --git a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java index f80ec109..36fa146b 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java @@ -480,10 +480,10 @@ public class ASTToTargetAST { } var filteredParams = new ArrayList(); for (var i = 0; i < newParams.size(); i++) { - if (gep.parameters.get(i) != null) + if (i < gep.inParams.size() && gep.inParams.get(i) != null) filteredParams.add(newParams.get(i)); } - return TargetFunNType.fromParams(params, filteredParams, params.size()); + return TargetFunNType.fromParams(params, filteredParams, gep.getReturnType() != null ? 1 : 0); } private boolean isSubtype(TargetType test, TargetType other) { @@ -547,17 +547,16 @@ public class ASTToTargetAST { } var params = refType.getParaList().stream().map(type -> { - var res = convert(type, generics); - if (res == null) res = new TargetRefType("java.lang.Void"); - return res; + return convert(type, generics); }).toList(); if (name.matches("Fun\\d+\\$\\$")) { // TODO This seems like a bad idea - var className = FunNGenerator.getSpecializedClassName(FunNGenerator.getArguments(params), FunNGenerator.getReturnType(params)); + var returnType = FunNGenerator.getReturnType(params); + var className = FunNGenerator.getSpecializedClassName(FunNGenerator.getArguments(params), returnType); if (!usedFunNSuperTypes.contains(params.size())) { usedFunNSuperTypes.add(params.size()); - var code = FunNGenerator.generateSuperBytecode(params.size() - 1); - var superClassName = FunNGenerator.getSuperClassName(params.size() - 1); + var code = FunNGenerator.generateSuperBytecode(params.size() - 1, returnType != null ? 1 : 0); + var superClassName = FunNGenerator.getSuperClassName(params.size() - 1, returnType != null ? 1 : 0); try { classLoader.findClass(superClassName); } catch (ClassNotFoundException e) { @@ -569,7 +568,7 @@ public class ASTToTargetAST { } FunNGenerator.GenericParameters gep = null; if (!usedFunN.containsKey(className)) { - gep = new FunNGenerator.GenericParameters(params); + gep = new FunNGenerator.GenericParameters(params, returnType != null ? 1 : 0); usedFunN.put(className, gep); } else { gep = usedFunN.get(className); diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java index 1c998662..db6343de 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java @@ -4,24 +4,24 @@ import de.dhbwstuttgart.bytecode.FunNGenerator; import java.util.List; -public record TargetFunNType(String name, List params, int arity) implements TargetSpecializedType { +public record TargetFunNType(String name, List funNParams, List params, int returnArguments) implements TargetSpecializedType { - public static TargetFunNType fromParams(List params, int arity) { - return fromParams(params, params, arity); + public static TargetFunNType fromParams(List params, int returnArguments) { + return fromParams(params, params, returnArguments); } - public static TargetFunNType fromParams(List params, List realParams, int arity) { + public static TargetFunNType fromParams(List params, List realParams, int returnArguments) { var name = FunNGenerator.getSpecializedClassName(FunNGenerator.getArguments(params), FunNGenerator.getReturnType(params)); - return new TargetFunNType(name, realParams, arity); + return new TargetFunNType(name, params, realParams, returnArguments); } public String toMethodDescriptor() { var res = "("; - for (var i = 0; i < arity - 1; i++) { + for (var i = 0; i < funNParams.size() - 1; i++) { res += "Ljava/lang/Object;"; } res += ")"; - if (arity > 0) { + if (returnArguments > 0) { res += "Ljava/lang/Object;"; } else { res += "V"; From 56b73332c0ab2e69bfb507a808faea7995ad30d0 Mon Sep 17 00:00:00 2001 From: "pl@gohorb.ba-horb.de" Date: Wed, 24 Jul 2024 18:27:32 +0200 Subject: [PATCH 08/13] modified: pom.xml new file: resources/AllgemeinTest/Foo.jav modified: src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java --- pom.xml | 4 +- resources/AllgemeinTest/Foo.jav | 6 +++ .../SyntaxTreeGenerator/FCGenerator.java | 51 ++++++++++++++++++- 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 resources/AllgemeinTest/Foo.jav diff --git a/pom.xml b/pom.xml index d9dbc114..120f4204 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,8 @@ http://maven.apache.org/maven-v4_0_0.xsd"> 3.11.0 --enable-preview - 22 - 22 + 21 + 21 diff --git a/resources/AllgemeinTest/Foo.jav b/resources/AllgemeinTest/Foo.jav new file mode 100644 index 00000000..d8195c0f --- /dev/null +++ b/resources/AllgemeinTest/Foo.jav @@ -0,0 +1,6 @@ +public class Foo{ + + public accept(Bar b){ + b.visit(this); + } +} \ No newline at end of file diff --git a/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java b/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java index 4f4474a5..37df41fa 100644 --- a/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java +++ b/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java @@ -158,7 +158,8 @@ public class FCGenerator { } /** - * Tauscht die GTVs in einem Typ gegen die entsprechenden Typen in der übergebenen Map aus. + * Tauscht die GTVs in einem Typ gegen die entsprechenden Typen in der übergebenen Map aus auf der direkten Argumentebene. + * Hier sind keine Wildcards zulässig */ private static class TypeExchanger implements TypeVisitor{ @@ -172,6 +173,7 @@ public class FCGenerator { public RefTypeOrTPHOrWildcardOrGeneric visit(RefType refType) { List params = new ArrayList<>(); for(RefTypeOrTPHOrWildcardOrGeneric param : refType.getParaList()){ + //params.add(param.acceptTV(new TypeExchangerInner(gtvs))); params.add(param.acceptTV(this)); } RefTypeOrTPHOrWildcardOrGeneric ret = new RefType(refType.getName(), params, new NullToken()); @@ -201,4 +203,51 @@ public class FCGenerator { } } + + /** + * Tauscht die GTVs in einem Typ gegen die entsprechenden Typen in der übergebenen Map aus auf den Argumenten der Argumente. + * Hier sind Wildcards zulässig + */ + private static class TypeExchangerInner implements TypeVisitor{ + + private final HashMap gtvs; + + TypeExchangerInner(HashMap gtvs){ + this.gtvs = gtvs; + } + + @Override + public RefTypeOrTPHOrWildcardOrGeneric visit(RefType refType) { + List params = new ArrayList<>(); + for(RefTypeOrTPHOrWildcardOrGeneric param : refType.getParaList()){ + params.add(param.acceptTV(this)); + } + RefTypeOrTPHOrWildcardOrGeneric ret = new RefType(refType.getName(), params, new NullToken()); + return ret; + } + + @Override + public RefTypeOrTPHOrWildcardOrGeneric visit(SuperWildcardType superWildcardType) { + return superWildcardType; + } + + @Override + public RefTypeOrTPHOrWildcardOrGeneric visit(TypePlaceholder typePlaceholder) { + throw new DebugException("Dieser Fall darf nicht auftreten"); + } + + @Override + public RefTypeOrTPHOrWildcardOrGeneric visit(ExtendsWildcardType extendsWildcardType) { + return extendsWildcardType; + } + + @Override + public RefTypeOrTPHOrWildcardOrGeneric visit(GenericRefType genericRefType) { + if(! gtvs.containsKey(genericRefType.getParsedName())) + throw new DebugException("Dieser Fall darf nicht auftreten"); + return gtvs.get(genericRefType.getParsedName()); + } + + } + } From c10de35ca24de8192b0921c5b6d565db9b3d763e Mon Sep 17 00:00:00 2001 From: "pl@gohorb.ba-horb.de" Date: Wed, 24 Jul 2024 18:28:07 +0200 Subject: [PATCH 09/13] new file: resources/AllgemeinTest/Bar.java --- resources/AllgemeinTest/Bar.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 resources/AllgemeinTest/Bar.java diff --git a/resources/AllgemeinTest/Bar.java b/resources/AllgemeinTest/Bar.java new file mode 100644 index 00000000..5d82fbb2 --- /dev/null +++ b/resources/AllgemeinTest/Bar.java @@ -0,0 +1,10 @@ +public class Bar{ + + void visit(Object o){ + System.out.println("Object"); + } + + void visit(Bla f){ + System.out.println("Foo"); + } +} \ No newline at end of file From fdffc11580d8bceacea426090a5db20c085b3719 Mon Sep 17 00:00:00 2001 From: "pl@gohorb.ba-horb.de" Date: Wed, 24 Jul 2024 23:23:39 +0200 Subject: [PATCH 10/13] modified: resources/AllgemeinTest/Box.jav deleted: resources/bytecode/javFiles/Box.java modified: src/test/java/AllgemeinTest.java --- resources/AllgemeinTest/Box.jav | 5 +++-- resources/bytecode/javFiles/Box.java | 3 --- src/test/java/AllgemeinTest.java | 3 ++- 3 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 resources/bytecode/javFiles/Box.java diff --git a/resources/AllgemeinTest/Box.jav b/resources/AllgemeinTest/Box.jav index b2716354..95b869ab 100644 --- a/resources/AllgemeinTest/Box.jav +++ b/resources/AllgemeinTest/Box.jav @@ -1,8 +1,9 @@ -class Box { +public class Box { A a; + public Box() { } public Box(A a) { - this.a = a; + //this.a = a; } } \ No newline at end of file diff --git a/resources/bytecode/javFiles/Box.java b/resources/bytecode/javFiles/Box.java deleted file mode 100644 index 581e7256..00000000 --- a/resources/bytecode/javFiles/Box.java +++ /dev/null @@ -1,3 +0,0 @@ -class Box { - void m(A a) { } -} \ No newline at end of file diff --git a/src/test/java/AllgemeinTest.java b/src/test/java/AllgemeinTest.java index adc91ad6..461dd557 100644 --- a/src/test/java/AllgemeinTest.java +++ b/src/test/java/AllgemeinTest.java @@ -66,7 +66,8 @@ public class AllgemeinTest { //String className = "WildcardList"; //String className = "List"; //String className = "Box"; - String className = "GenBox"; + //String className = "GenBox"; + String className = "InnerInf"; //PL 2019-10-24: genutzt fuer unterschiedliche Tests path = System.getProperty("user.dir")+"/resources/AllgemeinTest/" + className + ".jav"; //path = System.getProperty("user.dir")+"/src/test/resources/AllgemeinTest/Overloading_Generics.jav"; From 7037bdf9ef784ae583ecfb3b6e6f2ef55a9ff1cf Mon Sep 17 00:00:00 2001 From: "pl@gohorb.ba-horb.de" Date: Wed, 24 Jul 2024 23:31:13 +0200 Subject: [PATCH 11/13] modified: src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java --- .../dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java b/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java index 37df41fa..fa923b4e 100644 --- a/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java +++ b/src/main/java/de/dhbwstuttgart/parser/SyntaxTreeGenerator/FCGenerator.java @@ -173,8 +173,7 @@ public class FCGenerator { public RefTypeOrTPHOrWildcardOrGeneric visit(RefType refType) { List params = new ArrayList<>(); for(RefTypeOrTPHOrWildcardOrGeneric param : refType.getParaList()){ - //params.add(param.acceptTV(new TypeExchangerInner(gtvs))); - params.add(param.acceptTV(this)); + params.add(param.acceptTV(new TypeExchangerInner(gtvs))); } RefTypeOrTPHOrWildcardOrGeneric ret = new RefType(refType.getName(), params, new NullToken()); return ret; From a654f55deb74d75c22fe5dbdd5e5bb265da85342 Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Thu, 25 Jul 2024 17:05:27 +0200 Subject: [PATCH 12/13] Fix yTest --- .../dhbwstuttgart/bytecode/FunNGenerator.java | 24 +++++++++++++++---- .../syntaxtree/factory/ASTFactory.java | 2 ++ .../target/generate/GenerateGenerics.java | 12 ++++------ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java index 9b6bd15f..ae039ade 100644 --- a/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java +++ b/src/main/java/de/dhbwstuttgart/bytecode/FunNGenerator.java @@ -1,11 +1,13 @@ package de.dhbwstuttgart.bytecode; +import de.dhbwstuttgart.syntaxtree.statement.Break; import de.dhbwstuttgart.target.tree.TargetGeneric; import de.dhbwstuttgart.target.tree.type.*; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; +import java.sql.Array; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -35,19 +37,33 @@ public class FunNGenerator { public List parameters = new ArrayList<>(); final String descriptor; public final List inParams; + public final List realParams; public GenericParameters(List params, int numReturns) { - this.inParams = params; + this.realParams = params; + this.inParams = flattenTypeParams(params); var type = new TargetRefType(FunNGenerator.getSuperClassName(params.size() - 1, numReturns), params); descriptor = applyDescriptor(type, this); } + private static List flattenTypeParams(List params) { + var res = new ArrayList(); + for (var param : params) { + if (param instanceof TargetSpecializedType tspec) { + res.addAll(flattenTypeParams(tspec.params())); + } else { + res.add(param); + } + } + return res; + } + public TargetType getReturnType() { - return FunNGenerator.getReturnType(inParams); + return FunNGenerator.getReturnType(realParams); } public List getArguments() { - return FunNGenerator.getArguments(inParams); + return FunNGenerator.getArguments(realParams); } } @@ -162,7 +178,7 @@ public class FunNGenerator { } public static String getSpecializedClassName(GenericParameters gep) { - return getSpecializedClassName(getArguments(gep.inParams), getReturnType(gep.inParams)); + return getSpecializedClassName(gep.getArguments(), gep.getReturnType()); } public static String getSpecializedClassName(List argumentTypes, TargetType returnType) { diff --git a/src/main/java/de/dhbwstuttgart/syntaxtree/factory/ASTFactory.java b/src/main/java/de/dhbwstuttgart/syntaxtree/factory/ASTFactory.java index 63767c4d..520097d8 100644 --- a/src/main/java/de/dhbwstuttgart/syntaxtree/factory/ASTFactory.java +++ b/src/main/java/de/dhbwstuttgart/syntaxtree/factory/ASTFactory.java @@ -34,6 +34,7 @@ public class ASTFactory { private static final HashMap cache = new HashMap<>(); public static ClassOrInterface createClass(java.lang.Class jreClass) { + System.out.println(jreClass); if (cache.containsKey(jreClass)) return cache.get(jreClass); @@ -173,6 +174,7 @@ public class ASTFactory { superClass = (RefType) createType(java.lang.Object.class); } List implementedInterfaces = new ArrayList<>(); + System.out.println(jreClass); for (Type jreInterface : jreClass.getGenericInterfaces()) { implementedInterfaces.add((RefType) createType(jreInterface)); } diff --git a/src/main/java/de/dhbwstuttgart/target/generate/GenerateGenerics.java b/src/main/java/de/dhbwstuttgart/target/generate/GenerateGenerics.java index 403ccdb8..a0d7c5c7 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/GenerateGenerics.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/GenerateGenerics.java @@ -652,14 +652,12 @@ public abstract class GenerateGenerics { } 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; - } + for (var p1 : new HashSet<>(result)) { + if (p1 instanceof PairLT ptph && ptph.left.resolve().equals(ptph.right.resolve())) + result.remove(p1); // TODO This is a bit strange + } + for (var tph : usedTphs) { if (classGenerics == null || classGenerics.stream().noneMatch((pair) -> pair.left.equals(tph))) addToPairs(result, new PairEQ(tph, ASTToTargetAST.OBJECT)); } From 7785c2d0aad61da34e4d81f597307d8779e5ad73 Mon Sep 17 00:00:00 2001 From: Daniel Holle Date: Mon, 5 Aug 2024 11:54:13 +0200 Subject: [PATCH 13/13] Fix equals method of ClassOrInterface --- .../java/de/dhbwstuttgart/syntaxtree/ClassOrInterface.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/dhbwstuttgart/syntaxtree/ClassOrInterface.java b/src/main/java/de/dhbwstuttgart/syntaxtree/ClassOrInterface.java index 71240ae9..834fabb7 100644 --- a/src/main/java/de/dhbwstuttgart/syntaxtree/ClassOrInterface.java +++ b/src/main/java/de/dhbwstuttgart/syntaxtree/ClassOrInterface.java @@ -188,9 +188,8 @@ public class ClassOrInterface extends SyntaxTreeNode implements TypeScope { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ClassOrInterface that = (ClassOrInterface) o; - return Objects.equals(name, that.name); + if (!(o instanceof ClassOrInterface other)) return false; + return Objects.equals(name, other.name); } @Override