Simple metaprogramming test passes

This commit is contained in:
Alexey Andreev 2016-02-27 18:46:27 +03:00
parent 4819eee3ef
commit 3e562aa08a
20 changed files with 197 additions and 125 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Eclipse" /> <option name="DEFAULT_COMPILER" value="Javac" />
<excludeFromCompile> <excludeFromCompile>
<directory url="file://$PROJECT_DIR$/tools/maven/webapp/src/main/resources/archetype-resources" includeSubdirectories="true" /> <directory url="file://$PROJECT_DIR$/tools/maven/webapp/src/main/resources/archetype-resources" includeSubdirectories="true" />
</excludeFromCompile> </excludeFromCompile>

View File

@ -17,7 +17,6 @@ package org.teavm.dependency;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -55,10 +54,6 @@ import org.teavm.model.util.ProgramUtils;
import org.teavm.optimization.UnreachableBasicBlockEliminator; import org.teavm.optimization.UnreachableBasicBlockEliminator;
import org.teavm.parsing.Parser; import org.teavm.parsing.Parser;
/**
*
* @author Alexey Andreev
*/
public class DependencyChecker implements DependencyInfo { public class DependencyChecker implements DependencyInfo {
static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true"); static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true");
private int classNameSuffix; private int classNameSuffix;
@ -73,14 +68,13 @@ public class DependencyChecker implements DependencyInfo {
private ServiceRepository services; private ServiceRepository services;
private Queue<Runnable> tasks = new ArrayDeque<>(); private Queue<Runnable> tasks = new ArrayDeque<>();
List<DependencyType> types = new ArrayList<>(); List<DependencyType> types = new ArrayList<>();
Map<String, DependencyType> typeMap = new HashMap<>(); private Map<String, DependencyType> typeMap = new HashMap<>();
private DependencyCheckerInterruptor interruptor; private DependencyCheckerInterruptor interruptor;
private boolean interrupted; private boolean interrupted;
private Diagnostics diagnostics; private Diagnostics diagnostics;
DefaultCallGraph callGraph = new DefaultCallGraph(); DefaultCallGraph callGraph = new DefaultCallGraph();
private DependencyAgent agent; private DependencyAgent agent;
List<DependencyNode> nodes = new ArrayList<>(); List<DependencyNode> nodes = new ArrayList<>();
List<BitSet> typeBitSets = new ArrayList<>();
Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>(); Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>();
private boolean completing; private boolean completing;
@ -133,14 +127,13 @@ public class DependencyChecker implements DependencyInfo {
if (type == null) { if (type == null) {
type = new DependencyType(this, name, types.size()); type = new DependencyType(this, name, types.size());
types.add(type); types.add(type);
typeBitSets.add(new BitSet(nodes.size()));
typeMap.put(name, type); typeMap.put(name, type);
} }
return type; return type;
} }
public DependencyNode createNode() { public DependencyNode createNode() {
DependencyNode node = new DependencyNode(this, nodes.size()); DependencyNode node = new DependencyNode(this);
nodes.add(node); nodes.add(node);
return node; return node;
} }

View File

@ -18,10 +18,6 @@ package org.teavm.dependency;
import java.util.*; import java.util.*;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public class DependencyNode implements ValueDependencyInfo { public class DependencyNode implements ValueDependencyInfo {
private DependencyChecker dependencyChecker; private DependencyChecker dependencyChecker;
private List<DependencyConsumer> followers; private List<DependencyConsumer> followers;
@ -32,17 +28,15 @@ public class DependencyNode implements ValueDependencyInfo {
private DependencyNode arrayItemNode; private DependencyNode arrayItemNode;
private DependencyNode classValueNode; private DependencyNode classValueNode;
private int degree; private int degree;
int index;
boolean locked; boolean locked;
MethodReference method; MethodReference method;
DependencyNode(DependencyChecker dependencyChecker, int index) { DependencyNode(DependencyChecker dependencyChecker) {
this(dependencyChecker, index, 0); this(dependencyChecker, 0);
} }
DependencyNode(DependencyChecker dependencyChecker, int index, int degree) { private DependencyNode(DependencyChecker dependencyChecker, int degree) {
this.dependencyChecker = dependencyChecker; this.dependencyChecker = dependencyChecker;
this.index = index;
this.degree = degree; this.degree = degree;
} }
@ -192,7 +186,7 @@ public class DependencyNode implements ValueDependencyInfo {
@Override @Override
public DependencyNode getArrayItem() { public DependencyNode getArrayItem() {
if (arrayItemNode == null) { if (arrayItemNode == null) {
arrayItemNode = new DependencyNode(dependencyChecker, dependencyChecker.nodes.size(), degree + 1); arrayItemNode = new DependencyNode(dependencyChecker, degree + 1);
dependencyChecker.nodes.add(arrayItemNode); dependencyChecker.nodes.add(arrayItemNode);
if (DependencyChecker.shouldLog) { if (DependencyChecker.shouldLog) {
arrayItemNode.tag = tag + "["; arrayItemNode.tag = tag + "[";
@ -204,7 +198,7 @@ public class DependencyNode implements ValueDependencyInfo {
public DependencyNode getClassValueNode() { public DependencyNode getClassValueNode() {
if (classValueNode == null) { if (classValueNode == null) {
classValueNode = new DependencyNode(dependencyChecker, dependencyChecker.nodes.size(), degree); classValueNode = new DependencyNode(dependencyChecker, degree);
dependencyChecker.nodes.add(classValueNode); dependencyChecker.nodes.add(classValueNode);
if (DependencyChecker.shouldLog) { if (DependencyChecker.shouldLog) {
classValueNode.tag = tag + "@"; classValueNode.tag = tag + "@";

View File

@ -15,12 +15,8 @@
*/ */
package org.teavm.dependency; package org.teavm.dependency;
/**
*
* @author Alexey Andreev
*/
class DependencyNodeToNodeTransition implements DependencyConsumer { class DependencyNodeToNodeTransition implements DependencyConsumer {
DependencyNode source; private DependencyNode source;
DependencyNode destination; DependencyNode destination;
private DependencyTypeFilter filter; private DependencyTypeFilter filter;

View File

@ -20,10 +20,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
/**
*
* @author Alexey Andreev
*/
public class AccumulationDiagnostics implements Diagnostics, ProblemProvider { public class AccumulationDiagnostics implements Diagnostics, ProblemProvider {
private List<Problem> problems = new ArrayList<>(); private List<Problem> problems = new ArrayList<>();
private List<Problem> readonlyProblems = Collections.unmodifiableList(problems); private List<Problem> readonlyProblems = Collections.unmodifiableList(problems);

View File

@ -17,10 +17,6 @@ package org.teavm.diagnostics;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
/**
*
* @author Alexey Andreev
*/
public interface Diagnostics { public interface Diagnostics {
void error(CallLocation location, String error, Object... params); void error(CallLocation location, String error, Object... params);

View File

@ -24,6 +24,7 @@ import org.teavm.dependency.MethodDependency;
import org.teavm.dependency.MethodDependencyInfo; import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.metaprogramming.impl.model.MethodDescriber; import org.teavm.metaprogramming.impl.model.MethodDescriber;
import org.teavm.metaprogramming.impl.model.MethodModel; import org.teavm.metaprogramming.impl.model.MethodModel;
import org.teavm.metaprogramming.impl.reflect.ReflectContext;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
@ -40,6 +41,11 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene
public void started(DependencyAgent agent) { public void started(DependencyAgent agent) {
proxyClassLoader = new MetaprogrammingClassLoader(agent.getClassLoader()); proxyClassLoader = new MetaprogrammingClassLoader(agent.getClassLoader());
describer = new MethodDescriber(agent.getDiagnostics(), agent.getClassSource()); describer = new MethodDescriber(agent.getDiagnostics(), agent.getClassSource());
MetaprogrammingImpl.classLoader = proxyClassLoader;
MetaprogrammingImpl.classSource = agent.getClassSource();
MetaprogrammingImpl.agent = agent;
MetaprogrammingImpl.reflectContext = new ReflectContext(agent.getClassSource(), proxyClassLoader);
} }
@Override @Override
@ -56,8 +62,9 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene
ProgramEmitter pe = ProgramEmitter.create(model.getMethod().getDescriptor(), agent.getClassSource()); ProgramEmitter pe = ProgramEmitter.create(model.getMethod().getDescriptor(), agent.getClassSource());
ValueEmitter[] paramVars = new ValueEmitter[model.getMetaParameterCount()]; ValueEmitter[] paramVars = new ValueEmitter[model.getMetaParameterCount()];
int offset = model.isStatic() ? 1 : 0;
for (int i = 0; i < paramVars.length; ++i) { for (int i = 0; i < paramVars.length; ++i) {
paramVars[i] = pe.var(i, model.getMetaParameterType(i)); paramVars[i] = pe.var(i + offset, model.getMetaParameterType(i));
} }
if (model.getUsages().size() == 1) { if (model.getUsages().size() == 1) {
@ -91,7 +98,7 @@ public class MetaprogrammingDependencyListener extends AbstractDependencyListene
ValueEmitter[] paramVars) { ValueEmitter[] paramVars) {
MethodDependencyInfo methodDep = agent.getMethod(model.getMethod()); MethodDependencyInfo methodDep = agent.getMethod(model.getMethod());
ValueEmitter paramVar = paramVars[model.getMetaClassParameterIndex()]; ValueEmitter paramVar = paramVars[model.getMetaClassParameterIndex()];
ValueEmitter tag = paramVar.invokeVirtual("getClass", Class.class).invokeVirtual("getName", String.class); ValueEmitter tag = paramVar.invokeVirtual("getName", String.class);
StringChooseEmitter choice = pe.stringChoice(tag); StringChooseEmitter choice = pe.stringChoice(tag);
for (Map.Entry<ValueType, MethodReference> usageEntry : model.getUsages().entrySet()) { for (Map.Entry<ValueType, MethodReference> usageEntry : model.getUsages().entrySet()) {

View File

@ -53,6 +53,7 @@ public final class MetaprogrammingImpl {
private MetaprogrammingImpl() { private MetaprogrammingImpl() {
} }
@SuppressWarnings("WeakerAccess")
public static <T> Value<T> emit(Computation<T> computation) { public static <T> Value<T> emit(Computation<T> computation) {
if (computation instanceof ValueImpl<?>) { if (computation instanceof ValueImpl<?>) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -90,7 +91,8 @@ public final class MetaprogrammingImpl {
return new LazyValueImpl<>(varContext, computation, type, generator.forcedLocation); return new LazyValueImpl<>(varContext, computation, type, generator.forcedLocation);
} }
public static void exit(Value<?> value) { @SuppressWarnings({"WeakerAccess", "SameParameterValue"})
public static void exit(Computation<?> value) {
if (value == null) { if (value == null) {
returnValue(null); returnValue(null);
return; return;
@ -103,19 +105,12 @@ public final class MetaprogrammingImpl {
generator.blockIndex = generator.returnBlockIndex; generator.blockIndex = generator.returnBlockIndex;
returnValue(unbox(generator.getResultVar())); returnValue(unbox(generator.getResultVar()));
} else if (value instanceof ValueImpl) {
ValueImpl<?> valueImpl = (ValueImpl<?>) value;
returnValue(unbox(varContext.emitVariable(valueImpl, new CallLocation(templateMethod,
generator.location))));
} else if (value instanceof LazyValueImpl) {
Variable var = generator.lazy((LazyValueImpl<?>) value);
returnValue(unbox(var));
} else { } else {
throw new IllegalStateException("Unexpected computation type: " + value.getClass().getName()); throw new IllegalStateException("Unexpected computation type: " + value.getClass().getName());
} }
} }
static Variable unbox(Variable var) { private static Variable unbox(Variable var) {
if (returnType instanceof ValueType.Primitive) { if (returnType instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) returnType).getKind()) { switch (((ValueType.Primitive) returnType).getKind()) {
case BOOLEAN: case BOOLEAN:
@ -147,7 +142,7 @@ public final class MetaprogrammingImpl {
return var; return var;
} }
static Variable unbox(Variable var, Class<?> boxed, Class<?> primitive) { private static Variable unbox(Variable var, Class<?> boxed, Class<?> primitive) {
InvokeInstruction insn = new InvokeInstruction(); InvokeInstruction insn = new InvokeInstruction();
insn.setInstance(var); insn.setInstance(var);
insn.setType(InvocationType.VIRTUAL); insn.setType(InvocationType.VIRTUAL);
@ -170,10 +165,12 @@ public final class MetaprogrammingImpl {
unsupported(); unsupported();
} }
@SuppressWarnings("WeakerAccess")
public static ReflectClass<?> findClass(String name) { public static ReflectClass<?> findClass(String name) {
return reflectContext.findClass(name); return reflectContext.findClass(name);
} }
@SuppressWarnings("WeakerAccess")
public static <T> ReflectClass<T> findClass(Class<T> cls) { public static <T> ReflectClass<T> findClass(Class<T> cls) {
return reflectContext.findClass(cls); return reflectContext.findClass(cls);
} }
@ -200,6 +197,7 @@ public final class MetaprogrammingImpl {
return proxy(findClass(type), handler); return proxy(findClass(type), handler);
} }
@SuppressWarnings("WeakerAccess")
public static <T> Value<T> proxy(ReflectClass<T> type, InvocationHandler<T> handler) { public static <T> Value<T> proxy(ReflectClass<T> type, InvocationHandler<T> handler) {
unsupported(); unsupported();
return null; return null;

View File

@ -0,0 +1,37 @@
/*
* 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.impl;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.CallLocation;
import org.teavm.model.Variable;
class TopLevelVariableContext extends VariableContext {
private Diagnostics diagnostics;
TopLevelVariableContext(Diagnostics diagnostics) {
super(null);
this.diagnostics = diagnostics;
}
@Override
public Variable emitVariable(ValueImpl<?> value, CallLocation location) {
if (value.context != this) {
diagnostics.error(location, "Can't get variable from another context");
}
return value.innerValue;
}
}

View File

@ -23,7 +23,6 @@ import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyType;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.metaprogramming.CompileTime; import org.teavm.metaprogramming.CompileTime;
@ -41,16 +40,17 @@ import org.teavm.model.Variable;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
public class UsageGenerator { class UsageGenerator {
private static Map<DependencyAgent, Integer> suffixGenerator = new WeakHashMap<>(); private static Map<DependencyAgent, Integer> suffixGenerator = new WeakHashMap<>();
DependencyAgent agent; private DependencyAgent agent;
MethodModel model; private MethodModel model;
MethodDependency methodDep; private MethodDependency methodDep;
CallLocation location; private CallLocation location;
Diagnostics diagnostics; private Diagnostics diagnostics;
Method proxyMethod; private Method proxyMethod;
private MetaprogrammingClassLoader classLoader; private MetaprogrammingClassLoader classLoader;
private boolean annotationErrorReported; private boolean annotationErrorReported;
private MethodDependency nameDependency;
UsageGenerator(DependencyAgent agent, MethodModel model, MethodDependency methodDep, CallLocation location, UsageGenerator(DependencyAgent agent, MethodModel model, MethodDependency methodDep, CallLocation location,
MetaprogrammingClassLoader classLoader) { MetaprogrammingClassLoader classLoader) {
@ -65,10 +65,6 @@ public class UsageGenerator {
public void installProxyEmitter() { public void installProxyEmitter() {
Diagnostics diagnostics = agent.getDiagnostics(); Diagnostics diagnostics = agent.getDiagnostics();
MethodDependency getClassDep = agent.linkMethod(new MethodReference(Object.class, "getClass", Class.class),
location);
getClassDep.getThrown().connect(methodDep.getThrown());
try { try {
proxyMethod = getJavaMethod(classLoader, model.getMetaMethod()); proxyMethod = getJavaMethod(classLoader, model.getMetaMethod());
proxyMethod.setAccessible(true); proxyMethod.setAccessible(true);
@ -80,20 +76,20 @@ public class UsageGenerator {
return; return;
} }
if (model.getClassParameterIndex() >= 0) { nameDependency = installAdditionalDependencies();
int index = (model.isStatic() ? 0 : 1) + model.getClassParameterIndex();
methodDep.getVariable(index).addConsumer(type -> consumeType(type, getClassDep));
} else {
emitPermutation(null, getClassDep);
}
installAdditionalDependencies(getClassDep); if (model.getClassParameterIndex() >= 0) {
int index = 1 + model.getClassParameterIndex();
methodDep.getVariable(index).getClassValueNode().addConsumer(
type -> emitPermutation(findClass(type.getName())));
} else {
emitPermutation(null);
}
} }
private void installAdditionalDependencies(MethodDependency getClassDep) { private MethodDependency installAdditionalDependencies() {
MethodDependency nameDep = agent.linkMethod(new MethodReference(Class.class, "getName", String.class), MethodDependency nameDep = agent.linkMethod(new MethodReference(Class.class, "getName", String.class),
location); location);
getClassDep.getResult().connect(nameDep.getVariable(0));
nameDep.getThrown().connect(methodDep.getThrown()); nameDep.getThrown().connect(methodDep.getThrown());
nameDep.use(); nameDep.use();
@ -109,13 +105,11 @@ public class UsageGenerator {
nameDep.getResult().connect(hashCodeDep.getVariable(0)); nameDep.getResult().connect(hashCodeDep.getVariable(0));
hashCodeDep.getThrown().connect(methodDep.getThrown()); hashCodeDep.getThrown().connect(methodDep.getThrown());
hashCodeDep.use(); hashCodeDep.use();
return nameDep;
} }
private void consumeType(DependencyType type, MethodDependency getClassDep) { private void emitPermutation(ValueType type) {
emitPermutation(findClass(type.getName()), getClassDep);
}
private void emitPermutation(ValueType type, MethodDependency getClassDep) {
if (!classLoader.isCompileTimeClass(model.getMetaMethod().getClassName()) && !annotationErrorReported) { if (!classLoader.isCompileTimeClass(model.getMetaMethod().getClassName()) && !annotationErrorReported) {
annotationErrorReported = true; annotationErrorReported = true;
diagnostics.error(location, "Metaprogramming method should be within class marked with " diagnostics.error(location, "Metaprogramming method should be within class marked with "
@ -128,28 +122,31 @@ public class UsageGenerator {
return; return;
} }
implRef = buildMethodReference(type); implRef = buildMethodReference();
model.getUsages().put(type, implRef); model.getUsages().put(type, implRef);
//emitter = new EmitterImpl<>(emitterContext, model.getProxyMethod(), model.getMethod().getReturnType()); MetaprogrammingImpl.templateMethod = model.getMetaMethod();
VariableContext varContext = new TopLevelVariableContext(diagnostics);
MetaprogrammingImpl.generator = new CompositeMethodGenerator(varContext);
MetaprogrammingImpl.varContext = varContext;
MetaprogrammingImpl.returnType = model.getMethod().getReturnType();
/*for (int i = 0; i <= model.getParameters().size(); ++i) { for (int i = 0; i <= model.getMetaParameterCount(); ++i) {
emitter.generator.getProgram().createVariable(); MetaprogrammingImpl.generator.getProgram().createVariable();
} }
*/
Object[] proxyArgs = new Object[model.getMetaParameterCount()]; Object[] proxyArgs = new Object[model.getMetaParameterCount()];
for (int i = 0; i < proxyArgs.length; ++i) { for (int i = 0; i < proxyArgs.length; ++i) {
if (i == model.getMetaClassParameterIndex()) { if (i == model.getMetaClassParameterIndex()) {
proxyArgs[i] = MetaprogrammingImpl.findClass(type); proxyArgs[i] = MetaprogrammingImpl.findClass(type);
} else { } else {
proxyArgs[i] = new ValueImpl<>(null, null, model.getMetaParameterType(i)); proxyArgs[i] = new ValueImpl<>(getParameterVar(i), MetaprogrammingImpl.varContext,
model.getMetaParameterType(i));
} }
} }
try { try {
proxyMethod.invoke(null, proxyArgs); proxyMethod.invoke(null, proxyArgs);
//emitter.close(); Program program = MetaprogrammingImpl.generator.getProgram();
Program program = null; //emitter.generator.getProgram();
//new BoxingEliminator().optimize(program); //new BoxingEliminator().optimize(program);
ClassHolder cls = new ClassHolder(implRef.getClassName()); ClassHolder cls = new ClassHolder(implRef.getClassName());
@ -176,7 +173,8 @@ public class UsageGenerator {
} }
if (model.getClassParameterIndex() >= 0) { if (model.getClassParameterIndex() >= 0) {
implMethod.getVariable(model.getClassParameterIndex() + 1).connect(getClassDep.getVariable(0)); implMethod.getVariable(model.getClassParameterIndex() + 1).getClassValueNode()
.connect(nameDependency.getVariable(0));
} }
if (implMethod.getResult() != null) { if (implMethod.getResult() != null) {
@ -211,15 +209,15 @@ public class UsageGenerator {
} }
} }
private MethodReference buildMethodReference(ValueType type) { private MethodReference buildMethodReference() {
if (model.getClassParameterIndex() < 0) { if (model.getClassParameterIndex() < 0) {
return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + getSuffix(), return new MethodReference(model.getMethod().getClassName() + "$PROXY$" + getSuffix(),
model.getMethod().getDescriptor()); model.getMethod().getDescriptor());
} }
int i = 0; int i;
ValueType[] signature = new ValueType[model.getMetaParameterCount()]; ValueType[] signature = new ValueType[model.getMetaParameterCount() + 1];
for (i = 0; i < signature.length; ++i) { for (i = 0; i < signature.length - 1; ++i) {
signature[i] = model.getMetaParameterType(i); signature[i] = model.getMetaParameterType(i);
} }
signature[i] = model.getMethod().getReturnType(); signature[i] = model.getMethod().getReturnType();
@ -275,9 +273,43 @@ public class UsageGenerator {
throw new AssertionError("Don't know how to map type: " + type); throw new AssertionError("Don't know how to map type: " + type);
} }
private Variable getParameterVar(int index) {
Program program = MetaprogrammingImpl.generator.getProgram();
Variable var = program.variableAt(index + 1);
ValueType type = model.getMethod().parameterType(index);
if (type instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) type).getKind()) {
case BOOLEAN:
var = box(var, Boolean.class, boolean.class);
break;
case BYTE:
var = box(var, Byte.class, byte.class);
break;
case SHORT:
var = box(var, Short.class, short.class);
break;
case CHARACTER:
var = box(var, Character.class, char.class);
break;
case INTEGER:
var = box(var, Integer.class, int.class);
break;
case LONG:
var = box(var, Long.class, long.class);
break;
case FLOAT:
var = box(var, Float.class, float.class);
break;
case DOUBLE:
var = box(var, Double.class, double.class);
break;
}
}
return var;
}
private Variable box(Variable var, Class<?> boxed, Class<?> primitive) { private Variable box(Variable var, Class<?> boxed, Class<?> primitive) {
Program program = null; //emitter.generator.getProgram(); Program program = MetaprogrammingImpl.generator.getProgram();
BasicBlock block = program.basicBlockAt(0); BasicBlock block = program.basicBlockAt(0);
InvokeInstruction insn = new InvokeInstruction(); InvokeInstruction insn = new InvokeInstruction();

View File

@ -72,17 +72,18 @@ public class MethodDescriber {
private MethodModel findMetaMethod(MethodReader method) { private MethodModel findMetaMethod(MethodReader method) {
ClassReader cls = classSource.get(method.getOwnerName()); ClassReader cls = classSource.get(method.getOwnerName());
boolean isStatic = method.hasModifier(ElementModifier.STATIC);
int expectedParameterCount = (isStatic ? 0 : 1) + method.parameterCount();
for (MethodReader meta : cls.getMethods()) { for (MethodReader meta : cls.getMethods()) {
if (meta == method if (meta == method
|| !meta.hasModifier(ElementModifier.STATIC) || !meta.hasModifier(ElementModifier.STATIC)
|| !meta.getName().equals(method.getName()) || !meta.getName().equals(method.getName())
|| meta.getResultType() != ValueType.VOID || meta.getResultType() != ValueType.VOID
|| meta.parameterCount() != method.parameterCount() + 1) { || meta.parameterCount() != expectedParameterCount) {
continue; continue;
} }
int paramOffset = 0; int paramOffset = 0;
boolean isStatic = method.hasModifier(ElementModifier.STATIC);
if (!isStatic) { if (!isStatic) {
if (meta.parameterCount() == 0 || meta.parameterType(0).isObject(Value.class)) { if (meta.parameterCount() == 0 || meta.parameterType(0).isObject(Value.class)) {
return null; return null;

View File

@ -31,6 +31,7 @@ import org.teavm.javascript.spi.GeneratedBy;
import org.teavm.javascript.spi.Generator; import org.teavm.javascript.spi.Generator;
import org.teavm.javascript.spi.InjectedBy; import org.teavm.javascript.spi.InjectedBy;
import org.teavm.javascript.spi.Injector; import org.teavm.javascript.spi.Injector;
import org.teavm.metaprogramming.impl.MetaprogrammingDependencyListener;
import org.teavm.model.*; import org.teavm.model.*;
import org.teavm.model.instructions.*; import org.teavm.model.instructions.*;
import org.teavm.model.util.*; import org.teavm.model.util.*;
@ -354,6 +355,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
if (wasCancelled()) { if (wasCancelled()) {
return; return;
} }
dependencyChecker.addDependencyListener(new MetaprogrammingDependencyListener());
AliasProvider aliasProvider = minifying ? new MinifyingAliasProvider() : new DefaultAliasProvider(); AliasProvider aliasProvider = minifying ? new MinifyingAliasProvider() : new DefaultAliasProvider();
dependencyChecker.setInterruptor(() -> progressListener.progressReached(0) == TeaVMProgressFeedback.CONTINUE); dependencyChecker.setInterruptor(() -> progressListener.progressReached(0) == TeaVMProgressFeedback.CONTINUE);
dependencyChecker.linkMethod(new MethodReference(Class.class.getName(), "getClass", dependencyChecker.linkMethod(new MethodReference(Class.class.getName(), "getClass",
@ -473,6 +477,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
} }
} }
@SuppressWarnings("WeakerAccess")
public ListableClassHolderSource link(DependencyInfo dependency) { public ListableClassHolderSource link(DependencyInfo dependency) {
reportPhase(TeaVMPhase.LINKING, dependency.getReachableClasses().size()); reportPhase(TeaVMPhase.LINKING, dependency.getReachableClasses().size());
Linker linker = new Linker(); Linker linker = new Linker();

View File

@ -15,7 +15,6 @@
*/ */
package org.teavm.metaprogramming; package org.teavm.metaprogramming;
@SuppressWarnings("unused")
public interface Computation<T> { public interface Computation<T> {
T compute(); T compute();
} }

View File

@ -39,7 +39,7 @@ public final class Metaprogramming {
return null; return null;
} }
public static void exit(Value<?> returnValue) { public static void exit(Computation<?> returnValue) {
unsupported(); unsupported();
} }

View File

@ -24,16 +24,18 @@ import org.junit.runner.RunWith;
import org.teavm.classlib.support.Support_MapTest2; import org.teavm.classlib.support.Support_MapTest2;
import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TeaVMTestRunner;
@SuppressWarnings({"UnnecessaryUnboxing", "ClassInitializerMayBeStatic", "UnnecessaryTemporaryOnConversionToString",
"MismatchedQueryAndUpdateOfCollection", "StringEquality"})
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
public class LinkedHashMapTest { public class LinkedHashMapTest {
LinkedHashMap<Object, Object> hm; private LinkedHashMap<Object, Object> hm;
final static int hmSize = 1000; private final static int hmSize = 1000;
static Object[] objArray; private static Object[] objArray;
static Object[] objArray2; private static Object[] objArray2;
{ {
objArray = new Object[hmSize]; objArray = new Object[hmSize];
objArray2 = new Object[hmSize]; objArray2 = new Object[hmSize];
@ -47,14 +49,14 @@ public class LinkedHashMapTest {
hm = new LinkedHashMap<>(); hm = new LinkedHashMap<>();
for (int i = 0; i < objArray.length; i++) for (int i = 0; i < objArray.length; i++)
hm.put(objArray2[i], objArray[i]); hm.put(objArray2[i], objArray[i]);
hm.put("test", null); hm.put("org/teavm/metaprogramming/test", null);
hm.put(null, "test"); hm.put(null, "org/teavm/metaprogramming/test");
} }
@Test @Test
public void test_Constructor() { public void test_Constructor() {
// Test for method java.util.LinkedHashMap() // Test for method java.util.LinkedHashMap()
new Support_MapTest2(new LinkedHashMap<String, String>()).runTest(); new Support_MapTest2(new LinkedHashMap<>()).runTest();
LinkedHashMap<Object, Object> hm2 = new LinkedHashMap<>(); LinkedHashMap<Object, Object> hm2 = new LinkedHashMap<>();
assertEquals("Created incorrect LinkedHashMap", 0, hm2.size()); assertEquals("Created incorrect LinkedHashMap", 0, hm2.size());
@ -69,6 +71,7 @@ public class LinkedHashMapTest {
new LinkedHashMap<>(-1); new LinkedHashMap<>(-1);
fail("Failed to throw IllegalArgumentException for initial capacity < 0"); fail("Failed to throw IllegalArgumentException for initial capacity < 0");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// as expected
} }
LinkedHashMap<Object, Object> empty = new LinkedHashMap<>(0); LinkedHashMap<Object, Object> empty = new LinkedHashMap<>(0);
@ -86,6 +89,7 @@ public class LinkedHashMapTest {
new LinkedHashMap<>(0, 0); new LinkedHashMap<>(0, 0);
fail("Failed to throw IllegalArgumentException for initial load factor <= 0"); fail("Failed to throw IllegalArgumentException for initial load factor <= 0");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// as expected
} }
LinkedHashMap<String, String> empty = new LinkedHashMap<>(0, 0.75f); LinkedHashMap<String, String> empty = new LinkedHashMap<>(0, 0.75f);
assertNull("Empty hashtable access", empty.get("nothing")); assertNull("Empty hashtable access", empty.get("nothing"));
@ -114,8 +118,8 @@ public class LinkedHashMapTest {
assertEquals("Get returned incorecct value for existing key", "HELLO", hm.get("T")); assertEquals("Get returned incorecct value for existing key", "HELLO", hm.get("T"));
LinkedHashMap<Object, String> m = new LinkedHashMap<>(); LinkedHashMap<Object, String> m = new LinkedHashMap<>();
m.put(null, "test"); m.put(null, "org/teavm/metaprogramming/test");
assertEquals("Failed with null key", "test", m.get(null)); assertEquals("Failed with null key", "org/teavm/metaprogramming/test", m.get(null));
assertNull("Failed with missing key matching null hash", m.get(new Integer(0))); assertNull("Failed with missing key matching null hash", m.get(new Integer(0)));
} }
@ -128,7 +132,7 @@ public class LinkedHashMapTest {
LinkedHashMap<Number, String> m = new LinkedHashMap<>(); LinkedHashMap<Number, String> m = new LinkedHashMap<>();
m.put(new Short((short) 0), "short"); m.put(new Short((short) 0), "short");
m.put(null, "test"); m.put(null, "org/teavm/metaprogramming/test");
m.put(new Integer(0), "int"); m.put(new Integer(0), "int");
assertEquals("Failed adding to bucket containing null", "short", m.get(new Short((short) 0))); assertEquals("Failed adding to bucket containing null", "short", m.get(new Short((short) 0)));
assertEquals("Failed adding to bucket containing null2", "int", m.get(new Integer(0))); assertEquals("Failed adding to bucket containing null2", "int", m.get(new Integer(0)));
@ -159,7 +163,7 @@ public class LinkedHashMapTest {
assertTrue("Returned set does not contain all keys", s.contains(objArray[i].toString())); assertTrue("Returned set does not contain all keys", s.contains(objArray[i].toString()));
LinkedHashMap<Object, String> m = new LinkedHashMap<>(); LinkedHashMap<Object, String> m = new LinkedHashMap<>();
m.put(null, "test"); m.put(null, "org/teavm/metaprogramming/test");
assertTrue("Failed with null key", m.keySet().contains(null)); assertTrue("Failed with null key", m.keySet().contains(null));
assertNull("Failed with null key", m.keySet().iterator().next()); assertNull("Failed with null key", m.keySet().iterator().next());
@ -228,9 +232,9 @@ public class LinkedHashMapTest {
assertNull("Remove of non-existent key returned non-null", hm.remove("LCLCLC")); assertNull("Remove of non-existent key returned non-null", hm.remove("LCLCLC"));
LinkedHashMap<Object, String> m = new LinkedHashMap<>(); LinkedHashMap<Object, String> m = new LinkedHashMap<>();
m.put(null, "test"); m.put(null, "org/teavm/metaprogramming/test");
assertNull("Failed with same hash as null", m.remove(new Integer(0))); assertNull("Failed with same hash as null", m.remove(new Integer(0)));
assertEquals("Failed with null key", "test", m.remove(null)); assertEquals("Failed with null key", "org/teavm/metaprogramming/test", m.remove(null));
} }
@Test @Test
@ -313,7 +317,7 @@ public class LinkedHashMapTest {
assertTrue("Returned true for invalid key", !hm.containsKey("KKDKDKD")); assertTrue("Returned true for invalid key", !hm.containsKey("KKDKDKD"));
LinkedHashMap<Object, String> m = new LinkedHashMap<>(); LinkedHashMap<Object, String> m = new LinkedHashMap<>();
m.put(null, "test"); m.put(null, "org/teavm/metaprogramming/test");
assertTrue("Failed with null key", m.containsKey(null)); assertTrue("Failed with null key", m.containsKey(null));
assertTrue("Failed with missing key matching null hash", !m.containsKey(new Integer(0))); assertTrue("Failed with missing key matching null hash", !m.containsKey(new Integer(0)));
} }

View File

@ -24,6 +24,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TeaVMTestRunner;
@SuppressWarnings({"RedundantStringConstructorCall", "RedundantCast"})
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
public class StringTokenizerTest { public class StringTokenizerTest {
@Test @Test
@ -79,7 +80,7 @@ public class StringTokenizerTest {
st.nextElement(); st.nextElement();
fail("nextElement failed to throw a NoSuchElementException when it should have been out of elements"); fail("nextElement failed to throw a NoSuchElementException when it should have been out of elements");
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
return; // do nothing
} }
} }
@ -95,7 +96,7 @@ public class StringTokenizerTest {
st.nextToken(); st.nextToken();
fail("nextToken failed to throw a NoSuchElementException when it should have been out of elements"); fail("nextToken failed to throw a NoSuchElementException when it should have been out of elements");
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
return; // do nothing
} }
} }

View File

@ -38,6 +38,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TeaVMTestRunner;
@SuppressWarnings({"UnnecessaryTemporaryOnConversionToString", "SuspiciousMethodCalls"})
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
public class TreeMapTest { public class TreeMapTest {
@ -64,14 +65,13 @@ public class TreeMapTest {
if (null == o1 || null == o2) { if (null == o1 || null == o2) {
return -1; return -1;
} }
T c1 = o1; return o1.compareTo(o2);
T c2 = o2;
return c1.compareTo(c2);
} }
} }
// Regression for Harmony-1161 // Regression for Harmony-1161
class MockComparatorNullTolerable implements Comparator<String> { class MockComparatorNullTolerable implements Comparator<String> {
@SuppressWarnings("StringEquality")
@Override @Override
public int compare(String o1, String o2) { public int compare(String o1, String o2) {
if (o1 == o2) { if (o1 == o2) {
@ -87,9 +87,9 @@ public class TreeMapTest {
} }
} }
TreeMap<Object, Object> tm; private TreeMap<Object, Object> tm;
Object objArray[] = new Object[1000]; private Object[] objArray = new Object[1000];
public TreeMapTest() { public TreeMapTest() {
tm = new TreeMap<>(); tm = new TreeMap<>();
@ -255,7 +255,7 @@ public class TreeMapTest {
&& head.containsKey("10")); && head.containsKey("10"));
// Regression for Harmony-1026 // Regression for Harmony-1026
TreeMap<Integer, Double> map = new TreeMap<>(new MockComparator<Integer>()); TreeMap<Integer, Double> map = new TreeMap<>(new MockComparator<>());
map.put(1, 2.1); map.put(1, 2.1);
map.put(2, 3.1); map.put(2, 3.1);
map.put(3, 4.5); map.put(3, 4.5);
@ -308,12 +308,14 @@ public class TreeMapTest {
sub.firstKey(); sub.firstKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
try{ try{
sub.lastKey(); sub.lastKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
size = 256; size = 256;
@ -328,12 +330,14 @@ public class TreeMapTest {
sub.firstKey(); sub.firstKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
try{ try{
sub.lastKey(); sub.lastKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
} }
@ -441,7 +445,7 @@ public class TreeMapTest {
assertEquals(3, map.size()); assertEquals(3, map.size());
Map<String, String> subMap = map.subMap("", "test"); Map<String, String> subMap = map.subMap("", "org/teavm/metaprogramming/test");
assertEquals(3, subMap.size()); assertEquals(3, subMap.size());
int size = 0; int size = 0;
@ -487,12 +491,14 @@ public class TreeMapTest {
sub.firstKey(); sub.firstKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
try{ try{
sub.lastKey(); sub.lastKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
size = 256; size = 256;
@ -507,12 +513,14 @@ public class TreeMapTest {
sub.firstKey(); sub.firstKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
try{ try{
sub.lastKey(); sub.lastKey();
fail("java.util.NoSuchElementException should be thrown"); fail("java.util.NoSuchElementException should be thrown");
} catch(java.util.NoSuchElementException e) { } catch(java.util.NoSuchElementException e) {
// as expected
} }
} }

View File

@ -18,14 +18,19 @@ package org.teavm.metaprogramming.test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.teavm.metaprogramming.Metaprogramming.exit; import static org.teavm.metaprogramming.Metaprogramming.exit;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.metaprogramming.CompileTime; 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;
@CompileTime @CompileTime
@RunWith(TeaVMTestRunner.class)
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));

View File

@ -19,11 +19,8 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.diagnostics.Problem; import org.teavm.diagnostics.Problem;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
import org.teavm.vm.TeaVM; import org.teavm.vm.TeaVM;
@ -98,7 +95,7 @@ public class JSOTest {
private List<Problem> build(String methodName) { private List<Problem> build(String methodName) {
TeaVM vm = new TeaVMBuilder().build(); TeaVM vm = new TeaVMBuilder().build();
vm.installPlugins(); vm.installPlugins();
vm.entryPoint("test", new MethodReference(JSOTest.class, methodName, void.class)); vm.entryPoint("org/teavm/metaprogramming/test", new MethodReference(JSOTest.class, methodName, void.class));
vm.build(new StringBuilder(), null); vm.build(new StringBuilder(), null);
return vm.getProblemProvider().getSevereProblems(); return vm.getProblemProvider().getSevereProblems();
} }

View File

@ -73,7 +73,6 @@ public class TeaVMTestRunner extends Runner {
private Class<?> testClass; private Class<?> testClass;
private ClassHolder classHolder; private ClassHolder classHolder;
private ClassLoader classLoader; private ClassLoader classLoader;
private ClassHolderSource classSource;
private Description suiteDescription; private Description suiteDescription;
private static Map<ClassLoader, ClassHolderSource> classSources = new WeakHashMap<>(); private static Map<ClassLoader, ClassHolderSource> classSources = new WeakHashMap<>();
private File outputDir; private File outputDir;
@ -88,9 +87,11 @@ public class TeaVMTestRunner extends Runner {
static { static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> { Runtime.getRuntime().addShutdownHook(new Thread(() -> {
synchronized (TeaVMTestRunner.class) { synchronized (TeaVMTestRunner.class) {
cleanupFuture = null; if (runner != null) {
runner.stop(); cleanupFuture = null;
runner.waitForCompletion(); runner.stop();
runner.waitForCompletion();
}
} }
})); }));
} }
@ -98,7 +99,7 @@ public class TeaVMTestRunner extends Runner {
public TeaVMTestRunner(Class<?> testClass) throws InitializationError { public TeaVMTestRunner(Class<?> testClass) throws InitializationError {
this.testClass = testClass; this.testClass = testClass;
classLoader = TeaVMTestRunner.class.getClassLoader(); classLoader = TeaVMTestRunner.class.getClassLoader();
classSource = getClassSource(classLoader); ClassHolderSource classSource = getClassSource(classLoader);
classHolder = classSource.get(testClass.getName()); classHolder = classSource.get(testClass.getName());
String outputPath = System.getProperty(PATH_PARAM); String outputPath = System.getProperty(PATH_PARAM);
if (outputPath != null) { if (outputPath != null) {
@ -157,7 +158,7 @@ public class TeaVMTestRunner extends Runner {
notifier.fireTestFinished(getDescription()); notifier.fireTestFinished(getDescription());
} }
protected List<Method> getChildren() { private List<Method> getChildren() {
List<Method> children = new ArrayList<>(); List<Method> children = new ArrayList<>();
for (Method method : testClass.getDeclaredMethods()) { for (Method method : testClass.getDeclaredMethods()) {
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method)); MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
@ -168,12 +169,12 @@ public class TeaVMTestRunner extends Runner {
return children; return children;
} }
protected Description describeChild(Method child) { private Description describeChild(Method child) {
return descriptions.computeIfAbsent(child, method -> Description.createTestDescription(testClass, return descriptions.computeIfAbsent(child, method -> Description.createTestDescription(testClass,
method.getName())); method.getName()));
} }
protected void runChild(Method child, RunNotifier notifier) { private void runChild(Method child, RunNotifier notifier) {
notifier.fireTestStarted(describeChild(child)); notifier.fireTestStarted(describeChild(child));
boolean run = false; boolean run = false;
@ -261,6 +262,8 @@ public class TeaVMTestRunner extends Runner {
if (!compileResult.success) { if (!compileResult.success) {
notifier.fireTestFailure(new Failure(description, notifier.fireTestFailure(new Failure(description,
new AssertionError(compileResult.errorMessage))); new AssertionError(compileResult.errorMessage)));
notifier.fireTestFinished(description);
latch.countDown();
return false; return false;
} }