Add several tests for metaprogramming

This commit is contained in:
Alexey Andreev 2016-02-29 23:07:20 +03:00
parent 3e562aa08a
commit 518e13cf07
10 changed files with 472 additions and 5 deletions

View File

@ -2,7 +2,7 @@
<configuration default="false" name="run-tests" type="JUnit" factoryName="JUnit"> <configuration default="false" name="run-tests" type="JUnit" factoryName="JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea"> <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea">
<pattern> <pattern>
<option name="PATTERN" value="org.teavm.classlib.java.lang.*" /> <option name="PATTERN" value="org.teavm.*" />
<option name="ENABLED" value="true" /> <option name="ENABLED" value="true" />
</pattern> </pattern>
</extension> </extension>

View File

@ -28,17 +28,25 @@ import org.teavm.metaprogramming.impl.reflect.ReflectClassImpl;
import org.teavm.metaprogramming.impl.reflect.ReflectContext; import org.teavm.metaprogramming.impl.reflect.ReflectContext;
import org.teavm.metaprogramming.impl.reflect.ReflectFieldImpl; import org.teavm.metaprogramming.impl.reflect.ReflectFieldImpl;
import org.teavm.metaprogramming.impl.reflect.ReflectMethodImpl; import org.teavm.metaprogramming.impl.reflect.ReflectMethodImpl;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.Instruction;
import org.teavm.model.InstructionLocation; import org.teavm.model.InstructionLocation;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
import org.teavm.model.Variable; import org.teavm.model.Variable;
import org.teavm.model.instructions.DoubleConstantInstruction;
import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.FloatConstantInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.LongConstantInstruction;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.util.InstructionTransitionExtractor;
public final class MetaprogrammingImpl { public final class MetaprogrammingImpl {
static ClassLoader classLoader; static ClassLoader classLoader;
@ -217,6 +225,62 @@ public final class MetaprogrammingImpl {
agent.submitClass(cls); agent.submitClass(cls);
} }
public static void close() {
InstructionTransitionExtractor transitionExtractor = new InstructionTransitionExtractor();
BasicBlock block = generator.currentBlock();
Instruction lastInstruction = block.getLastInstruction();
if (lastInstruction != null) {
lastInstruction.acceptVisitor(transitionExtractor);
}
if (transitionExtractor.getTargets() != null) {
return;
}
Variable var;
if (returnType instanceof ValueType.Void) {
var = null;
} else if (returnType instanceof ValueType.Primitive) {
var = generator.program.createVariable();
switch (((ValueType.Primitive) returnType).getKind()) {
case BOOLEAN:
case BYTE:
case SHORT:
case CHARACTER:
case INTEGER: {
IntegerConstantInstruction constantInsn = new IntegerConstantInstruction();
constantInsn.setReceiver(var);
generator.add(constantInsn);
break;
}
case LONG: {
LongConstantInstruction constantInsn = new LongConstantInstruction();
constantInsn.setReceiver(var);
generator.add(constantInsn);
break;
}
case FLOAT: {
FloatConstantInstruction constantInsn = new FloatConstantInstruction();
constantInsn.setReceiver(var);
generator.add(constantInsn);
break;
}
case DOUBLE: {
DoubleConstantInstruction constantInsn = new DoubleConstantInstruction();
constantInsn.setReceiver(var);
generator.add(constantInsn);
break;
}
}
} else {
NullConstantInstruction constantInsn = new NullConstantInstruction();
var = generator.program.createVariable();
constantInsn.setReceiver(var);
generator.add(constantInsn);
}
returnValue(var);
}
private static void unsupported() { private static void unsupported() {
throw new UnsupportedOperationException("This operation is only supported from TeaVM compile-time " throw new UnsupportedOperationException("This operation is only supported from TeaVM compile-time "
+ "environment"); + "environment");

View File

@ -146,6 +146,7 @@ class UsageGenerator {
try { try {
proxyMethod.invoke(null, proxyArgs); proxyMethod.invoke(null, proxyArgs);
MetaprogrammingImpl.close();
Program program = MetaprogrammingImpl.generator.getProgram(); Program program = MetaprogrammingImpl.generator.getProgram();
//new BoxingEliminator().optimize(program); //new BoxingEliminator().optimize(program);

View File

@ -18,5 +18,5 @@ package org.teavm.metaprogramming;
import org.teavm.metaprogramming.reflect.ReflectMethod; import org.teavm.metaprogramming.reflect.ReflectMethod;
public interface InvocationHandler<T> { public interface InvocationHandler<T> {
void invoke(Value<T> proxy, ReflectMethod method, Value<Object>[] args); Computation<?> invoke(Value<T> proxy, ReflectMethod method, Value<Object>[] args);
} }

View File

@ -71,8 +71,7 @@ public final class Metaprogramming {
} }
public static <T> ReflectClass<T[]> arrayClass(ReflectClass<T> componentType) { public static <T> ReflectClass<T[]> arrayClass(ReflectClass<T> componentType) {
unsupported(); throw new UnsupportedOperationException();
return null;
} }
public static ReflectClass<?> createClass(byte[] bytecode) { public static ReflectClass<?> createClass(byte[] bytecode) {

View File

@ -0,0 +1,27 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.metaprogramming.test;
import static org.teavm.metaprogramming.Metaprogramming.emit;
import org.teavm.metaprogramming.CompileTime;
import org.teavm.metaprogramming.Value;
@CompileTime
public class MetaprogrammingGenerator2 {
public Value<String> addParentheses(String value) {
return emit(() -> "[" + value + "]");
}
}

View File

@ -16,7 +16,13 @@
package org.teavm.metaprogramming.test; package org.teavm.metaprogramming.test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.teavm.metaprogramming.Metaprogramming.arrayClass;
import static org.teavm.metaprogramming.Metaprogramming.emit;
import static org.teavm.metaprogramming.Metaprogramming.exit; import static org.teavm.metaprogramming.Metaprogramming.exit;
import static org.teavm.metaprogramming.Metaprogramming.findClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.junit.SkipJVM; import org.teavm.junit.SkipJVM;
@ -25,12 +31,15 @@ import org.teavm.metaprogramming.CompileTime;
import org.teavm.metaprogramming.Meta; import org.teavm.metaprogramming.Meta;
import org.teavm.metaprogramming.ReflectClass; import org.teavm.metaprogramming.ReflectClass;
import org.teavm.metaprogramming.Value; import org.teavm.metaprogramming.Value;
import org.teavm.metaprogramming.reflect.ReflectField;
import org.teavm.metaprogramming.reflect.ReflectMethod;
import org.teavm.metaprogramming.test.subpackage.MetaprogrammingGenerator;
@CompileTime @CompileTime
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
@SkipJVM
public class MetaprogrammingTest { public class MetaprogrammingTest {
@Test @Test
@SkipJVM
public void works() { public void works() {
assertEquals("java.lang.Object".length() + 2, classNameLength(Object.class, 2)); assertEquals("java.lang.Object".length() + 2, classNameLength(Object.class, 2));
assertEquals("java.lang.Integer".length() + 3, classNameLength(Integer.valueOf(5).getClass(), 3)); assertEquals("java.lang.Integer".length() + 3, classNameLength(Integer.valueOf(5).getClass(), 3));
@ -42,4 +51,299 @@ public class MetaprogrammingTest {
int length = cls.getName().length(); int length = cls.getName().length();
exit(() -> length + add.get()); exit(() -> length + add.get());
} }
@Test
public void getsField() {
Context ctx = new Context();
ctx.a = 2;
ctx.b = 3;
assertEquals(2, getField(ctx.getClass(), ctx));
}
@Meta
private static native Object getField(Class<?> cls, Object obj);
private static void getField(ReflectClass<Object> cls, Value<Object> obj) {
ReflectField field = cls.getField("a");
exit(() -> field.get(obj));
}
@Test
public void setsField() {
Context ctx = new Context();
setField(ctx.getClass(), ctx, 3);
assertEquals(3, ctx.a);
}
@Meta
private static native void setField(Class<?> cls, Object obj, Object value);
private static void setField(ReflectClass<Object> cls, Value<Object> obj, Value<Object> value) {
ReflectField field = cls.getField("a");
emit(() -> field.set(obj, value));
}
@Test
public void methodInvoked() {
assertEquals("debug!", callDebug(A.class, new A()));
assertEquals("missing", callDebug(B.class, new B()));
assertEquals("missing", callDebug(A.class, new A(), "foo", 23));
assertEquals("debug!foo:23", callDebug(B.class, new B(), "foo", 23));
}
@Meta
private static native String callDebug(Class<?> cls, Object obj);
private static void callDebug(ReflectClass<?> cls, Value<Object> obj) {
ReflectMethod method = cls.getMethod("debug");
if (method == null) {
exit(() -> "missing");
} else {
exit(() -> method.invoke(obj.get()));
}
}
@Meta
private static native String callDebug(Class<?> cls, Object obj, String a, int b);
private static void callDebug(ReflectClass<?> cls, Value<Object> obj, Value<String> a, Value<Integer> b) {
ReflectClass<String> stringClass = findClass(String.class);
ReflectClass<Integer> intClass = findClass(int.class);
ReflectMethod method = cls.getMethod("debug", stringClass, intClass);
if (method == null) {
exit(() -> "missing");
} else {
exit(() -> method.invoke(obj.get(), a.get(), b.get()));
}
}
@Test
public void constructorInvoked() {
assertEquals(C.class.getName(), callConstructor(C.class).getClass().getName());
assertNull(callConstructor(D.class));
assertNull(callConstructor(C.class, "foo", 23));
D instance = (D) callConstructor(D.class, "foo", 23);
assertEquals(D.class.getName(), instance.getClass().getName());
assertEquals("foo", instance.a);
assertEquals(23, instance.b);
}
@Meta
private static native Object callConstructor(Class<?> type);
private static void callConstructor(ReflectClass<?> type) {
ReflectMethod ctor = type.getMethod("<init>");
if (ctor != null) {
exit(() -> ctor.construct());
} else {
exit(() -> null);
}
}
@Meta
private static native Object callConstructor(Class<?> type, String a, int b);
private static void callConstructor(ReflectClass<?> type, Value<String> a, Value<Integer> b) {
ReflectClass<String> stringClass = findClass(String.class);
ReflectClass<Integer> intClass = findClass(int.class);
ReflectMethod ctor = type.getMethod("<init>", stringClass, intClass);
if (ctor != null) {
exit(() -> ctor.construct(a, b));
} else {
exit(() -> null);
}
}
@Test
public void capturesArray() {
assertEquals("23:foo", captureArray(23, "foo"));
}
@Meta
private static native String captureArray(int a, String b);
private static void captureArray(Value<Integer> a, Value<String> b) {
Value<?>[] array = { a, emit(() -> ":"), b };
exit(() -> String.valueOf(array[0].get()) + array[1].get() + array[2].get());
}
@Test
public void isInstanceWorks() {
assertTrue(isInstance("foo", String.class));
assertFalse(isInstance(23, String.class));
}
@Meta
private static native boolean isInstance(Object obj, Class<?> type);
private static void isInstance(Value<Object> obj, ReflectClass<?> type) {
exit(() -> type.isInstance(obj.get()));
}
@Test
public void capturesNull() {
assertEquals("foo:", captureArgument("foo"));
}
@Meta
private static native String captureArgument(String a);
private static void captureArgument(Value<String> a) {
exit(() -> a.get() + ":");
}
@Test
public void annotationsWork() {
assertEquals(""
+ "foo:23:Object\n"
+ "foo=!:42:String:int\n"
+ "f=!:23\n",
readAnnotations(WithAnnotations.class, new WithAnnotations()));
}
@Meta
private static native String readAnnotations(Class<?> cls, Object obj);
private static void readAnnotations(ReflectClass<Object> cls, Value<Object> obj) {
StringBuilder sb = new StringBuilder();
sb.append(describeAnnotation(cls.getAnnotation(TestAnnotation.class))).append('\n');
for (ReflectMethod method : cls.getDeclaredMethods()) {
TestAnnotation annot = method.getAnnotation(TestAnnotation.class);
if (annot == null) {
continue;
}
sb.append(method.getName()).append('=').append(describeAnnotation(annot)).append('\n');
}
for (ReflectField field : cls.getDeclaredFields()) {
TestAnnotation annot = field.getAnnotation(TestAnnotation.class);
if (annot == null) {
continue;
}
sb.append(field.getName()).append('=').append(describeAnnotation(annot)).append('\n');
}
String result = sb.toString();
exit(() -> result);
}
private static String describeAnnotation(TestAnnotation annot) {
StringBuilder sb = new StringBuilder();
sb.append(annot.a()).append(':').append(annot.b());
for (Class<?> cls : annot.c()) {
sb.append(':').append(cls.getSimpleName());
}
return sb.toString();
}
@Test
public void compileTimeAnnotationRespectsPackage() {
assertEquals("(foo)", compileTimePackage(true));
}
@Meta
private static native String compileTimePackage(boolean ignoreMe);
private static void compileTimePackage(Value<Boolean> ignoreMe) {
Value<String> result = new MetaprogrammingGenerator().addParentheses("foo");
exit(() -> result.get());
}
@Test
public void compileTimeAnnotationRespectsClass() {
assertEquals("[foo]", compileTimeClass(true));
}
@Meta
private static native String compileTimeClass(boolean ignoreMe);
private static void compileTimeClass(Value<Boolean> ignoreMe) {
Value<String> result = new MetaprogrammingGenerator2().addParentheses("foo");
exit(() -> result.get());
}
@Test
public void compileTimeAnnotationRespectsNestedClass() {
assertEquals("{foo}", compileTimeNestedClass(true));
}
@Meta
private static native String compileTimeNestedClass(boolean ignoreMe);
private static void compileTimeNestedClass(Value<Boolean> ignoreMe) {
Value<String> result = new MetaprogrammingGenerator3().addParentheses("foo");
exit(() -> result.get());
}
@Test
public void emitsClassLiteralFromReflectClass() {
assertEquals(String[].class.getName(), emitClassLiteral(String.class));
}
@Meta
private static native String emitClassLiteral(Class<?> cls);
private static void emitClassLiteral(ReflectClass<?> cls) {
ReflectClass<?> arrayClass = arrayClass(cls);
exit(() -> arrayClass.asJavaClass().getName());
}
@Test
public void createsArrayViaReflection() {
Object array = createArrayOfType(String.class, 10);
assertEquals(String[].class, array.getClass());
assertEquals(10, ((String[]) array).length);
}
@Meta
private static native Object createArrayOfType(Class<?> cls, int size);
private static void createArrayOfType(ReflectClass<?> cls, Value<Integer> size) {
exit(() -> cls.createArray(size.get()));
}
@Test
public void getsArrayElementViaReflection() {
assertEquals("foo", getArrayElement(String[].class, new String[] { "foo" }, 0));
}
@Meta
private static native Object getArrayElement(Class<?> type, Object array, int index);
private static void getArrayElement(ReflectClass<?> type, Value<Object> array, Value<Integer> index) {
exit(() -> type.getArrayElement(array.get(), index.get()));
}
static class Context {
public int a;
public int b;
}
class A {
public String debug() {
return "debug!";
}
}
class B {
public String debug(String a, int b) {
return "debug!" + a + ":" + b;
}
}
static class C {
public C() {
}
}
static class D {
String a;
int b;
public D(String a, int b) {
this.a = a;
this.b = b;
}
}
@TestAnnotation(a = "foo", c = Object.class)
static class WithAnnotations {
@TestAnnotation(c = {})
int f;
@TestAnnotation(b = 42, c = { String.class, int.class })
int foo() {
return 0;
}
}
static class MetaprogrammingGenerator3 {
public Value<String> addParentheses(String value) {
return emit(() -> "{" + value + "}");
}
}
} }

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.metaprogramming.test;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String a() default "!";
int b() default 23;
Class<?>[] c();
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.metaprogramming.test.subpackage;
import static org.teavm.metaprogramming.Metaprogramming.emit;
import org.teavm.metaprogramming.Value;
public class MetaprogrammingGenerator {
public Value<String> addParentheses(String value) {
return emit(() -> "(" + value + ")");
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@CompileTime
package org.teavm.metaprogramming.test.subpackage;
import org.teavm.metaprogramming.CompileTime;