mhs) {
176 | loop: for(var mh1: mhs) {
177 | for(var mh2: mhs) {
178 | if (mh1 == mh2) {
179 | continue;
180 | }
181 | if (!moreSpecific(mh1.type(), mh2.type())) {
182 | continue loop;
183 | }
184 | }
185 | return mh1;
186 | }
187 | return null;
188 | }
189 | }
190 |
191 |
192 | // ---
193 |
194 | private static void assertEquals(Object exptected, Object result) {
195 | if (!(Objects.equals(exptected, result))) {
196 | throw new AssertionError("not equals, " + exptected + " != " + result);
197 | }
198 | }
199 |
200 | sealed interface Shape {}
201 | record Rectangle() implements Shape {}
202 | record Circle() implements Shape {}
203 |
204 | class Test {
205 | public int m1(Shape shape) { throw new AssertionError(); }
206 | public int m1(Rectangle rectangle) { return 1; }
207 | public int m1(Circle circle) { return 2; }
208 |
209 | public int m2(Rectangle r1, Rectangle r2) { return 10; }
210 | public int m2(Rectangle r1, Circle c2) { return 20; }
211 | public int m2(Circle c1, Rectangle r2) { return 30; }
212 | public int m2(Circle c1, Circle c2) { return 40; }
213 | }
214 |
215 | Multimethod MULTI_METHOD = Multimethod.of(MethodHandles.lookup());
216 |
217 | static void main(String[] args){
218 | var rectangle = new Rectangle();
219 | var circle = new Circle();
220 |
221 | assertEquals(1, MULTI_METHOD.call("m1", new Test(), rectangle));
222 | assertEquals(2, MULTI_METHOD.call("m1", new Test(), circle));
223 |
224 | assertEquals(10, MULTI_METHOD.call("m2", new Test(), rectangle, rectangle));
225 | assertEquals(20, MULTI_METHOD.call("m2", new Test(), rectangle, circle));
226 | assertEquals(30, MULTI_METHOD.call("m2", new Test(), circle, rectangle));
227 | assertEquals(40, MULTI_METHOD.call("m2", new Test(), circle, circle));
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/main/java/com/github/forax/macro/Macro.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.macro;
2 |
3 | import java.io.Serializable;
4 | import java.lang.invoke.MethodHandle;
5 | import java.lang.invoke.MethodHandles;
6 | import java.lang.invoke.MethodHandles.Lookup;
7 | import java.lang.invoke.MethodType;
8 | import java.lang.invoke.MutableCallSite;
9 | import java.lang.invoke.SerializedLambda;
10 | import java.lang.invoke.WrongMethodTypeException;
11 | import java.util.ArrayList;
12 | import java.util.Collections;
13 | import java.util.List;
14 |
15 | import static java.lang.invoke.MethodHandles.dropArguments;
16 | import static java.lang.invoke.MethodHandles.insertArguments;
17 | import static java.lang.invoke.MethodType.methodType;
18 | import static java.util.Objects.requireNonNull;
19 |
20 | /**
21 | * A way to defines macro at runtime in Java.
22 | *
23 | * A macro is a method that pre-compute a list of constants from some the arguments and ask a linker
24 | * to provide a method handle from the list of constants to be called with the resting arguments.
25 | *
26 | *
27 | * mh(arg1, arg2, arg3) + macro(param1, param2, param3) -> linker(const1, const2)(arg2, arg3)
28 | *
29 | *
30 | * The {@link Parameter macro parameters} describes how to extract a constant from an argument,
31 | * how to react if subsequent calls found new constants and if the argument are used or dropped
32 | * by the method handle retuend by the linker.
33 | *
34 | * @see #createMH(MethodType, List, Linker)
35 | */
36 | public class Macro {
37 | /**
38 | * Describe the parameters of a function call of a macro.
39 | * Instead of a classical function call where all arguments are sent to the function, a macro separate the arguments
40 | * in two categories, the constants and the values, the constants are extracted and a linker is called
41 | * to provide a method handle that will be called with the values.
42 | *
43 | * The fact that the constant are computed earlier than the arguments allows data structures and
44 | * method linkage to be computed resulting in a code more efficient than using reflection.
45 | *
46 | * {@link Macro#createMH(MethodType, List, Linker)} create a macro, as a method handle,
47 | * from a method type, a list of parameters and a {@link Linker}.
48 | *
49 | * There are 3 kinds of parameters
50 | *
51 | * {@link ConstantParameter a constant parameter}, a parameter from which a
52 | * {@link ConstantParameter#function() constant can be extracted},
53 | * the argument itself will be @link {@link ConstantParameter#dropValue() kept or not} and
54 | * {@link ConstantParameter#policy() one or more constants} can be extracted from a parameter.
55 | * {@link ValueParameter a value parameter}, a simple argument.
56 | * {@link IgnoreParameter an ignore parameter}, the corresponding argument is ignored.
57 | *
58 | *
59 | * The {@link Linker} defines the function called with the constants to provide the method handle
60 | * that will be called with the argument.
61 | *
62 | * @see Macro
63 | */
64 | public sealed interface Parameter {}
65 |
66 | /**
67 | * A parameter indicating that the corresponding argument is a constant.
68 | */
69 | public record ConstantParameter(ProjectionFunction function, boolean dropValue, ConstantPolicy policy) implements Parameter {
70 | /**
71 | * Creates a constant parameter with a projection function to compute the constant from a value,
72 | * a boolean indicating if the value should be dropped ot not and
73 | * an enum indicating the operation to do if there are different values for the constant
74 | * ({@link ConstantPolicy#ERROR}: emits an error, {@link ConstantPolicy#RELINK}: calls the linker again,
75 | * {@link ConstantPolicy#POLYMORPHIC}:install a polymorphic inlining cache).
76 | *
77 | * @param function the projection function, can be a lambda, {@link ProjectionFunction#VALUE} or
78 | * {@link ProjectionFunction#GET_CLASS}
79 | * @param dropValue a boolean indicating if the value should be dropped given it appears as a constant
80 | * @param policy policy when there are several constants for a parameter
81 | */
82 | public ConstantParameter {
83 | requireNonNull(function);
84 | requireNonNull(policy);
85 | }
86 |
87 | /**
88 | * Returns a new constant parameter using the {@link ConstantPolicy#ERROR}
89 | * @return a new constant parameter using the {@link ConstantPolicy#ERROR}
90 | */
91 | public ConstantParameter error() {
92 | return new ConstantParameter(function, dropValue, ConstantPolicy.ERROR);
93 | }
94 |
95 | /**
96 | * Returns a new constant parameter using the {@link ConstantPolicy#RELINK}
97 | * @return a new constant parameter using the {@link ConstantPolicy#RELINK}
98 | */
99 | public ConstantParameter relink() {
100 | return new ConstantParameter(function, dropValue, ConstantPolicy.RELINK);
101 | }
102 |
103 | /**
104 | * Returns a new constant parameter using the {@link ConstantPolicy#POLYMORPHIC}
105 | * @return a new constant parameter using the {@link ConstantPolicy#POLYMORPHIC}
106 | */
107 | public ConstantParameter polymorphic() {
108 | return new ConstantParameter(function, dropValue, ConstantPolicy.POLYMORPHIC);
109 | }
110 |
111 | /**
112 | * Returns a new constant parameter that drop/retain the value of the constant
113 | * If the value is dropped it will not be an argument of the method handle returned
114 | * by the {@link Linker}
115 | *
116 | * @param dropValue drop or retain the value of a constant
117 | * @return a new constant parameter that drop/retain the value of the constant
118 | */
119 | public ConstantParameter dropValue(boolean dropValue) {
120 | return new ConstantParameter(function, dropValue, policy);
121 | }
122 | }
123 |
124 | /**
125 | * The projection function used to extract the constant from a value.
126 | */
127 | @FunctionalInterface
128 | public interface ProjectionFunction {
129 | /**
130 | * Returns a constant from the value and its declared type.
131 | *
132 | * @param declaredType the declared type of the argument
133 | * @param value the value of the argument
134 | * @return a constant
135 | */
136 | Object computeConstant(Class> declaredType, Object value);
137 |
138 | /**
139 | * A projection function that returns the value as constant.
140 | */
141 | ProjectionFunction VALUE = (declaredType, value) -> value;
142 |
143 | /**
144 | * A projection function that return the class of the value as a constant.
145 | */
146 | ProjectionFunction GET_CLASS = (declaredType, value) -> declaredType.isPrimitive()? declaredType: value.getClass();
147 | }
148 |
149 | /**
150 | * The behavior when the macro system detects that a constant has several different values.
151 | */
152 | public enum ConstantPolicy {
153 | /**
154 | * Emits an exception
155 | */
156 | ERROR,
157 |
158 | /**
159 | * Calls the linker again, trashing all previous computation
160 | */
161 | RELINK,
162 |
163 | /**
164 | * Use a polymorphic inlining cache to remember all the linkages of the constants
165 | */
166 | POLYMORPHIC
167 | }
168 |
169 | /**
170 | * A parameter indicating that the corresponding argument should be ignored.
171 | */
172 | public enum IgnoreParameter implements Parameter {
173 | /**
174 | * The singleton instance.
175 | *
176 | * @see #IGNORE
177 | */
178 | INSTANCE
179 | }
180 |
181 | /**
182 | * A parameter indicating that the corresponding argument is just a value (not a constant).
183 | */
184 | public enum ValueParameter implements Parameter {
185 | /**
186 | * The singleton instance.
187 | *
188 | * @see #VALUE
189 | */
190 | INSTANCE
191 | }
192 |
193 | /**
194 | * A constant parameter that defines the value of the argument as a constant,
195 | * drop the value and throws an exception if the parameter see several values
196 | */
197 | public static final ConstantParameter CONSTANT_VALUE = new ConstantParameter(ProjectionFunction.VALUE, true, ConstantPolicy.ERROR);
198 |
199 | /**
200 | * A constant parameter that defines the class of the argument as a constant,
201 | * the value is not dropped and throws an exception if the parameter see several classes
202 | */
203 | public static final ConstantParameter CONSTANT_CLASS = new ConstantParameter(ProjectionFunction.GET_CLASS, false, ConstantPolicy.ERROR);
204 |
205 | /**
206 | * A value parameter
207 | */
208 | public static final ValueParameter VALUE = ValueParameter.INSTANCE;
209 |
210 | /**
211 | * An ignored parameter
212 | */
213 | public static final IgnoreParameter IGNORE = IgnoreParameter.INSTANCE;
214 |
215 | /**
216 | * Called by the macro system with the constants and the method handle type
217 | * to get a method handle implementing the behavior.
218 | */
219 | public interface Linker {
220 | /**
221 | * This method is called by the macro system to find the method handle
222 | * to execute with the arguments.
223 | *
224 | * @param constants the constants gather from the arguments.
225 | * @param methodType the method type that must be the type of the returned method handle
226 | * @return a method handle
227 | *
228 | * @throws ClassNotFoundException if a class is not found
229 | * @throws IllegalAccessException if access is not possible
230 | * @throws InstantiationException if instantiation is not possible
231 | * @throws NoSuchFieldException if no field is found
232 | * @throws NoSuchMethodException if no method is found
233 | */
234 | MethodHandle apply(List constants, MethodType methodType)
235 | throws ClassNotFoundException, IllegalAccessException, InstantiationException,
236 | NoSuchFieldException, NoSuchMethodException;
237 | }
238 |
239 | /**
240 | * Creates a method handle conforming to the method type taken as parameter which
241 | * separate the constant arguments from the other arguments and call the linker
242 | * one or more time with the constant arguments.
243 | *
244 | * @param methodType a method type
245 | * @param parameters the macro parameters
246 | * @param linker the linker that will be called with the constant to get the corresponding method handles
247 | * @return a method handle
248 | */
249 | public static MethodHandle createMH(MethodType methodType,
250 | List extends Parameter> parameters,
251 | Linker linker) {
252 | return createMHControl(methodType, parameters, linker).createMH();
253 | }
254 |
255 | /**
256 | * An interface that can create a method handle from the parameters of
257 | * {@link #createMHControl(MethodType, List, Linker)} and reset all created method handles to their initial state.
258 | */
259 | public interface MethodHandleControl {
260 | /**
261 | * Creates a method handle from the parameter of {@link #createMHControl(MethodType, List, Linker)}.
262 | * @return a new method handle
263 | */
264 | MethodHandle createMH();
265 |
266 | /**
267 | * De-optimize all method handles created with {@link #createMH()}.
268 | */
269 | void deoptimize();
270 | }
271 |
272 | /**
273 | * Return a method handle control which provides
274 | *
275 | * a method {@link MethodHandleControl#createMH()} that create a method handle from a recipe.
276 | * a method {@link MethodHandleControl#deoptimize()} that can reset the method handle to its initial state
277 | *
278 | *
279 | * @param methodType a method type
280 | * @param parameters the macro parameters
281 | * @param linker the linker that will be called with the constant to get the corresponding method handles
282 | * @return a method handle
283 | */
284 | public static MethodHandleControl createMHControl(MethodType methodType,
285 | List extends Parameter> parameters,
286 | Linker linker) {
287 | requireNonNull(methodType, "linkageType is null");
288 | requireNonNull(parameters, "parameters is null");
289 | requireNonNull(linker, "linker is null");
290 | if (methodType.parameterCount() != parameters.size()) {
291 | throw new IllegalArgumentException("methodType.parameterCount() != parameters.size()");
292 | }
293 | return new RootCallSite(methodType, List.copyOf(parameters), linker);
294 | }
295 |
296 | private sealed interface Argument { }
297 |
298 | private enum ValueArgument implements Argument { INSTANCE }
299 | private record IgnoredArgument(Class> type, int position) implements Argument { }
300 | private record GuardedArgument(Class> type, int position, boolean dropValue, ProjectionFunction function, Object constant, ConstantPolicy policy) implements Argument { }
301 |
302 | private record AnalysisResult(List arguments, List constants, List values, MethodType linkageType) {
303 | private AnalysisResult {
304 | arguments = Collections.unmodifiableList(arguments);
305 | constants = Collections.unmodifiableList(constants);
306 | values = Collections.unmodifiableList(values);
307 | }
308 | }
309 |
310 | private static AnalysisResult argumentAnalysis(Object[] args, List parameters, MethodType methodType) {
311 | var arguments = new ArrayList();
312 | var constants = new ArrayList<>();
313 | var values = new ArrayList<>();
314 | var valueTypes = new ArrayList>();
315 | for(var i = 0; i < args.length; i++) {
316 | var parameter = parameters.get(i);
317 | var arg = args[i];
318 | var type = methodType.parameterType(i);
319 | var argument = switch (parameter) {
320 | case IgnoreParameter __ -> new IgnoredArgument(type, i);
321 | case ValueParameter __ -> {
322 | values.add(arg);
323 | valueTypes.add(type);
324 | yield ValueArgument.INSTANCE;
325 | }
326 | case ConstantParameter constantParameter -> {
327 | var constant = constantParameter.function().computeConstant(type, arg);
328 | constants.add(constant);
329 | if (!constantParameter.dropValue()) {
330 | values.add(arg);
331 | valueTypes.add(type);
332 | }
333 | yield new GuardedArgument(type, i, constantParameter.dropValue(),
334 | constantParameter.function(), constant,
335 | constantParameter.policy());
336 | }
337 | };
338 | arguments.add(argument);
339 | }
340 | return new AnalysisResult(arguments, constants, values, methodType(methodType.returnType(), valueTypes));
341 | }
342 |
343 | private static MethodHandle link(Linker linker, List constants, MethodType linkageType) {
344 | MethodHandle target;
345 | try {
346 | target = linker.apply(constants, linkageType);
347 | } catch (ClassNotFoundException e) {
348 | throw (NoClassDefFoundError) new NoClassDefFoundError().initCause(e);
349 | } catch (IllegalAccessException e) {
350 | throw (IllegalAccessError) new IllegalAccessError().initCause(e);
351 | } catch (InstantiationException e) {
352 | throw (InstantiationError) new InstantiationError().initCause(e);
353 | } catch (NoSuchFieldException e) {
354 | throw (NoSuchFieldError) new NoSuchFieldError().initCause(e);
355 | } catch (NoSuchMethodException e) {
356 | throw (NoSuchMethodError) new NoSuchMethodError().initCause(e);
357 | }
358 |
359 | requireNonNull(target, "linker return value is null");
360 | if (!target.type().equals(linkageType)) {
361 | throw new WrongMethodTypeException("linker return value as the wrong type");
362 | }
363 | return target;
364 | }
365 |
366 | private static final class RootCallSite extends MutableCallSite implements MethodHandleControl {
367 | private static final MethodHandle FALLBACK, DERIVE_CHECK, REQUIRE_CONSTANT;
368 | static {
369 | var lookup = MethodHandles.lookup();
370 | try {
371 | FALLBACK = lookup.findVirtual(RootCallSite.class, "fallback", methodType(Object.class, Object[].class));
372 | DERIVE_CHECK = lookup.findStatic(RootCallSite.class, "derivedCheck", methodType(boolean.class, Object.class, Class.class, ProjectionFunction.class, Object.class));
373 | REQUIRE_CONSTANT = lookup.findStatic(RootCallSite.class, "requireConstant", methodType(Object.class, Object.class, Class.class, ProjectionFunction.class, Object.class));
374 | } catch (NoSuchMethodException | IllegalAccessException e) {
375 | throw new AssertionError(e);
376 | }
377 | }
378 |
379 | private final List parameters;
380 | private final Linker linker;
381 | private final MethodHandle fallback;
382 |
383 | public RootCallSite(MethodType type, List parameters, Linker linker) {
384 | super(type);
385 | this.parameters = parameters;
386 | this.linker = linker;
387 | var fallback = FALLBACK.bindTo(this).asCollector(Object[].class, type.parameterCount()).asType(type);
388 | this.fallback = fallback;
389 | setTarget(fallback);
390 | }
391 |
392 | @Override
393 | public MethodHandle createMH() {
394 | return dynamicInvoker();
395 | }
396 |
397 | @Override
398 | public void deoptimize() {
399 | setTarget(fallback);
400 | MutableCallSite.syncAll(new MutableCallSite[] { this });
401 | }
402 |
403 | private static boolean derivedCheck(Object arg, Class> type, ProjectionFunction function, Object constant) {
404 | return function.computeConstant(type, arg) == constant;
405 | }
406 |
407 | private static Object requireConstant(Object arg, Class> type, ProjectionFunction function, Object constant) {
408 | if (function.computeConstant(type, arg) != constant) {
409 | throw new IllegalStateException("constant violation for argument " + arg + " != " + constant);
410 | }
411 | return arg;
412 | }
413 |
414 | private MethodHandle dropValuesAndInstallGuards(List arguments,
415 | MethodType methodType, MethodHandle target) {
416 |
417 | // take care of the dropped values
418 | for(var argument: arguments) {
419 | switch (argument) {
420 | case IgnoredArgument ignoredArgument -> {
421 | target = dropArguments(target, ignoredArgument.position, ignoredArgument.type);
422 | }
423 | case GuardedArgument guardedArgument -> {
424 | if (guardedArgument.dropValue) {
425 | target = dropArguments(target, guardedArgument.position, guardedArgument.type);
426 | }
427 | }
428 | case ValueArgument __ -> {}
429 | }
430 | }
431 |
432 | // install guards
433 | for(var argument: arguments) {
434 | switch (argument) {
435 | case IgnoredArgument __ -> {}
436 | case GuardedArgument guardedArgument -> {
437 | var type = guardedArgument.type;
438 | if (guardedArgument.policy == ConstantPolicy.ERROR) {
439 | var requireConstant = insertArguments(REQUIRE_CONSTANT, 1, type, guardedArgument.function, guardedArgument.constant)
440 | .asType(methodType(type, type));
441 | target = MethodHandles.filterArguments(target, guardedArgument.position, requireConstant);
442 | continue;
443 | }
444 | var deriveCheck = insertArguments(DERIVE_CHECK, 1, type, guardedArgument.function, guardedArgument.constant)
445 | .asType(methodType(boolean.class, type));
446 | var test = dropArguments(deriveCheck, 0, target.type().parameterList().subList(0, guardedArgument.position));
447 | var fallback = guardedArgument.policy == ConstantPolicy.RELINK?
448 | this.fallback :
449 | new RootCallSite(methodType, parameters, linker).dynamicInvoker();
450 | target = MethodHandles.guardWithTest(test, target, fallback);
451 | }
452 | case ValueArgument __ -> {}
453 | }
454 | }
455 | return target;
456 | }
457 |
458 | private Object fallback(Object[] args) throws Throwable {
459 | var analysisResult = argumentAnalysis(args, parameters, type());
460 | var arguments = analysisResult.arguments;
461 | var constants = analysisResult.constants;
462 | var values = analysisResult.values;
463 | var linkageType = analysisResult.linkageType;
464 |
465 | var linkerTarget = link(linker, constants, linkageType);
466 | var target = dropValuesAndInstallGuards(arguments, type(), linkerTarget);
467 | setTarget(target);
468 |
469 | return linkerTarget.invokeWithArguments(values);
470 | }
471 | }
472 |
473 | /**
474 | * Rethrow any throwable without the compiler considering as a checked exception.
475 | *
476 | * @param cause a throwable to throw
477 | * @return nothing typed as an AssertionError so rethrow can be a parameter of @code throw}.
478 | */
479 | public static AssertionError rethrow(Throwable cause) {
480 | throw rethrow0(cause);
481 | }
482 |
483 | @SuppressWarnings("unchecked") // allow to erase the exception type, see above
484 | private static AssertionError rethrow0(Throwable cause) throws T {
485 | throw (T) cause;
486 | }
487 |
488 | /**
489 | * Returns a {@code java.lang.invoke.SerializedLambda} from a serializable lambda.
490 | * This operation quite slow, so it should not be done in a fast path.
491 | *
492 | * @param lookup a lookup that can see the lambda
493 | * @param lambda a serializable lambda
494 | * @return a SerializedLambda object containing all the info about a lambda
495 | */
496 | public static SerializedLambda crack(Lookup lookup, Object lambda) {
497 | if (!(lambda instanceof Serializable)) {
498 | throw new IllegalArgumentException("the lambda is not serializable");
499 | }
500 | MethodHandle writeReplace;
501 | try {
502 | writeReplace = lookup.findVirtual(lambda.getClass(), "writeReplace", methodType(Object.class));
503 | } catch (IllegalAccessException e) {
504 | throw (IllegalAccessError) new IllegalAccessError().initCause(e);
505 | } catch (NoSuchMethodException e) {
506 | throw (NoSuchMethodError) new NoSuchMethodError().initCause(e);
507 | }
508 | try {
509 | return (SerializedLambda) writeReplace.invoke(lambda);
510 | } catch (Throwable t) {
511 | throw rethrow(t);
512 | }
513 | }
514 | }
515 |
--------------------------------------------------------------------------------
/src/test/java/com/github/forax/macro/DeoptimizableTest.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.macro;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.lang.invoke.MethodHandle;
6 | import java.lang.invoke.MethodHandles;
7 | import java.lang.invoke.MethodType;
8 | import java.util.List;
9 |
10 | import static java.lang.invoke.MethodType.methodType;
11 | import static org.junit.jupiter.api.Assertions.assertEquals;
12 |
13 | public class DeoptimizableTest {
14 | @Test
15 | public void constantAndDeoptimization() throws Throwable {
16 | var box = new Object() { private int value = 42; };
17 | var control = Macro.createMHControl(methodType(int.class), List.of(),
18 | (__, methodType) -> MethodHandles.constant(int.class, box.value));
19 | var mh = control.createMH();
20 | assertEquals(42, (int)mh.invokeExact());
21 |
22 | box.value = 747;
23 | assertEquals(42, (int) mh.invokeExact());
24 |
25 | control.deoptimize();
26 | assertEquals(747, (int) mh.invokeExact());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/java/com/github/forax/macro/ExampleTest.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.macro;
2 |
3 | import com.github.forax.macro.example.almostconstant;
4 | import com.github.forax.macro.example.builder1;
5 | import com.github.forax.macro.example.builder2;
6 | import com.github.forax.macro.example.builder3;
7 | import com.github.forax.macro.example.fmt;
8 | import com.github.forax.macro.example.multimethods;
9 |
10 | import org.junit.jupiter.api.Test;
11 |
12 | public class ExampleTest {
13 | @Test
14 | public void builder1() {
15 | builder1.main(new String[0]);
16 | }
17 |
18 | @Test
19 | public void builder2() {
20 | builder2.main(new String[0]);
21 | }
22 |
23 | @Test
24 | public void builder3() {
25 | builder3.main(new String[0]);
26 | }
27 |
28 | @Test
29 | public void fmt() {
30 | fmt.main(new String[0]);
31 | }
32 |
33 | @Test
34 | public void almostconstant() {
35 | almostconstant.main(new String[0]);
36 | }
37 |
38 | @Test
39 | public void multimethod() {
40 | multimethods.main(new String[0]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/com/github/forax/macro/MacroTest.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.macro;
2 |
3 | import com.github.forax.macro.Macro.Linker;
4 | import com.github.forax.macro.Macro.Parameter;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.io.Serializable;
8 | import java.lang.invoke.MethodHandle;
9 | import java.lang.invoke.MethodHandles;
10 | import java.lang.invoke.MethodHandles.Lookup;
11 | import java.lang.invoke.MethodType;
12 | import java.lang.reflect.RecordComponent;
13 | import java.util.Arrays;
14 | import java.util.Collections;
15 | import java.util.HashSet;
16 | import java.util.List;
17 | import java.util.regex.Pattern;
18 | import java.util.stream.Collectors;
19 | import java.util.stream.IntStream;
20 | import java.util.stream.Stream;
21 |
22 | import static com.github.forax.macro.Macro.CONSTANT_CLASS;
23 | import static com.github.forax.macro.Macro.CONSTANT_VALUE;
24 | import static com.github.forax.macro.Macro.VALUE;
25 | import static java.lang.invoke.MethodType.methodType;
26 | import static org.junit.jupiter.api.Assertions.*;
27 |
28 | public class MacroTest {
29 | @Test
30 | public void simple() throws Throwable {
31 | class Foo {
32 | public double bar(int value) { return 1.0; }
33 | public double baz(int value) { return 2.0; }
34 | }
35 |
36 | class Example {
37 | private static final MethodHandle MH;
38 | static {
39 | Lookup lookup = MethodHandles.lookup();
40 | MH = Macro.createMH(MethodType.methodType(double.class, Foo.class, String.class, int.class),
41 | List.of(Macro.VALUE, Macro.CONSTANT_VALUE.polymorphic(), Macro.VALUE),
42 | (constants, type) -> {
43 | String name = (String) constants.get(0);
44 | return lookup.findVirtual(Foo.class, name, MethodType.methodType(double.class, int.class)).asType(type);
45 | });
46 | }
47 |
48 | public static double call(Foo foo, String name, int value) {
49 | try {
50 | return (double) MH.invokeExact(foo, name, value);
51 | } catch(Throwable t) {
52 | throw Macro.rethrow(t);
53 | }
54 | }
55 | }
56 |
57 | Foo foo = new Foo();
58 | assertEquals(1.0, Example.call(foo, "bar", 42));
59 | assertEquals(1.0, Example.call(foo, "bar", 42));
60 | assertEquals(2.0, Example.call(foo, "baz", 42));
61 | assertEquals(2.0, Example.call(foo, "baz", 42));
62 | }
63 |
64 | @Test
65 | public void pattern() {
66 | interface PatternFactory {
67 | Pattern pattern(String pattern);
68 |
69 | static PatternFactory of() {
70 | record PatternFactoryImpl(MethodHandle mh) implements PatternFactory {
71 | @Override
72 | public Pattern pattern(String pattern) {
73 | try {
74 | return (Pattern) mh.invokeExact(pattern);
75 | } catch(Throwable t) {
76 | throw Macro.rethrow(t);
77 | }
78 | }
79 | }
80 |
81 | var mh = Macro.createMH(methodType(Pattern.class, String.class),
82 | List.of(CONSTANT_VALUE),
83 | (constants, type) -> MethodHandles.constant(Pattern.class, Pattern.compile((String) constants.get(0))));
84 | return new PatternFactoryImpl(mh);
85 | }
86 | }
87 |
88 | var factory = PatternFactory.of();
89 | var pattern1 = factory.pattern("foo");
90 | var pattern2 = factory.pattern("foo");
91 | assertAll(
92 | () -> assertSame(pattern1, pattern2),
93 | () -> assertThrows(IllegalStateException.class, () -> factory.pattern("bar"))
94 | );
95 | }
96 |
97 | @Test
98 | public void dynamicDispatch() {
99 | interface Dispatch {
100 | T call(Object receiver, String name, MethodType methodType, Object... args);
101 |
102 | static Dispatch of(Lookup lookup) {
103 | record DispatchImpl(MethodHandle mh) implements Dispatch {
104 | @Override
105 | @SuppressWarnings("unchecked")
106 | public T call(Object receiver, String name, MethodType methodType, Object... args) {
107 | try {
108 | return (T) mh.invokeExact(receiver, name, methodType, args);
109 | } catch(Throwable t) {
110 | throw Macro.rethrow(t);
111 | }
112 | }
113 | }
114 |
115 | var mh = Macro.createMH(methodType(Object.class, Object.class, String.class, MethodType.class, Object[].class),
116 | List.of(CONSTANT_CLASS.polymorphic(), CONSTANT_VALUE, CONSTANT_VALUE, VALUE),
117 | (constants, type) -> {
118 | var receiverClass = (Class>) constants.get(0);
119 | var name = (String) constants.get(1);
120 | var methodType = (MethodType) constants.get(2);
121 | return lookup.findVirtual(receiverClass, name, methodType)
122 | .asSpreader(Object[].class, methodType.parameterCount())
123 | .asType(type);
124 | });
125 | return new DispatchImpl(mh);
126 | }
127 | }
128 |
129 | record A() {
130 | String m(long value) { return "A"; }
131 | }
132 | record B() {
133 | String m(long value) { return "B"; }
134 | }
135 |
136 | var dispatch = Dispatch.of(MethodHandles.lookup());
137 | assertEquals("A", dispatch.call(new A(), "m", methodType(String.class, long.class), 3L));
138 | assertEquals("B", dispatch.call(new B(), "m", methodType(String.class, long.class), 4L));
139 | assertEquals("A", dispatch.call(new A(), "m", methodType(String.class, long.class), 5L));
140 | assertEquals("B", dispatch.call(new B(), "m", methodType(String.class, long.class), 6L));
141 | }
142 |
143 | @Test
144 | public void visitorDispatch() {
145 | interface VisitorCaller {
146 | R call(Object visitor, Object object);
147 |
148 | static VisitorCaller of(Lookup lookup, Class> visitorClass, Class returnType) {
149 | record VisitorCallerImpl(MethodHandle mh) implements VisitorCaller {
150 | @Override
151 | @SuppressWarnings("unchecked")
152 | public R call(Object visitor, Object element) {
153 | try {
154 | return (R) mh.invokeExact(visitor, element);
155 | } catch(Throwable t) {
156 | throw Macro.rethrow(t);
157 | }
158 | }
159 | }
160 |
161 | var mh = Macro.createMH(methodType(returnType, visitorClass, Object.class),
162 | List.of(CONSTANT_CLASS.relink(), CONSTANT_CLASS.polymorphic()),
163 | (constants, type) -> {
164 | var visitorType = (Class>) constants.get(0);
165 | var elementType = (Class>) constants.get(1);
166 | return lookup.findVirtual(visitorType, "visit", methodType(returnType, elementType)).asType(type);
167 | })
168 | .asType(methodType(Object.class, Object.class, Object.class));
169 | return new VisitorCallerImpl<>(mh);
170 | }
171 | }
172 |
173 | interface Vehicle {}
174 | record Car() implements Vehicle {}
175 | record Bus() implements Vehicle {}
176 | interface Visitor {
177 | R visit(Car car);
178 | R visit(Bus bus);
179 | }
180 | var visitor = new Visitor() {
181 | @Override
182 | public String visit(Car car) {
183 | return "Car";
184 | }
185 | @Override
186 | public String visit(Bus bus) {
187 | return "Bus";
188 | }
189 | };
190 |
191 | var caller = VisitorCaller.of(MethodHandles.lookup(), visitor.getClass(), String.class);
192 | assertEquals("Car", caller.call(visitor, new Car()));
193 | assertEquals("Bus", caller.call(visitor, new Bus()));
194 | assertEquals("Car", caller.call(visitor, new Car()));
195 | assertEquals("Bus", caller.call(visitor, new Bus()));
196 | }
197 | }
--------------------------------------------------------------------------------
/src/test/java/com/github/forax/macro/ParameterTest.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.macro;
2 |
3 | import com.github.forax.macro.Macro.ConstantParameter;
4 | import com.github.forax.macro.Macro.ConstantPolicy;
5 | import com.github.forax.macro.Macro.ProjectionFunction;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.Arguments;
8 | import org.junit.jupiter.params.provider.MethodSource;
9 |
10 | import java.lang.invoke.MethodHandles;
11 | import java.lang.invoke.MethodType;
12 | import java.util.List;
13 | import java.util.stream.Stream;
14 |
15 | import static org.junit.jupiter.api.Assertions.assertEquals;
16 | import static org.junit.jupiter.api.Assertions.assertNull;
17 |
18 | public class ParameterTest {
19 | private static Stream provideArguments() {
20 | return Stream.of(0)
21 | .mapMulti((__, consumer) -> {
22 | for(var projection: new ProjectionFunction[] { ProjectionFunction.VALUE, ProjectionFunction.GET_CLASS }) {
23 | for(var dropValue: new boolean[] { true, false}) {
24 | for(var policy: ConstantPolicy.values()) {
25 | consumer.accept(Arguments.of(projection, dropValue, policy));
26 | }
27 | }
28 | }
29 | });
30 | }
31 |
32 | @ParameterizedTest
33 | @MethodSource("provideArguments")
34 | public void macroParameterWithObject(ProjectionFunction function, boolean dropValue, ConstantPolicy policy) throws Throwable {
35 | var parameter = new ConstantParameter(function, dropValue, policy);
36 | var mh =
37 | Macro.createMH(
38 | MethodType.methodType(Object.class, Object.class), List.of(parameter), (constants, methodType) -> {
39 | var expectedConstant = (function == ProjectionFunction.VALUE)? 42: Integer.class;
40 | assertEquals(List.of(expectedConstant), constants);
41 |
42 | var expectedMethodType = dropValue? MethodType.methodType(Object.class): MethodType.methodType(Object.class, Object.class);
43 | assertEquals(expectedMethodType, methodType);
44 |
45 | return MethodHandles.empty(methodType);
46 | });
47 | assertNull(mh.invoke(42));
48 | }
49 |
50 | @ParameterizedTest
51 | @MethodSource("provideArguments")
52 | public void macroParameterWithAnInterface(ProjectionFunction function, boolean dropValue, ConstantPolicy policy) throws Throwable {
53 | interface I {}
54 | record R() implements I {}
55 |
56 | var r = new R();
57 | var parameter = new ConstantParameter(function, dropValue, policy);
58 | var mh =
59 | Macro.createMH(
60 | MethodType.methodType(int.class, I.class), List.of(parameter), (constants, methodType) -> {
61 | var expectedConstant = (function == ProjectionFunction.VALUE)? r: R.class;
62 | assertEquals(List.of(expectedConstant), constants);
63 |
64 | var expectedMethodType = dropValue? MethodType.methodType(int.class): MethodType.methodType(int.class, I.class);
65 | assertEquals(expectedMethodType, methodType);
66 |
67 | return MethodHandles.empty(methodType);
68 | });
69 | assertEquals(0, mh.invoke(r));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------