Make Function Types implement others to allow Subtyping, fixes #337

This commit is contained in:
Daniel Holle 2024-06-07 12:03:16 +02:00
parent 12bb613eb0
commit 7e6aeaf728
7 changed files with 110 additions and 19 deletions

View File

@ -54,8 +54,8 @@ http://maven.apache.org/maven-v4_0_0.xsd">
<version>3.11.0</version>
<configuration>
<compilerArgs>--enable-preview</compilerArgs>
<source>21</source>
<target>21</target>
<source>22</source>
<target>22</target>
</configuration>
</plugin>
<plugin>

View File

@ -0,0 +1,10 @@
import java.lang.Integer;
import java.lang.Number;
import java.lang.Object;
public class Bug337 {
public void main() {
Fun1$$<Object, Integer> fun1 = x -> x.hashCode() + 1;
Fun1$$<Number, Number> fun2 = fun1;
}
}

View File

@ -33,6 +33,23 @@ public class FunNGenerator {
public static class GenericParameters {
int start;
public List<TargetType> parameters = new ArrayList<>();
final String descriptor;
final List<TargetType> inParams;
public GenericParameters(List<TargetType> 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<TargetType> 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<TargetType> argumentTypes, TargetType returnType, GenericParameters gep) {
List<TargetType> parameters = Stream
.concat(argumentTypes.stream(), Stream.of(returnType))
.toList();
public static byte[] generateSpecializedBytecode(GenericParameters gep, List<String> 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<TargetType> argumentTypes, TargetType returnType) {
return String.format("Fun%d$$%s%s",
argumentTypes.size(),

View File

@ -783,6 +783,7 @@ public class JavaTXCompiler {
});
}
generatedGenerics.put(sf, converter.javaGenerics());
converter.generateFunNTypes();
return generatedClasses;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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());
}