diff --git a/JavaIntegration/src/main/java/org/openzen/zencode/java/impl/conversion/JavaRuntimeTypeConverterImpl.java b/JavaIntegration/src/main/java/org/openzen/zencode/java/impl/conversion/JavaRuntimeTypeConverterImpl.java index 795fd2bf..2fb601ab 100644 --- a/JavaIntegration/src/main/java/org/openzen/zencode/java/impl/conversion/JavaRuntimeTypeConverterImpl.java +++ b/JavaIntegration/src/main/java/org/openzen/zencode/java/impl/conversion/JavaRuntimeTypeConverterImpl.java @@ -196,6 +196,12 @@ private TypeID loadClass(TypeVariableContext context, Class type) { // ((JavaRuntimeClass) definition).translateTypeParameters(context); //} + // Type variables not in context mean a raw type used from outside the class, + // e.g. a parameter declared as `Foo` instead of `Foo`. Inside Foo itself, T is in context. + if (!context.containsAll(type.getTypeParameters())) { + return loadRawClass(context, type, definition); + } + final List typeParameters = new ArrayList<>(); for (TypeVariable> typeParameter : type.getTypeParameters()) { typeParameters.add(new GenericTypeID(context.get(typeParameter))); @@ -204,6 +210,36 @@ private TypeID loadClass(TypeVariableContext context, Class type) { return DefinitionTypeID.create(definition, typeParameters.toArray(TypeID.NONE)); } + // Raw types are treated the way Java erases them: each type argument becomes "out ". + // Like JavaRuntimeClass.translateTypeParameters, the type variables are put into the context before their bounds + // are resolved, so a bound leading back to a class being loaded (e.g. T extends Node) is detected and becomes Object. + private TypeID loadRawClass(TypeVariableContext context, Class type, TypeSymbol definition) { + final TypeVariable>[] javaTypeParameters = type.getTypeParameters(); + final TypeVariableContext rawContext = context.createChildContext(); + for (TypeVariable> typeVariable : javaTypeParameters) { + rawContext.put(typeVariable, new TypeParameter(CodePosition.NATIVE, typeVariable.getName())); + } + + final TypeID[] typeArguments = new TypeID[javaTypeParameters.length]; + for (int i = 0; i < javaTypeParameters.length; i++) { + Class bound = erase(javaTypeParameters[i].getBounds()[0]); + boolean boundIsLoading = bound.getTypeParameters().length > 0 && rawContext.containsAll(bound.getTypeParameters()); + typeArguments[i] = new WildcardOutTypeID(loadClass(rawContext, boundIsLoading ? Object.class : bound)); + } + + return DefinitionTypeID.create(definition, typeArguments); + } + + private static Class erase(Type type) { + if (type instanceof Class) + return (Class) type; + if (type instanceof ParameterizedType) + return erase(((ParameterizedType) type).getRawType()); + if (type instanceof TypeVariable) + return erase(((TypeVariable) type).getBounds()[0]); + return Object.class; + } + private TypeID loadGenericArray(TypeVariableContext context, GenericArrayType type) { final JavaAnnotatedType componentType = JavaAnnotatedType.of(type.getGenericComponentType()); final TypeID baseType = this.loadType(context, componentType); diff --git a/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeDescriptorVisitor.java b/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeDescriptorVisitor.java index 2c274fdf..5102ae0c 100644 --- a/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeDescriptorVisitor.java +++ b/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeDescriptorVisitor.java @@ -159,7 +159,7 @@ public String visitWildcardIn(WildcardInTypeID type) { @Override public String visitWildcardOut(WildcardOutTypeID type) { - return type.accept(this); + return type.upperBound.accept(this); } @Override diff --git a/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeInternalNameVisitor.java b/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeInternalNameVisitor.java index bd1042db..dd0de5a9 100644 --- a/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeInternalNameVisitor.java +++ b/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaTypeInternalNameVisitor.java @@ -143,7 +143,7 @@ public String visitWildcardIn(WildcardInTypeID type) { @Override public String visitWildcardOut(WildcardOutTypeID type) { - return "java/lang/Object"; + return type.upperBound.accept(this); } @Override diff --git a/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/RawBoundedTypeInHeaderTest.java b/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/RawBoundedTypeInHeaderTest.java new file mode 100644 index 00000000..f4e3b040 --- /dev/null +++ b/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/RawBoundedTypeInHeaderTest.java @@ -0,0 +1,89 @@ +package org.openzen.zenscript.scriptingexample.tests.actual_test.java_native; + +import org.junit.jupiter.api.Test; +import org.openzen.zencode.java.ZenCodeGlobals; +import org.openzen.zencode.java.ZenCodeType; +import org.openzen.zenscript.scriptingexample.tests.helpers.ZenCodeTest; + +import java.util.List; + +public class RawBoundedTypeInHeaderTest extends ZenCodeTest { + + @Override + public List> getRequiredClasses() { + final List> requiredClasses = super.getRequiredClasses(); + requiredClasses.add(Parent.class); + requiredClasses.add(Child.class); + requiredClasses.add(BoundedGenClass.class); + requiredClasses.add(TestGlobals.class); + return requiredClasses; + } + + @Test + void rawBoundedTypeAcceptsParameterizedValue() { + addScript("acceptRaw(new test_module.java_native.BoundedGenClass(new test_module.java_native.Child()));"); + + executeEngine(); + + logger.assertNoErrors(); + logger.assertNoWarnings(); + } + + @Test + void rawBoundedTypeExposesBoundMembers() { + addScript("println(makeRaw().unwrap().greet());"); + + executeEngine(); + + logger.assertNoErrors(); + logger.printlnOutputs().assertLinesInOrder("Hi from child"); + } + + @ZenCodeType.Name("test_module.java_native.RawBoundedTestGlobals") + public static final class TestGlobals { + @ZenCodeGlobals.Global + public static void acceptRaw(BoundedGenClass clazz) { + } + + @ZenCodeGlobals.Global + public static BoundedGenClass makeRaw() { + return new BoundedGenClass<>(new Child()); + } + } + + @ZenCodeType.Name("test_module.java_native.Parent") + public static class Parent { + @ZenCodeType.Method + public String greet() { + return "Hi from parent"; + } + } + + @ZenCodeType.Name("test_module.java_native.Child") + public static class Child extends Parent { + @ZenCodeType.Constructor + public Child() { + } + + @Override + @ZenCodeType.Method + public String greet() { + return "Hi from child"; + } + } + + @ZenCodeType.Name("test_module.java_native.BoundedGenClass") + public static final class BoundedGenClass { + private final T value; + + @ZenCodeType.Constructor + public BoundedGenClass(T value) { + this.value = value; + } + + @ZenCodeType.Method + public T unwrap() { + return value; + } + } +} diff --git a/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/RawRecursiveBoundTypeInHeaderTest.java b/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/RawRecursiveBoundTypeInHeaderTest.java new file mode 100644 index 00000000..cb42689b --- /dev/null +++ b/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/RawRecursiveBoundTypeInHeaderTest.java @@ -0,0 +1,52 @@ +package org.openzen.zenscript.scriptingexample.tests.actual_test.java_native; + +import org.junit.jupiter.api.Test; +import org.openzen.zencode.java.ZenCodeGlobals; +import org.openzen.zencode.java.ZenCodeType; +import org.openzen.zenscript.scriptingexample.tests.helpers.ZenCodeTest; + +import java.util.List; + +/** + * Raw types whose type parameter bound leads back to the type itself (T extends SelfBoundedGenClass). + * Without the guard in JavaRuntimeTypeConverterImpl.loadRawClass, the bound would either recurse or leak the + * guard's placeholder type parameter, so the round trip below would no longer type-check. + * No other test registers these types, so a failure here points at that guard. + */ +public class RawRecursiveBoundTypeInHeaderTest extends ZenCodeTest { + + @Override + public List> getRequiredClasses() { + final List> requiredClasses = super.getRequiredClasses(); + requiredClasses.add(SelfBoundedGenClass.class); + requiredClasses.add(TestGlobals.class); + return requiredClasses; + } + + @Test + void rawSelfBoundedTypeRoundTrips() { + addScript("println(acceptRawSelfBounded(makeRawSelfBounded()));"); + + executeEngine(); + + logger.assertNoErrors(); + logger.printlnOutputs().assertLinesInOrder("self bounded"); + } + + @ZenCodeType.Name("test_module.java_native.RawRecursiveBoundTestGlobals") + public static final class TestGlobals { + @ZenCodeGlobals.Global + public static SelfBoundedGenClass makeRawSelfBounded() { + return new SelfBoundedGenClass<>(); + } + + @ZenCodeGlobals.Global + public static String acceptRawSelfBounded(SelfBoundedGenClass clazz) { + return "self bounded"; + } + } + + @ZenCodeType.Name("test_module.java_native.SelfBoundedGenClass") + public static final class SelfBoundedGenClass> { + } +}