Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>`. Inside Foo itself, T is in context.
if (!context.containsAll(type.getTypeParameters())) {
return loadRawClass(context, type, definition);
}

final List<TypeID> typeParameters = new ArrayList<>();
for (TypeVariable<? extends Class<?>> typeParameter : type.getTypeParameters()) {
typeParameters.add(new GenericTypeID(context.get(typeParameter)));
Expand All @@ -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 <erasure of its first bound>".
// 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<T>) is detected and becomes Object.
private TypeID loadRawClass(TypeVariableContext context, Class<?> type, TypeSymbol definition) {
final TypeVariable<? extends Class<?>>[] javaTypeParameters = type.getTypeParameters();
final TypeVariableContext rawContext = context.createChildContext();
for (TypeVariable<? extends Class<?>> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Class<?>> getRequiredClasses() {
final List<Class<?>> 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<test_module.java_native.Child>(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<T extends Parent> {
private final T value;

@ZenCodeType.Constructor
public BoundedGenClass(T value) {
this.value = value;
}

@ZenCodeType.Method
public T unwrap() {
return value;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<T>).
* 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<Class<?>> getRequiredClasses() {
final List<Class<?>> 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<T extends SelfBoundedGenClass<T>> {
}
}
Loading