8151179: address issues raised by JCK team on JEP 274 API
Reviewed-by: jrose, redestad, psandoz
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java Wed Sep 28 03:18:01 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java Wed Sep 28 14:02:21 2016 +0200
@@ -1962,12 +1962,12 @@
* This method is bound as the predicate in {@linkplain MethodHandles#countedLoop(MethodHandle, MethodHandle,
* MethodHandle) counting loops}.
*
+ * @param limit the upper bound of the parameter, statically bound at loop creation time.
* @param counter the counter parameter, passed in during loop execution.
- * @param limit the upper bound of the parameter, statically bound at loop creation time.
*
* @return whether the counter has reached the limit.
*/
- static boolean countedLoopPredicate(int counter, int limit) {
+ static boolean countedLoopPredicate(int limit, int counter) {
return counter < limit;
}
@@ -1975,27 +1975,16 @@
* This method is bound as the step function in {@linkplain MethodHandles#countedLoop(MethodHandle, MethodHandle,
* MethodHandle) counting loops} to increment the counter.
*
+ * @param limit the upper bound of the loop counter (ignored).
* @param counter the loop counter.
*
* @return the loop counter incremented by 1.
*/
- static int countedLoopStep(int counter, int limit) {
+ static int countedLoopStep(int limit, int counter) {
return counter + 1;
}
/**
- * This method is bound as a filter in {@linkplain MethodHandles#countedLoop(MethodHandle, MethodHandle, MethodHandle,
- * MethodHandle) counting loops} to pass the correct counter value to the body.
- *
- * @param counter the loop counter.
- *
- * @return the loop counter decremented by 1.
- */
- static int decrementCounter(int counter) {
- return counter - 1;
- }
-
- /**
* This is bound to initialize the loop-local iterator in {@linkplain MethodHandles#iteratedLoop iterating loops}.
*
* @param it the {@link Iterable} over which the loop iterates.
@@ -2164,12 +2153,11 @@
MH_arrayIdentity = 5,
MH_countedLoopPred = 6,
MH_countedLoopStep = 7,
- MH_iteratePred = 8,
- MH_initIterator = 9,
+ MH_initIterator = 8,
+ MH_iteratePred = 9,
MH_iterateNext = 10,
- MH_decrementCounter = 11,
- MH_Array_newInstance = 12,
- MH_LIMIT = 13;
+ MH_Array_newInstance = 11,
+ MH_LIMIT = 12;
static MethodHandle getConstantHandle(int idx) {
MethodHandle handle = HANDLES[idx];
@@ -2220,18 +2208,15 @@
case MH_countedLoopStep:
return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "countedLoopStep",
MethodType.methodType(int.class, int.class, int.class));
- case MH_iteratePred:
- return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "iteratePredicate",
- MethodType.methodType(boolean.class, Iterator.class));
case MH_initIterator:
return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "initIterator",
MethodType.methodType(Iterator.class, Iterable.class));
+ case MH_iteratePred:
+ return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "iteratePredicate",
+ MethodType.methodType(boolean.class, Iterator.class));
case MH_iterateNext:
return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "iterateNext",
MethodType.methodType(Object.class, Iterator.class));
- case MH_decrementCounter:
- return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "decrementCounter",
- MethodType.methodType(int.class, int.class));
case MH_Array_newInstance:
return IMPL_LOOKUP.findStatic(Array.class, "newInstance",
MethodType.methodType(Object.class, Class.class, int.class));
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java Wed Sep 28 03:18:01 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java Wed Sep 28 14:02:21 2016 +0200
@@ -44,8 +44,6 @@
import java.lang.reflect.Modifier;
import java.lang.reflect.ReflectPermission;
import java.nio.ByteOrder;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
@@ -3114,7 +3112,7 @@
* @see MethodHandles#explicitCastArguments
* @since 9
*/
- public static MethodHandle zero(Class<?> type) {
+ public static MethodHandle zero(Class<?> type) {
Objects.requireNonNull(type);
return type.isPrimitive() ? zero(Wrapper.forPrimitiveType(type), type) : zero(Wrapper.OBJECT, type);
}
@@ -3403,7 +3401,8 @@
throw newIllegalArgumentException("illegal pos", pos, newTypes);
}
addTypes = addTypes.subList(pos, add);
- add -= pos; assert(addTypes.size() == add);
+ add -= pos;
+ assert(addTypes.size() == add);
}
// Do not add types which already match the existing arguments.
if (match > add || !oldTypes.equals(addTypes.subList(0, match))) {
@@ -3413,7 +3412,8 @@
throw newIllegalArgumentException("argument lists do not match", oldTypes, newTypes);
}
addTypes = addTypes.subList(match, add);
- add -= match; assert(addTypes.size() == add);
+ add -= match;
+ assert(addTypes.size() == add);
// newTypes: ( P*[pos], M*[match], A*[add] )
// target: ( S*[skip], M*[match] )
MethodHandle adapter = target;
@@ -3423,26 +3423,37 @@
// adapter: (S*[skip], M*[match], A*[add] )
if (pos > 0) {
adapter = dropArguments0(adapter, skip, newTypes.subList(0, pos));
- }
+ }
// adapter: (S*[skip], P*[pos], M*[match], A*[add] )
return adapter;
}
/**
- * Adapts a target method handle to match the given parameter type list, if necessary, by adding dummy arguments.
- * Some leading parameters are first skipped; they will be left unchanged and are otherwise ignored.
- * The remaining types in the target's parameter type list must be contained as a sub-list of the given type list,
- * at the given position.
- * Any non-matching parameter types (before or after the matching sub-list) are inserted in corresponding
- * positions of the target method handle's parameters, as if by {@link #dropArguments}.
- * (More precisely, elements in the new list before {@code pos} are inserted into the target list at {@code skip},
- * while elements in the new list after the match beginning at {@code pos} are inserted at the end of the
- * target list.)
- * The target's return type will be unchanged.
+ * Adapts a target method handle to match the given parameter type list. If necessary, adds dummy arguments. Some
+ * leading parameters can be skipped before matching begins. The remaining types in the {@code target}'s parameter
+ * type list must be a sub-list of the {@code newTypes} type list at the starting position {@code pos}. The
+ * resulting handle will have the target handle's parameter type list, with any non-matching parameter types (before
+ * or after the matching sub-list) inserted in corresponding positions of the target's original parameters, as if by
+ * {@link #dropArguments(MethodHandle, int, Class[])}.
+ * <p>
+ * The resulting handle will have the same return type as the target handle.
+ * <p>
+ * In more formal terms, assume these two type lists:<ul>
+ * <li>The target handle has the parameter type list {@code S..., M...}, with as many types in {@code S} as
+ * indicated by {@code skip}. The {@code M} types are those that are supposed to match part of the given type list,
+ * {@code newTypes}.
+ * <li>The {@code newTypes} list contains types {@code P..., M..., A...}, with as many types in {@code P} as
+ * indicated by {@code pos}. The {@code M} types are precisely those that the {@code M} types in the target handle's
+ * parameter type list are supposed to match. The types in {@code A} are additional types found after the matching
+ * sub-list.
+ * </ul>
+ * Given these assumptions, the result of an invocation of {@code dropArgumentsToMatch} will have the parameter type
+ * list {@code S..., P..., M..., A...}, with the {@code P} and {@code A} types inserted as if by
+ * {@link #dropArguments(MethodHandle, int, Class[])}.
+ * <p>
* @apiNote
- * Two method handles whose argument lists are "effectively identical" (i.e., identical
- * in a common prefix) may be mutually converted to a common type
- * by two calls to {@code dropArgumentsToMatch}, as follows:
+ * Two method handles whose argument lists are "effectively identical" (i.e., identical in a common prefix) may be
+ * mutually converted to a common type by two calls to {@code dropArgumentsToMatch}, as follows:
* <blockquote><pre>{@code
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;
@@ -3461,14 +3472,15 @@
* }</pre></blockquote>
* @param target the method handle to adapt
* @param skip number of targets parameters to disregard (they will be unchanged)
- * @param newTypes the desired argument list of the method handle
+ * @param newTypes the list of types to match {@code target}'s parameter type list to
* @param pos place in {@code newTypes} where the non-skipped target parameters must occur
* @return a possibly adapted method handle
* @throws NullPointerException if either argument is null
* @throws IllegalArgumentException if any element of {@code newTypes} is {@code void.class},
* or if {@code skip} is negative or greater than the arity of the target,
* or if {@code pos} is negative or greater than the newTypes list size,
- * or if the non-skipped target parameter types match the new types at {@code pos}
+ * or if {@code newTypes} does not contain the {@code target}'s non-skipped parameter types at position
+ * {@code pos}.
* @since 9
*/
public static
@@ -3922,6 +3934,113 @@
return foldArguments(target, 0, combiner);
}
+ /**
+ * Adapts a target method handle by pre-processing some of its arguments, starting at a given position, and then
+ * calling the target with the result of the pre-processing, inserted into the original sequence of arguments just
+ * before the folded arguments.
+ * <p>
+ * This method is closely related to {@link #foldArguments(MethodHandle, MethodHandle)}, but allows to control the
+ * position in the parameter list at which folding takes place. The argument controlling this, {@code pos}, is a
+ * zero-based index. The aforementioned method {@link #foldArguments(MethodHandle, MethodHandle)} assumes position
+ * 0.
+ * <p>
+ * @apiNote Example:
+ * <blockquote><pre>{@code
+ import static java.lang.invoke.MethodHandles.*;
+ import static java.lang.invoke.MethodType.*;
+ ...
+ MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
+ "println", methodType(void.class, String.class))
+ .bindTo(System.out);
+ MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+ assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
+ MethodHandle catTrace = foldArguments(cat, 1, trace);
+ // also prints "jum":
+ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
+ * }</pre></blockquote>
+ * <p>Here is pseudocode for the resulting adapter. In the code, {@code T}
+ * represents the result type of the {@code target} and resulting adapter.
+ * {@code V}/{@code v} represent the type and value of the parameter and argument
+ * of {@code target} that precedes the folding position; {@code V} also is
+ * the result type of the {@code combiner}. {@code A}/{@code a} denote the
+ * types and values of the {@code N} parameters and arguments at the folding
+ * position. {@code Z}/{@code z} and {@code B}/{@code b} represent the types
+ * and values of the {@code target} parameters and arguments that precede and
+ * follow the folded parameters and arguments starting at {@code pos},
+ * respectively.
+ * <blockquote><pre>{@code
+ * // there are N arguments in A...
+ * T target(Z..., V, A[N]..., B...);
+ * V combiner(A...);
+ * T adapter(Z... z, A... a, B... b) {
+ * V v = combiner(a...);
+ * return target(z..., v, a..., b...);
+ * }
+ * // and if the combiner has a void return:
+ * T target2(Z..., A[N]..., B...);
+ * void combiner2(A...);
+ * T adapter2(Z... z, A... a, B... b) {
+ * combiner2(a...);
+ * return target2(z..., a..., b...);
+ * }
+ * }</pre></blockquote>
+ * <p>
+ * <em>Note:</em> The resulting adapter is never a {@linkplain MethodHandle#asVarargsCollector
+ * variable-arity method handle}, even if the original target method handle was.
+ *
+ * @param target the method handle to invoke after arguments are combined
+ * @param pos the position at which to start folding and at which to insert the folding result; if this is {@code
+ * 0}, the effect is the same as for {@link #foldArguments(MethodHandle, MethodHandle)}.
+ * @param combiner method handle to call initially on the incoming arguments
+ * @return method handle which incorporates the specified argument folding logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if either of the following two conditions holds:
+ * (1) {@code combiner}'s return type is non-{@code void} and not the same as the argument type at position
+ * {@code pos} of the target signature;
+ * (2) the {@code N} argument types at position {@code pos} of the target signature (skipping one matching
+ * the {@code combiner}'s return type) are not identical with the argument types of {@code combiner}.
+ *
+ * @see #foldArguments(MethodHandle, MethodHandle)
+ * @since 9
+ */
+ public static MethodHandle foldArguments(MethodHandle target, int pos, MethodHandle combiner) {
+ MethodType targetType = target.type();
+ MethodType combinerType = combiner.type();
+ Class<?> rtype = foldArgumentChecks(pos, targetType, combinerType);
+ BoundMethodHandle result = target.rebind();
+ boolean dropResult = rtype == void.class;
+ LambdaForm lform = result.editor().foldArgumentsForm(1 + pos, dropResult, combinerType.basicType());
+ MethodType newType = targetType;
+ if (!dropResult) {
+ newType = newType.dropParameterTypes(pos, pos + 1);
+ }
+ result = result.copyWithExtendL(newType, lform, combiner);
+ return result;
+ }
+
+ /**
+ * As {@see foldArguments(MethodHandle, int, MethodHandle)}, but with the
+ * added capability of selecting the arguments from the targets parameters
+ * to call the combiner with. This allows us to avoid some simple cases of
+ * permutations and padding the combiner with dropArguments to select the
+ * right argument, which may ultimately produce fewer intermediaries.
+ */
+ static MethodHandle foldArguments(MethodHandle target, int pos, MethodHandle combiner, int ... argPositions) {
+ MethodType targetType = target.type();
+ MethodType combinerType = combiner.type();
+ Class<?> rtype = foldArgumentChecks(pos, targetType, combinerType, argPositions);
+ BoundMethodHandle result = target.rebind();
+ boolean dropResult = rtype == void.class;
+ LambdaForm lform = result.editor().foldArgumentsForm(1 + pos, dropResult, combinerType.basicType(), argPositions);
+ MethodType newType = targetType;
+ if (!dropResult) {
+ newType = newType.dropParameterTypes(pos, pos + 1);
+ }
+ result = result.copyWithExtendL(newType, lform, combiner);
+ return result;
+ }
+
private static Class<?> foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) {
int foldArgs = combinerType.parameterCount();
Class<?> rtype = combinerType.returnType();
@@ -4125,32 +4244,69 @@
* iteration. Upon termination of the loop due to one of the predicates, a corresponding finalizer is run and
* delivers the loop's result, which is the return value of the resulting handle.
* <p>
- * Intuitively, every loop is formed by one or more "clauses", each specifying a local iteration value and/or a loop
+ * Intuitively, every loop is formed by one or more "clauses", each specifying a local <em>iteration variable</em> and/or a loop
* exit. Each iteration of the loop executes each clause in order. A clause can optionally update its iteration
* variable; it can also optionally perform a test and conditional loop exit. In order to express this logic in
- * terms of method handles, each clause will determine four actions:<ul>
- * <li>Before the loop executes, the initialization of an iteration variable or loop invariant local.
- * <li>When a clause executes, an update step for the iteration variable.
- * <li>When a clause executes, a predicate execution to test for loop exit.
- * <li>If a clause causes a loop exit, a finalizer execution to compute the loop's return value.
+ * terms of method handles, each clause will specify up to four independent actions:<ul>
+ * <li><em>init:</em> Before the loop executes, the initialization of an iteration variable {@code v} of type {@code V}.
+ * <li><em>step:</em> When a clause executes, an update step for the iteration variable {@code v}.
+ * <li><em>pred:</em> When a clause executes, a predicate execution to test for loop exit.
+ * <li><em>fini:</em> If a clause causes a loop exit, a finalizer execution to compute the loop's return value.
* </ul>
+ * The full sequence of all iteration variable types, in clause order, will be notated as {@code (V...)}.
+ * The values themselves will be {@code (v...)}. When we speak of "parameter lists", we will usually
+ * be referring to types, but in some contexts (describing execution) the lists will be of actual values.
* <p>
* Some of these clause parts may be omitted according to certain rules, and useful default behavior is provided in
* this case. See below for a detailed description.
* <p>
- * Each clause function, with the exception of clause initializers, is able to observe the entire loop state,
- * because it will be passed <em>all</em> current iteration variable values, as well as all incoming loop
- * parameters. Most clause functions will not need all of this information, but they will be formally connected as
- * if by {@link #dropArguments}.
+ * <em>Parameters optional everywhere:</em>
+ * Each clause function is allowed but not required to accept a parameter for each iteration variable {@code v}.
+ * As an exception, the init functions cannot take any {@code v} parameters,
+ * because those values are not yet computed when the init functions are executed.
+ * Any clause function may neglect to take any trailing subsequence of parameters it is entitled to take.
+ * In fact, any clause function may take no arguments at all.
* <p>
+ * <em>Loop parameters:</em>
+ * A clause function may take all the iteration variable values it is entitled to, in which case
+ * it may also take more trailing parameters. Such extra values are called <em>loop parameters</em>,
+ * with their types and values notated as {@code (A...)} and {@code (a...)}.
+ * These become the parameters of the resulting loop handle, to be supplied whenever the loop is executed.
+ * (Since init functions do not accept iteration variables {@code v}, any parameter to an
+ * init function is automatically a loop parameter {@code a}.)
+ * As with iteration variables, clause functions are allowed but not required to accept loop parameters.
+ * These loop parameters act as loop-invariant values visible across the whole loop.
+ * <p>
+ * <em>Parameters visible everywhere:</em>
+ * Each non-init clause function is permitted to observe the entire loop state, because it can be passed the full
+ * list {@code (v... a...)} of current iteration variable values and incoming loop parameters.
+ * The init functions can observe initial pre-loop state, in the form {@code (a...)}.
+ * Most clause functions will not need all of this information, but they will be formally connected to it
+ * as if by {@link #dropArguments}.
+ * <a name="astar"></a>
+ * More specifically, we shall use the notation {@code (V*)} to express an arbitrary prefix of a full
+ * sequence {@code (V...)} (and likewise for {@code (v*)}, {@code (A*)}, {@code (a*)}).
+ * In that notation, the general form of an init function parameter list
+ * is {@code (A*)}, and the general form of a non-init function parameter list is {@code (V*)} or {@code (V... A*)}.
+ * <p>
+ * <em>Checking clause structure:</em>
* Given a set of clauses, there is a number of checks and adjustments performed to connect all the parts of the
* loop. They are spelled out in detail in the steps below. In these steps, every occurrence of the word "must"
- * corresponds to a place where {@link IllegalArgumentException} may be thrown if the required constraint is not met
- * by the inputs to the loop combinator. The term "effectively identical", applied to parameter type lists, means
- * that they must be identical, or else one list must be a proper prefix of the other.
+ * corresponds to a place where {@link IllegalArgumentException} will be thrown if the required constraint is not
+ * met by the inputs to the loop combinator.
+ * <p>
+ * <em>Effectively identical sequences:</em>
+ * <a name="effid"></a>
+ * A parameter list {@code A} is defined to be <em>effectively identical</em> to another parameter list {@code B}
+ * if {@code A} and {@code B} are identical, or if {@code A} is shorter and is identical with a proper prefix of {@code B}.
+ * When speaking of an unordered set of parameter lists, we say they the set is "effectively identical"
+ * as a whole if the set contains a longest list, and all members of the set are effectively identical to
+ * that longest list.
+ * For example, any set of type sequences of the form {@code (V*)} is effectively identical,
+ * and the same is true if more sequences of the form {@code (V... A*)} are added.
* <p>
* <em>Step 0: Determine clause structure.</em><ol type="a">
- * <li>The clause array (of type {@code MethodHandle[][]} must be non-{@code null} and contain at least one element.
+ * <li>The clause array (of type {@code MethodHandle[][]}) must be non-{@code null} and contain at least one element.
* <li>The clause array may not contain {@code null}s or sub-arrays longer than four elements.
* <li>Clauses shorter than four elements are treated as if they were padded by {@code null} elements to length
* four. Padding takes place by appending elements to the array.
@@ -4158,30 +4314,35 @@
* <li>Each clause is treated as a four-tuple of functions, called "init", "step", "pred", and "fini".
* </ol>
* <p>
- * <em>Step 1A: Determine iteration variables.</em><ol type="a">
- * <li>Examine init and step function return types, pairwise, to determine each clause's iteration variable type.
- * <li>If both functions are omitted, use {@code void}; else if one is omitted, use the other's return type; else
- * use the common return type (they must be identical).
+ * <em>Step 1A: Determine iteration variable types {@code (V...)}.</em><ol type="a">
+ * <li>The iteration variable type for each clause is determined using the clause's init and step return types.
+ * <li>If both functions are omitted, there is no iteration variable for the corresponding clause ({@code void} is
+ * used as the type to indicate that). If one of them is omitted, the other's return type defines the clause's
+ * iteration variable type. If both are given, the common return type (they must be identical) defines the clause's
+ * iteration variable type.
* <li>Form the list of return types (in clause order), omitting all occurrences of {@code void}.
- * <li>This list of types is called the "common prefix".
+ * <li>This list of types is called the "iteration variable types" ({@code (V...)}).
* </ol>
* <p>
- * <em>Step 1B: Determine loop parameters.</em><ul>
- * <li><b>If at least one init function is given,</b><ol type="a">
- * <li>Examine init function parameter lists.
- * <li>Omitted init functions are deemed to have {@code null} parameter lists.
- * <li>All init function parameter lists must be effectively identical.
- * <li>The longest parameter list (which is necessarily unique) is called the "common suffix".
- * </ol>
- * <li><b>If no init function is given,</b><ol type="a">
- * <li>Examine the suffixes of the step, pred, and fini parameter lists, after removing the "common prefix".
- * <li>The longest of these suffixes is taken as the "common suffix".
- * </ol></ul>
+ * <em>Step 1B: Determine loop parameters {@code (A...)}.</em><ul>
+ * <li>Examine and collect init function parameter lists (which are of the form {@code (A*)}).
+ * <li>Examine and collect the suffixes of the step, pred, and fini parameter lists, after removing the iteration variable types.
+ * (They must have the form {@code (V... A*)}; collect the {@code (A*)} parts only.)
+ * <li>Do not collect suffixes from step, pred, and fini parameter lists that do not begin with all the iteration variable types.
+ * (These types will checked in step 2, along with all the clause function types.)
+ * <li>Omitted clause functions are ignored. (Equivalently, they are deemed to have empty parameter lists.)
+ * <li>All of the collected parameter lists must be effectively identical.
+ * <li>The longest parameter list (which is necessarily unique) is called the "external parameter list" ({@code (A...)}).
+ * <li>If there is no such parameter list, the external parameter list is taken to be the empty sequence.
+ * <li>The combined list consisting of iteration variable types followed by the external parameter types is called
+ * the "internal parameter list".
+ * </ul>
* <p>
* <em>Step 1C: Determine loop return type.</em><ol type="a">
* <li>Examine fini function return types, disregarding omitted fini functions.
- * <li>If there are no fini functions, use {@code void} as the loop return type.
- * <li>Otherwise, use the common return type of the fini functions; they must all be identical.
+ * <li>If there are no fini functions, the loop return type is {@code void}.
+ * <li>Otherwise, the common return type {@code R} of the fini functions (their return types must be identical) defines the loop return
+ * type.
* </ol>
* <p>
* <em>Step 1D: Check other types.</em><ol type="a">
@@ -4190,69 +4351,107 @@
* </ol>
* <p>
* <em>Step 2: Determine parameter lists.</em><ol type="a">
- * <li>The parameter list for the resulting loop handle will be the "common suffix".
- * <li>The parameter list for init functions will be adjusted to the "common suffix". (Note that their parameter
- * lists are already effectively identical to the common suffix.)
- * <li>The parameter list for non-init (step, pred, and fini) functions will be adjusted to the common prefix
- * followed by the common suffix, called the "common parameter sequence".
- * <li>Every non-init, non-omitted function parameter list must be effectively identical to the common parameter
- * sequence.
+ * <li>The parameter list for the resulting loop handle will be the external parameter list {@code (A...)}.
+ * <li>The parameter list for init functions will be adjusted to the external parameter list.
+ * (Note that their parameter lists are already effectively identical to this list.)
+ * <li>The parameter list for every non-omitted, non-init (step, pred, and fini) function must be
+ * effectively identical to the internal parameter list {@code (V... A...)}.
* </ol>
* <p>
* <em>Step 3: Fill in omitted functions.</em><ol type="a">
- * <li>If an init function is omitted, use a {@linkplain #constant constant function} of the appropriate
- * {@code null}/zero/{@code false}/{@code void} type. (For this purpose, a constant {@code void} is simply a
- * function which does nothing and returns {@code void}; it can be obtained from another constant function by
- * {@linkplain MethodHandle#asType type conversion}.)
+ * <li>If an init function is omitted, use a {@linkplain #empty default value} for the clause's iteration variable
+ * type.
* <li>If a step function is omitted, use an {@linkplain #identity identity function} of the clause's iteration
* variable type; insert dropped argument parameters before the identity function parameter for the non-{@code void}
* iteration variables of preceding clauses. (This will turn the loop variable into a local loop invariant.)
- * <li>If a pred function is omitted, the corresponding fini function must also be omitted.
* <li>If a pred function is omitted, use a constant {@code true} function. (This will keep the loop going, as far
- * as this clause is concerned.)
- * <li>If a fini function is omitted, use a constant {@code null}/zero/{@code false}/{@code void} function of the
+ * as this clause is concerned. Note that in such cases the corresponding fini function is unreachable.)
+ * <li>If a fini function is omitted, use a {@linkplain #empty default value} for the
* loop return type.
* </ol>
* <p>
* <em>Step 4: Fill in missing parameter types.</em><ol type="a">
- * <li>At this point, every init function parameter list is effectively identical to the common suffix, but some
- * lists may be shorter. For every init function with a short parameter list, pad out the end of the list by
- * {@linkplain #dropArguments dropping arguments}.
- * <li>At this point, every non-init function parameter list is effectively identical to the common parameter
- * sequence, but some lists may be shorter. For every non-init function with a short parameter list, pad out the end
- * of the list by {@linkplain #dropArguments dropping arguments}.
+ * <li>At this point, every init function parameter list is effectively identical to the external parameter list {@code (A...)},
+ * but some lists may be shorter. For every init function with a short parameter list, pad out the end of the list.
+ * <li>At this point, every non-init function parameter list is effectively identical to the internal parameter
+ * list {@code (V... A...)}, but some lists may be shorter. For every non-init function with a short parameter list,
+ * pad out the end of the list.
+ * <li>Argument lists are padded out by {@linkplain #dropArgumentsToMatch dropping unused trailing arguments}.
* </ol>
* <p>
* <em>Final observations.</em><ol type="a">
* <li>After these steps, all clauses have been adjusted by supplying omitted functions and arguments.
- * <li>All init functions have a common parameter type list, which the final loop handle will also have.
- * <li>All fini functions have a common return type, which the final loop handle will also have.
- * <li>All non-init functions have a common parameter type list, which is the common parameter sequence, of
- * (non-{@code void}) iteration variables followed by loop parameters.
- * <li>Each pair of init and step functions agrees in their return types.
- * <li>Each non-init function will be able to observe the current values of all iteration variables, by means of the
- * common prefix.
+ * <li>All init functions have a common parameter type list {@code (A...)}, which the final loop handle will also have.
+ * <li>All fini functions have a common return type {@code R}, which the final loop handle will also have.
+ * <li>All non-init functions have a common parameter type list {@code (V... A...)}, of
+ * (non-{@code void}) iteration variables {@code V} followed by loop parameters.
+ * <li>Each pair of init and step functions agrees in their return type {@code V}.
+ * <li>Each non-init function will be able to observe the current values {@code (v...)} of all iteration variables.
+ * <li>Every function will be able to observe the incoming values {@code (a...)} of all loop parameters.
* </ol>
* <p>
+ * <em>Example.</em> As a consequence of step 1A above, the {@code loop} combinator has the following property:
+ * <ul>
+ * <li>Given {@code N} clauses {@code Cn = {null, Sn, Pn}} with {@code n = 1..N}.
+ * <li>Suppose predicate handles {@code Pn} are either {@code null} or have no parameters.
+ * (Only one {@code Pn} has to be non-{@code null}.)
+ * <li>Suppose step handles {@code Sn} have signatures {@code (B1..BX)Rn}, for some constant {@code X>=N}.
+ * <li>Suppose {@code Q} is the count of non-void types {@code Rn}, and {@code (V1...VQ)} is the sequence of those types.
+ * <li>It must be that {@code Vn == Bn} for {@code n = 1..min(X,Q)}.
+ * <li>The parameter types {@code Vn} will be interpreted as loop-local state elements {@code (V...)}.
+ * <li>Any remaining types {@code BQ+1..BX} (if {@code Q<X}) will determine
+ * the resulting loop handle's parameter types {@code (A...)}.
+ * </ul>
+ * In this example, the loop handle parameters {@code (A...)} were derived from the step functions,
+ * which is natural if most of the loop computation happens in the steps. For some loops,
+ * the burden of computation might be heaviest in the pred functions, and so the pred functions
+ * might need to accept the loop parameter values. For loops with complex exit logic, the fini
+ * functions might need to accept loop parameters, and likewise for loops with complex entry logic,
+ * where the init functions will need the extra parameters. For such reasons, the rules for
+ * determining these parameters are as symmetric as possible, across all clause parts.
+ * In general, the loop parameters function as common invariant values across the whole
+ * loop, while the iteration variables function as common variant values, or (if there is
+ * no step function) as internal loop invariant temporaries.
+ * <p>
* <em>Loop execution.</em><ol type="a">
- * <li>When the loop is called, the loop input values are saved in locals, to be passed (as the common suffix) to
+ * <li>When the loop is called, the loop input values are saved in locals, to be passed to
* every clause function. These locals are loop invariant.
- * <li>Each init function is executed in clause order (passing the common suffix) and the non-{@code void} values
- * are saved (as the common prefix) into locals. These locals are loop varying (unless their steps are identity
- * functions, as noted above).
- * <li>All function executions (except init functions) will be passed the common parameter sequence, consisting of
- * the non-{@code void} iteration values (in clause order) and then the loop inputs (in argument order).
+ * <li>Each init function is executed in clause order (passing the external arguments {@code (a...)})
+ * and the non-{@code void} values are saved (as the iteration variables {@code (v...)}) into locals.
+ * These locals will be loop varying (unless their steps behave as identity functions, as noted above).
+ * <li>All function executions (except init functions) will be passed the internal parameter list, consisting of
+ * the non-{@code void} iteration values {@code (v...)} (in clause order) and then the loop inputs {@code (a...)}
+ * (in argument order).
* <li>The step and pred functions are then executed, in clause order (step before pred), until a pred function
* returns {@code false}.
- * <li>The non-{@code void} result from a step function call is used to update the corresponding loop variable. The
- * updated value is immediately visible to all subsequent function calls.
+ * <li>The non-{@code void} result from a step function call is used to update the corresponding value in the
+ * sequence {@code (v...)} of loop variables.
+ * The updated value is immediately visible to all subsequent function calls.
* <li>If a pred function returns {@code false}, the corresponding fini function is called, and the resulting value
- * is returned from the loop as a whole.
+ * (of type {@code R}) is returned from the loop as a whole.
+ * <li>If all the pred functions always return true, no fini function is ever invoked, and the loop cannot exit
+ * except by throwing an exception.
* </ol>
* <p>
- * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the types / values
- * of loop variables; {@code A}/{@code a}, those of arguments passed to the resulting loop; and {@code R}, the
- * result types of finalizers as well as of the resulting loop.
+ * <em>Usage tips.</em>
+ * <ul>
+ * <li>Although each step function will receive the current values of <em>all</em> the loop variables,
+ * sometimes a step function only needs to observe the current value of its own variable.
+ * In that case, the step function may need to explicitly {@linkplain #dropArguments drop all preceding loop variables}.
+ * This will require mentioning their types, in an expression like {@code dropArguments(step, 0, V0.class, ...)}.
+ * <li>Loop variables are not required to vary; they can be loop invariant. A clause can create
+ * a loop invariant by a suitable init function with no step, pred, or fini function. This may be
+ * useful to "wire" an incoming loop argument into the step or pred function of an adjacent loop variable.
+ * <li>If some of the clause functions are virtual methods on an instance, the instance
+ * itself can be conveniently placed in an initial invariant loop "variable", using an initial clause
+ * like {@code new MethodHandle[]{identity(ObjType.class)}}. In that case, the instance reference
+ * will be the first iteration variable value, and it will be easy to use virtual
+ * methods as clause parts, since all of them will take a leading instance reference matching that value.
+ * </ul>
+ * <p>
+ * Here is pseudocode for the resulting loop handle. As above, {@code V} and {@code v} represent the types
+ * and values of loop variables; {@code A} and {@code a} represent arguments passed to the whole loop;
+ * and {@code R} is the common result type of all finalizers as well as of the resulting loop.
* <blockquote><pre>{@code
* V... init...(A...);
* boolean pred...(V..., A...);
@@ -4270,6 +4469,9 @@
* }
* }
* }</pre></blockquote>
+ * Note that the parameter type lists {@code (V...)} and {@code (A...)} have been expanded
+ * to their full length, even though individual clause functions may neglect to take them all.
+ * As noted above, missing parameters are filled in as if by {@link #dropArgumentsToMatch}.
* <p>
* @apiNote Example:
* <blockquote><pre>{@code
@@ -4286,6 +4488,43 @@
* MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
* assertEquals(120, loop.invoke(5));
* }</pre></blockquote>
+ * The same example, dropping arguments and using combinators:
+ * <blockquote><pre>{@code
+ * // simplified implementation of the factorial function as a loop handle
+ * static int inc(int i) { return i + 1; } // drop acc, k
+ * static int mult(int i, int acc) { return i * acc; } //drop k
+ * static boolean cmp(int i, int k) { return i < k; }
+ * // assume MH_inc, MH_mult, and MH_cmp are handles to the above methods
+ * // null initializer for counter, should initialize to 0
+ * MethodHandle MH_one = MethodHandles.constant(int.class, 1);
+ * MethodHandle MH_pred = MethodHandles.dropArguments(MH_cmp, 1, int.class); // drop acc
+ * MethodHandle MH_fin = MethodHandles.dropArguments(MethodHandles.identity(int.class), 0, int.class); // drop i
+ * MethodHandle[] counterClause = new MethodHandle[]{null, MH_inc};
+ * MethodHandle[] accumulatorClause = new MethodHandle[]{MH_one, MH_mult, MH_pred, MH_fin};
+ * MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
+ * assertEquals(720, loop.invoke(6));
+ * }</pre></blockquote>
+ * A similar example, using a helper object to hold a loop parameter:
+ * <blockquote><pre>{@code
+ * // instance-based implementation of the factorial function as a loop handle
+ * static class FacLoop {
+ * final int k;
+ * FacLoop(int k) { this.k = k; }
+ * int inc(int i) { return i + 1; }
+ * int mult(int i, int acc) { return i * acc; }
+ * boolean pred(int i) { return i < k; }
+ * int fin(int i, int acc) { return acc; }
+ * }
+ * // assume MH_FacLoop is a handle to the constructor
+ * // assume MH_inc, MH_mult, MH_pred, and MH_fin are handles to the above methods
+ * // null initializer for counter, should initialize to 0
+ * MethodHandle MH_one = MethodHandles.constant(int.class, 1);
+ * MethodHandle[] instanceClause = new MethodHandle[]{MH_FacLoop};
+ * MethodHandle[] counterClause = new MethodHandle[]{null, MH_inc};
+ * MethodHandle[] accumulatorClause = new MethodHandle[]{MH_one, MH_mult, MH_pred, MH_fin};
+ * MethodHandle loop = MethodHandles.loop(instanceClause, counterClause, accumulatorClause);
+ * assertEquals(5040, loop.invoke(7));
+ * }</pre></blockquote>
*
* @param clauses an array of arrays (4-tuples) of {@link MethodHandle}s adhering to the rules described above.
*
@@ -4301,7 +4540,7 @@
*/
public static MethodHandle loop(MethodHandle[]... clauses) {
// Step 0: determine clause structure.
- checkLoop0(clauses);
+ loopChecks0(clauses);
List<MethodHandle> init = new ArrayList<>();
List<MethodHandle> step = new ArrayList<>();
@@ -4318,7 +4557,7 @@
assert Stream.of(init, step, pred, fini).map(List::size).distinct().count() == 1;
final int nclauses = init.size();
- // Step 1A: determine iteration variables.
+ // Step 1A: determine iteration variables (V...).
final List<Class<?>> iterationVariableTypes = new ArrayList<>();
for (int i = 0; i < nclauses; ++i) {
MethodHandle in = init.get(i);
@@ -4326,7 +4565,7 @@
if (in == null && st == null) {
iterationVariableTypes.add(void.class);
} else if (in != null && st != null) {
- checkLoop1a(i, in, st);
+ loopChecks1a(i, in, st);
iterationVariableTypes.add(in.type().returnType());
} else {
iterationVariableTypes.add(in == null ? st.type().returnType() : in.type().returnType());
@@ -4335,20 +4574,20 @@
final List<Class<?>> commonPrefix = iterationVariableTypes.stream().filter(t -> t != void.class).
collect(Collectors.toList());
- // Step 1B: determine loop parameters.
+ // Step 1B: determine loop parameters (A...).
final List<Class<?>> commonSuffix = buildCommonSuffix(init, step, pred, fini, commonPrefix.size());
- checkLoop1b(init, commonSuffix);
+ loopChecks1b(init, commonSuffix);
// Step 1C: determine loop return type.
// Step 1D: check other types.
final Class<?> loopReturnType = fini.stream().filter(Objects::nonNull).map(MethodHandle::type).
map(MethodType::returnType).findFirst().orElse(void.class);
- checkLoop1cd(pred, fini, loopReturnType);
+ loopChecks1cd(pred, fini, loopReturnType);
// Step 2: determine parameter lists.
final List<Class<?>> commonParameterSequence = new ArrayList<>(commonPrefix);
commonParameterSequence.addAll(commonSuffix);
- checkLoop2(step, pred, fini, commonParameterSequence);
+ loopChecks2(step, pred, fini, commonParameterSequence);
// Step 3: fill in omitted functions.
for (int i = 0; i < nclauses; ++i) {
@@ -4382,6 +4621,79 @@
return MethodHandleImpl.makeLoop(loopReturnType, commonSuffix, finit, fstep, fpred, ffini);
}
+ private static void loopChecks0(MethodHandle[][] clauses) {
+ if (clauses == null || clauses.length == 0) {
+ throw newIllegalArgumentException("null or no clauses passed");
+ }
+ if (Stream.of(clauses).anyMatch(Objects::isNull)) {
+ throw newIllegalArgumentException("null clauses are not allowed");
+ }
+ if (Stream.of(clauses).anyMatch(c -> c.length > 4)) {
+ throw newIllegalArgumentException("All loop clauses must be represented as MethodHandle arrays with at most 4 elements.");
+ }
+ }
+
+ private static void loopChecks1a(int i, MethodHandle in, MethodHandle st) {
+ if (in.type().returnType() != st.type().returnType()) {
+ throw misMatchedTypes("clause " + i + ": init and step return types", in.type().returnType(),
+ st.type().returnType());
+ }
+ }
+
+ private static List<Class<?>> longestParameterList(Stream<MethodHandle> mhs, int skipSize) {
+ final List<Class<?>> empty = List.of();
+ final List<Class<?>> longest = mhs.filter(Objects::nonNull).
+ // take only those that can contribute to a common suffix because they are longer than the prefix
+ map(MethodHandle::type).
+ filter(t -> t.parameterCount() > skipSize).
+ map(MethodType::parameterList).
+ reduce((p, q) -> p.size() >= q.size() ? p : q).orElse(empty);
+ return longest.size() == 0 ? empty : longest.subList(skipSize, longest.size());
+ }
+
+ private static List<Class<?>> longestParameterList(List<List<Class<?>>> lists) {
+ final List<Class<?>> empty = List.of();
+ return lists.stream().reduce((p, q) -> p.size() >= q.size() ? p : q).orElse(empty);
+ }
+
+ private static List<Class<?>> buildCommonSuffix(List<MethodHandle> init, List<MethodHandle> step, List<MethodHandle> pred, List<MethodHandle> fini, int cpSize) {
+ final List<Class<?>> longest1 = longestParameterList(Stream.of(step, pred, fini).flatMap(List::stream), cpSize);
+ final List<Class<?>> longest2 = longestParameterList(init.stream(), 0);
+ return longestParameterList(Arrays.asList(longest1, longest2));
+ }
+
+ private static void loopChecks1b(List<MethodHandle> init, List<Class<?>> commonSuffix) {
+ if (init.stream().filter(Objects::nonNull).map(MethodHandle::type).
+ anyMatch(t -> !t.effectivelyIdenticalParameters(0, commonSuffix))) {
+ throw newIllegalArgumentException("found non-effectively identical init parameter type lists: " + init +
+ " (common suffix: " + commonSuffix + ")");
+ }
+ }
+
+ private static void loopChecks1cd(List<MethodHandle> pred, List<MethodHandle> fini, Class<?> loopReturnType) {
+ if (fini.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType).
+ anyMatch(t -> t != loopReturnType)) {
+ throw newIllegalArgumentException("found non-identical finalizer return types: " + fini + " (return type: " +
+ loopReturnType + ")");
+ }
+
+ if (!pred.stream().filter(Objects::nonNull).findFirst().isPresent()) {
+ throw newIllegalArgumentException("no predicate found", pred);
+ }
+ if (pred.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType).
+ anyMatch(t -> t != boolean.class)) {
+ throw newIllegalArgumentException("predicates must have boolean return type", pred);
+ }
+ }
+
+ private static void loopChecks2(List<MethodHandle> step, List<MethodHandle> pred, List<MethodHandle> fini, List<Class<?>> commonParameterSequence) {
+ if (Stream.of(step, pred, fini).flatMap(List::stream).filter(Objects::nonNull).map(MethodHandle::type).
+ anyMatch(t -> !t.effectivelyIdenticalParameters(0, commonParameterSequence))) {
+ throw newIllegalArgumentException("found non-effectively identical parameter type lists:\nstep: " + step +
+ "\npred: " + pred + "\nfini: " + fini + " (common parameter sequence: " + commonParameterSequence + ")");
+ }
+ }
+
private static List<MethodHandle> fillParameterTypes(List<MethodHandle> hs, final List<Class<?>> targetParams) {
return hs.stream().map(h -> {
int pc = h.type().parameterCount();
@@ -4395,26 +4707,60 @@
}
/**
- * Constructs a {@code while} loop from an initializer, a body, and a predicate. This is a convenience wrapper for
- * the {@linkplain #loop(MethodHandle[][]) generic loop combinator}.
+ * Constructs a {@code while} loop from an initializer, a body, and a predicate.
+ * This is a convenience wrapper for the {@linkplain #loop(MethodHandle[][]) generic loop combinator}.
+ * <p>
+ * The {@code pred} handle describes the loop condition; and {@code body}, its body. The loop resulting from this
+ * method will, in each iteration, first evaluate the predicate and then execute its body (if the predicate
+ * evaluates to {@code true}).
+ * The loop will terminate once the predicate evaluates to {@code false} (the body will not be executed in this case).
+ * <p>
+ * The {@code init} handle describes the initial value of an additional optional loop-local variable.
+ * In each iteration, this loop-local variable, if present, will be passed to the {@code body}
+ * and updated with the value returned from its invocation. The result of loop execution will be
+ * the final value of the additional loop-local variable (if present).
* <p>
- * The loop handle's result type is the same as the sole loop variable's, i.e., the result type of {@code init}.
- * The parameter type list of {@code init} also determines that of the resulting handle. The {@code pred} handle
- * must have an additional leading parameter of the same type as {@code init}'s result, and so must the {@code
- * body}. These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][])
- * generic loop combinator}.
+ * The following rules hold for these argument handles:<ul>
+ * <li>The {@code body} handle must not be {@code null}; its type must be of the form
+ * {@code (V A...)V}, where {@code V} is non-{@code void}, or else {@code (A...)void}.
+ * (In the {@code void} case, we assign the type {@code void} to the name {@code V},
+ * and we will write {@code (V A...)V} with the understanding that a {@code void} type {@code V}
+ * is quietly dropped from the parameter list, leaving {@code (A...)V}.)
+ * <li>The parameter list {@code (V A...)} of the body is called the <em>internal parameter list</em>.
+ * It will constrain the parameter lists of the other loop parts.
+ * <li>If the iteration variable type {@code V} is dropped from the internal parameter list, the resulting shorter
+ * list {@code (A...)} is called the <em>external parameter list</em>.
+ * <li>The body return type {@code V}, if non-{@code void}, determines the type of an
+ * additional state variable of the loop.
+ * The body must both accept and return a value of this type {@code V}.
+ * <li>If {@code init} is non-{@code null}, it must have return type {@code V}.
+ * Its parameter list (of some <a href="MethodHandles.html#astar">form {@code (A*)}</a>) must be
+ * <a href="MethodHandles.html#effid">effectively identical</a>
+ * to the external parameter list {@code (A...)}.
+ * <li>If {@code init} is {@code null}, the loop variable will be initialized to its
+ * {@linkplain #empty default value}.
+ * <li>The {@code pred} handle must not be {@code null}. It must have {@code boolean} as its return type.
+ * Its parameter list (either empty or of the form {@code (V A*)}) must be
+ * effectively identical to the internal parameter list.
+ * </ul>
+ * <p>
+ * The resulting loop handle's result type and parameter signature are determined as follows:<ul>
+ * <li>The loop handle's result type is the result type {@code V} of the body.
+ * <li>The loop handle's parameter types are the types {@code (A...)},
+ * from the external parameter list.
+ * </ul>
* <p>
* Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
* the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
* passed to the loop.
* <blockquote><pre>{@code
- * V init(A);
- * boolean pred(V, A);
- * V body(V, A);
- * V whileLoop(A a) {
- * V v = init(a);
- * while (pred(v, a)) {
- * v = body(v, a);
+ * V init(A...);
+ * boolean pred(V, A...);
+ * V body(V, A...);
+ * V whileLoop(A... a...) {
+ * V v = init(a...);
+ * while (pred(v, a...)) {
+ * v = body(v, a...);
* }
* return v;
* }
@@ -4439,58 +4785,96 @@
* }</pre></blockquote>
*
* <p>
- * @implSpec The implementation of this method is equivalent to:
+ * @apiNote The implementation of this method can be expressed as follows:
* <blockquote><pre>{@code
* MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body) {
+ * MethodHandle fini = (body.type().returnType() == void.class
+ * ? null : identity(body.type().returnType()));
* MethodHandle[]
- * checkExit = {null, null, pred, identity(init.type().returnType())},
- * varBody = {init, body};
+ * checkExit = { null, null, pred, fini },
+ * varBody = { init, body };
* return loop(checkExit, varBody);
* }
* }</pre></blockquote>
*
- * @param init initializer: it should provide the initial value of the loop variable. This controls the loop's
- * result type. Passing {@code null} or a {@code void} init function will make the loop's result type
- * {@code void}.
- * @param pred condition for the loop, which may not be {@code null}.
- * @param body body of the loop, which may not be {@code null}.
+ * @param init optional initializer, providing the initial value of the loop variable.
+ * May be {@code null}, implying a default initial value. See above for other constraints.
+ * @param pred condition for the loop, which may not be {@code null}. Its result type must be {@code boolean}. See
+ * above for other constraints.
+ * @param body body of the loop, which may not be {@code null}. It controls the loop parameters and result type.
+ * See above for other constraints.
*
- * @return the value of the loop variable as the loop terminates.
- * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
+ * @return a method handle implementing the {@code while} loop as described by the arguments.
+ * @throws IllegalArgumentException if the rules for the arguments are violated.
+ * @throws NullPointerException if {@code pred} or {@code body} are {@code null}.
*
- * @see MethodHandles#loop(MethodHandle[][])
+ * @see #loop(MethodHandle[][])
+ * @see #doWhileLoop(MethodHandle, MethodHandle, MethodHandle)
* @since 9
*/
public static MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body) {
- MethodHandle fin = init == null || init.type().returnType() == void.class ? zero(void.class) :
- identity(init.type().returnType());
- MethodHandle[] checkExit = {null, null, pred, fin};
- MethodHandle[] varBody = {init, body};
+ whileLoopChecks(init, pred, body);
+ MethodHandle fini = identityOrVoid(body.type().returnType());
+ MethodHandle[] checkExit = { null, null, pred, fini };
+ MethodHandle[] varBody = { init, body };
return loop(checkExit, varBody);
}
/**
- * Constructs a {@code do-while} loop from an initializer, a body, and a predicate. This is a convenience wrapper
- * for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}.
+ * Constructs a {@code do-while} loop from an initializer, a body, and a predicate.
+ * This is a convenience wrapper for the {@linkplain #loop(MethodHandle[][]) generic loop combinator}.
+ * <p>
+ * The {@code pred} handle describes the loop condition; and {@code body}, its body. The loop resulting from this
+ * method will, in each iteration, first execute its body and then evaluate the predicate.
+ * The loop will terminate once the predicate evaluates to {@code false} after an execution of the body.
+ * <p>
+ * The {@code init} handle describes the initial value of an additional optional loop-local variable.
+ * In each iteration, this loop-local variable, if present, will be passed to the {@code body}
+ * and updated with the value returned from its invocation. The result of loop execution will be
+ * the final value of the additional loop-local variable (if present).
* <p>
- * The loop handle's result type is the same as the sole loop variable's, i.e., the result type of {@code init}.
- * The parameter type list of {@code init} also determines that of the resulting handle. The {@code pred} handle
- * must have an additional leading parameter of the same type as {@code init}'s result, and so must the {@code
- * body}. These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][])
- * generic loop combinator}.
+ * The following rules hold for these argument handles:<ul>
+ * <li>The {@code body} handle must not be {@code null}; its type must be of the form
+ * {@code (V A...)V}, where {@code V} is non-{@code void}, or else {@code (A...)void}.
+ * (In the {@code void} case, we assign the type {@code void} to the name {@code V},
+ * and we will write {@code (V A...)V} with the understanding that a {@code void} type {@code V}
+ * is quietly dropped from the parameter list, leaving {@code (A...)V}.)
+ * <li>The parameter list {@code (V A...)} of the body is called the <em>internal parameter list</em>.
+ * It will constrain the parameter lists of the other loop parts.
+ * <li>If the iteration variable type {@code V} is dropped from the internal parameter list, the resulting shorter
+ * list {@code (A...)} is called the <em>external parameter list</em>.
+ * <li>The body return type {@code V}, if non-{@code void}, determines the type of an
+ * additional state variable of the loop.
+ * The body must both accept and return a value of this type {@code V}.
+ * <li>If {@code init} is non-{@code null}, it must have return type {@code V}.
+ * Its parameter list (of some <a href="MethodHandles.html#astar">form {@code (A*)}</a>) must be
+ * <a href="MethodHandles.html#effid">effectively identical</a>
+ * to the external parameter list {@code (A...)}.
+ * <li>If {@code init} is {@code null}, the loop variable will be initialized to its
+ * {@linkplain #empty default value}.
+ * <li>The {@code pred} handle must not be {@code null}. It must have {@code boolean} as its return type.
+ * Its parameter list (either empty or of the form {@code (V A*)}) must be
+ * effectively identical to the internal parameter list.
+ * </ul>
+ * <p>
+ * The resulting loop handle's result type and parameter signature are determined as follows:<ul>
+ * <li>The loop handle's result type is the result type {@code V} of the body.
+ * <li>The loop handle's parameter types are the types {@code (A...)},
+ * from the external parameter list.
+ * </ul>
* <p>
* Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
* the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
* passed to the loop.
* <blockquote><pre>{@code
- * V init(A);
- * boolean pred(V, A);
- * V body(V, A);
- * V doWhileLoop(A a) {
- * V v = init(a);
+ * V init(A...);
+ * boolean pred(V, A...);
+ * V body(V, A...);
+ * V doWhileLoop(A... a...) {
+ * V v = init(a...);
* do {
- * v = body(v, a);
- * } while (pred(v, a));
+ * v = body(v, a...);
+ * } while (pred(v, a...));
* return v;
* }
* }</pre></blockquote>
@@ -4507,59 +4891,491 @@
* }</pre></blockquote>
*
* <p>
- * @implSpec The implementation of this method is equivalent to:
+ * @apiNote The implementation of this method can be expressed as follows:
* <blockquote><pre>{@code
* MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred) {
- * MethodHandle[] clause = { init, body, pred, identity(init.type().returnType()) };
+ * MethodHandle fini = (body.type().returnType() == void.class
+ * ? null : identity(body.type().returnType()));
+ * MethodHandle[] clause = { init, body, pred, fini };
* return loop(clause);
* }
* }</pre></blockquote>
*
+ * @param init optional initializer, providing the initial value of the loop variable.
+ * May be {@code null}, implying a default initial value. See above for other constraints.
+ * @param body body of the loop, which may not be {@code null}. It controls the loop parameters and result type.
+ * See above for other constraints.
+ * @param pred condition for the loop, which may not be {@code null}. Its result type must be {@code boolean}. See
+ * above for other constraints.
*
- * @param init initializer: it should provide the initial value of the loop variable. This controls the loop's
- * result type. Passing {@code null} or a {@code void} init function will make the loop's result type
- * {@code void}.
- * @param pred condition for the loop, which may not be {@code null}.
- * @param body body of the loop, which may not be {@code null}.
+ * @return a method handle implementing the {@code while} loop as described by the arguments.
+ * @throws IllegalArgumentException if the rules for the arguments are violated.
+ * @throws NullPointerException if {@code pred} or {@code body} are {@code null}.
*
- * @return the value of the loop variable as the loop terminates.
- * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
- *
- * @see MethodHandles#loop(MethodHandle[][])
+ * @see #loop(MethodHandle[][])
+ * @see #whileLoop(MethodHandle, MethodHandle, MethodHandle)
* @since 9
*/
public static MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred) {
- MethodHandle fin = init == null || init.type().returnType() == void.class ? zero(void.class) :
- identity(init.type().returnType());
- MethodHandle[] clause = {init, body, pred, fin};
+ whileLoopChecks(init, pred, body);
+ MethodHandle fini = identityOrVoid(body.type().returnType());
+ MethodHandle[] clause = {init, body, pred, fini };
return loop(clause);
}
+ private static void whileLoopChecks(MethodHandle init, MethodHandle pred, MethodHandle body) {
+ Objects.requireNonNull(pred);
+ Objects.requireNonNull(body);
+ MethodType bodyType = body.type();
+ Class<?> returnType = bodyType.returnType();
+ List<Class<?>> innerList = bodyType.parameterList();
+ List<Class<?>> outerList = innerList;
+ if (returnType == void.class) {
+ // OK
+ } else if (innerList.size() == 0 || innerList.get(0) != returnType) {
+ // leading V argument missing => error
+ MethodType expected = bodyType.insertParameterTypes(0, returnType);
+ throw misMatchedTypes("body function", bodyType, expected);
+ } else {
+ outerList = innerList.subList(1, innerList.size());
+ }
+ MethodType predType = pred.type();
+ if (predType.returnType() != boolean.class ||
+ !predType.effectivelyIdenticalParameters(0, innerList)) {
+ throw misMatchedTypes("loop predicate", predType, methodType(boolean.class, innerList));
+ }
+ if (init != null) {
+ MethodType initType = init.type();
+ if (initType.returnType() != returnType ||
+ !initType.effectivelyIdenticalParameters(0, outerList)) {
+ throw misMatchedTypes("loop initializer", initType, methodType(returnType, outerList));
+ }
+ }
+ }
+
+ /**
+ * Constructs a loop that runs a given number of iterations.
+ * This is a convenience wrapper for the {@linkplain #loop(MethodHandle[][]) generic loop combinator}.
+ * <p>
+ * The number of iterations is determined by the {@code iterations} handle evaluation result.
+ * The loop counter {@code i} is an extra loop iteration variable of type {@code int}.
+ * It will be initialized to 0 and incremented by 1 in each iteration.
+ * <p>
+ * If the {@code body} handle returns a non-{@code void} type {@code V}, a leading loop iteration variable
+ * of that type is also present. This variable is initialized using the optional {@code init} handle,
+ * or to the {@linkplain #empty default value} of type {@code V} if that handle is {@code null}.
+ * <p>
+ * In each iteration, the iteration variables are passed to an invocation of the {@code body} handle.
+ * A non-{@code void} value returned from the body (of type {@code V}) updates the leading
+ * iteration variable.
+ * The result of the loop handle execution will be the final {@code V} value of that variable
+ * (or {@code void} if there is no {@code V} variable).
+ * <p>
+ * The following rules hold for the argument handles:<ul>
+ * <li>The {@code iterations} handle must not be {@code null}, and must return
+ * the type {@code int}, referred to here as {@code I} in parameter type lists.
+ * <li>The {@code body} handle must not be {@code null}; its type must be of the form
+ * {@code (V I A...)V}, where {@code V} is non-{@code void}, or else {@code (I A...)void}.
+ * (In the {@code void} case, we assign the type {@code void} to the name {@code V},
+ * and we will write {@code (V I A...)V} with the understanding that a {@code void} type {@code V}
+ * is quietly dropped from the parameter list, leaving {@code (I A...)V}.)
+ * <li>The parameter list {@code (V I A...)} of the body contributes to a list
+ * of types called the <em>internal parameter list</em>.
+ * It will constrain the parameter lists of the other loop parts.
+ * <li>As a special case, if the body contributes only {@code V} and {@code I} types,
+ * with no additional {@code A} types, then the internal parameter list is extended by
+ * the argument types {@code A...} of the {@code iterations} handle.
+ * <li>If the iteration variable types {@code (V I)} are dropped from the internal parameter list, the resulting shorter
+ * list {@code (A...)} is called the <em>external parameter list</em>.
+ * <li>The body return type {@code V}, if non-{@code void}, determines the type of an
+ * additional state variable of the loop.
+ * The body must both accept a leading parameter and return a value of this type {@code V}.
+ * <li>If {@code init} is non-{@code null}, it must have return type {@code V}.
+ * Its parameter list (of some <a href="MethodHandles.html#astar">form {@code (A*)}</a>) must be
+ * <a href="MethodHandles.html#effid">effectively identical</a>
+ * to the external parameter list {@code (A...)}.
+ * <li>If {@code init} is {@code null}, the loop variable will be initialized to its
+ * {@linkplain #empty default value}.
+ * <li>The parameter list of {@code iterations} (of some form {@code (A*)}) must be
+ * effectively identical to the external parameter list {@code (A...)}.
+ * </ul>
+ * <p>
+ * The resulting loop handle's result type and parameter signature are determined as follows:<ul>
+ * <li>The loop handle's result type is the result type {@code V} of the body.
+ * <li>The loop handle's parameter types are the types {@code (A...)},
+ * from the external parameter list.
+ * </ul>
+ * <p>
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
+ * the second loop variable as well as the result type of the loop; and {@code A...}/{@code a...} represent
+ * arguments passed to the loop.
+ * <blockquote><pre>{@code
+ * int iterations(A...);
+ * V init(A...);
+ * V body(V, int, A...);
+ * V countedLoop(A... a...) {
+ * int end = iterations(a...);
+ * V v = init(a...);
+ * for (int i = 0; i < end; ++i) {
+ * v = body(v, i, a...);
+ * }
+ * return v;
+ * }
+ * }</pre></blockquote>
+ * <p>
+ * @apiNote Example with a fully conformant body method:
+ * <blockquote><pre>{@code
+ * // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s;
+ * // => a variation on a well known theme
+ * static String step(String v, int counter, String init) { return "na " + v; }
+ * // assume MH_step is a handle to the method above
+ * MethodHandle fit13 = MethodHandles.constant(int.class, 13);
+ * MethodHandle start = MethodHandles.identity(String.class);
+ * MethodHandle loop = MethodHandles.countedLoop(fit13, start, MH_step);
+ * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
+ * }</pre></blockquote>
+ * <p>
+ * @apiNote Example with the simplest possible body method type,
+ * and passing the number of iterations to the loop invocation:
+ * <blockquote><pre>{@code
+ * // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s;
+ * // => a variation on a well known theme
+ * static String step(String v, int counter ) { return "na " + v; }
+ * // assume MH_step is a handle to the method above
+ * MethodHandle count = MethodHandles.dropArguments(MethodHandles.identity(int.class), 1, String.class);
+ * MethodHandle start = MethodHandles.dropArguments(MethodHandles.identity(String.class), 0, int.class);
+ * MethodHandle loop = MethodHandles.countedLoop(count, start, MH_step); // (v, i) -> "na " + v
+ * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke(13, "Lambdaman!"));
+ * }</pre></blockquote>
+ * <p>
+ * @apiNote Example that treats the number of iterations, string to append to, and string to append
+ * as loop parameters:
+ * <blockquote><pre>{@code
+ * // String s = "Lambdaman!", t = "na"; for (int i = 0; i < 13; ++i) { s = t + " " + s; } return s;
+ * // => a variation on a well known theme
+ * static String step(String v, int counter, int iterations_, String pre, String start_) { return pre + " " + v; }
+ * // assume MH_step is a handle to the method above
+ * MethodHandle count = MethodHandles.identity(int.class);
+ * MethodHandle start = MethodHandles.dropArguments(MethodHandles.identity(String.class), 0, int.class, String.class);
+ * MethodHandle loop = MethodHandles.countedLoop(count, start, MH_step); // (v, i, _, pre, _) -> pre + " " + v
+ * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke(13, "na", "Lambdaman!"));
+ * }</pre></blockquote>
+ * <p>
+ * @apiNote Example that illustrates the usage of {@link #dropArgumentsToMatch(MethodHandle, int, List, int)}
+ * to enforce a loop type:
+ * <blockquote><pre>{@code
+ * // String s = "Lambdaman!", t = "na"; for (int i = 0; i < 13; ++i) { s = t + " " + s; } return s;
+ * // => a variation on a well known theme
+ * static String step(String v, int counter, String pre) { return pre + " " + v; }
+ * // assume MH_step is a handle to the method above
+ * MethodType loopType = methodType(String.class, String.class, int.class, String.class);
+ * MethodHandle count = MethodHandles.dropArgumentsToMatch(MethodHandles.identity(int.class), 0, loopType.parameterList(), 1);
+ * MethodHandle start = MethodHandles.dropArgumentsToMatch(MethodHandles.identity(String.class), 0, loopType.parameterList(), 2);
+ * MethodHandle body = MethodHandles.dropArgumentsToMatch(MH_step, 2, loopType.parameterList(), 0);
+ * MethodHandle loop = MethodHandles.countedLoop(count, start, body); // (v, i, pre, _, _) -> pre + " " + v
+ * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("na", 13, "Lambdaman!"));
+ * }</pre></blockquote>
+ * <p>
+ * @apiNote The implementation of this method can be expressed as follows:
+ * <blockquote><pre>{@code
+ * MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) {
+ * return countedLoop(empty(iterations.type()), iterations, init, body);
+ * }
+ * }</pre></blockquote>
+ *
+ * @param iterations a non-{@code null} handle to return the number of iterations this loop should run. The handle's
+ * result type must be {@code int}. See above for other constraints.
+ * @param init optional initializer, providing the initial value of the loop variable.
+ * May be {@code null}, implying a default initial value. See above for other constraints.
+ * @param body body of the loop, which may not be {@code null}.
+ * It controls the loop parameters and result type in the standard case (see above for details).
+ * It must accept its own return type (if non-void) plus an {@code int} parameter (for the counter),
+ * and may accept any number of additional types.
+ * See above for other constraints.
+ *
+ * @return a method handle representing the loop.
+ * @throws NullPointerException if either of the {@code iterations} or {@code body} handles is {@code null}.
+ * @throws IllegalArgumentException if any argument violates the rules formulated above.
+ *
+ * @see #countedLoop(MethodHandle, MethodHandle, MethodHandle, MethodHandle)
+ * @since 9
+ */
+ public static MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) {
+ return countedLoop(empty(iterations.type()), iterations, init, body);
+ }
+
/**
- * Constructs a loop that runs a given number of iterations. The loop counter is an {@code int} initialized from the
- * {@code iterations} handle evaluation result. The counter is passed to the {@code body} function, so that must
- * accept an initial {@code int} argument. The result of the loop execution is the final value of the additional
- * local state. This is a convenience wrapper for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop
- * combinator}.
+ * Constructs a loop that counts over a range of numbers.
+ * This is a convenience wrapper for the {@linkplain #loop(MethodHandle[][]) generic loop combinator}.
+ * <p>
+ * The loop counter {@code i} is a loop iteration variable of type {@code int}.
+ * The {@code start} and {@code end} handles determine the start (inclusive) and end (exclusive)
+ * values of the loop counter.
+ * The loop counter will be initialized to the {@code int} value returned from the evaluation of the
+ * {@code start} handle and run to the value returned from {@code end} (exclusively) with a step width of 1.
+ * <p>
+ * If the {@code body} handle returns a non-{@code void} type {@code V}, a leading loop iteration variable
+ * of that type is also present. This variable is initialized using the optional {@code init} handle,
+ * or to the {@linkplain #empty default value} of type {@code V} if that handle is {@code null}.
+ * <p>
+ * In each iteration, the iteration variables are passed to an invocation of the {@code body} handle.
+ * A non-{@code void} value returned from the body (of type {@code V}) updates the leading
+ * iteration variable.
+ * The result of the loop handle execution will be the final {@code V} value of that variable
+ * (or {@code void} if there is no {@code V} variable).
* <p>
- * The result type and parameter type list of {@code init} determine those of the resulting handle. The {@code
- * iterations} handle must accept the same parameter types as {@code init} but return an {@code int}. The {@code
- * body} handle must accept the same parameter types as well, preceded by an {@code int} parameter for the counter,
- * and a parameter of the same type as {@code init}'s result. These constraints follow directly from those described
- * for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}.
+ * The following rules hold for the argument handles:<ul>
+ * <li>The {@code start} and {@code end} handles must not be {@code null}, and must both return
+ * the common type {@code int}, referred to here as {@code I} in parameter type lists.
+ * <li>The {@code body} handle must not be {@code null}; its type must be of the form
+ * {@code (V I A...)V}, where {@code V} is non-{@code void}, or else {@code (I A...)void}.
+ * (In the {@code void} case, we assign the type {@code void} to the name {@code V},
+ * and we will write {@code (V I A...)V} with the understanding that a {@code void} type {@code V}
+ * is quietly dropped from the parameter list, leaving {@code (I A...)V}.)
+ * <li>The parameter list {@code (V I A...)} of the body contributes to a list
+ * of types called the <em>internal parameter list</em>.
+ * It will constrain the parameter lists of the other loop parts.
+ * <li>As a special case, if the body contributes only {@code V} and {@code I} types,
+ * with no additional {@code A} types, then the internal parameter list is extended by
+ * the argument types {@code A...} of the {@code end} handle.
+ * <li>If the iteration variable types {@code (V I)} are dropped from the internal parameter list, the resulting shorter
+ * list {@code (A...)} is called the <em>external parameter list</em>.
+ * <li>The body return type {@code V}, if non-{@code void}, determines the type of an
+ * additional state variable of the loop.
+ * The body must both accept a leading parameter and return a value of this type {@code V}.
+ * <li>If {@code init} is non-{@code null}, it must have return type {@code V}.
+ * Its parameter list (of some <a href="MethodHandles.html#astar">form {@code (A*)}</a>) must be
+ * <a href="MethodHandles.html#effid">effectively identical</a>
+ * to the external parameter list {@code (A...)}.
+ * <li>If {@code init} is {@code null}, the loop variable will be initialized to its
+ * {@linkplain #empty default value}.
+ * <li>The parameter list of {@code start} (of some form {@code (A*)}) must be
+ * effectively identical to the external parameter list {@code (A...)}.
+ * <li>Likewise, the parameter list of {@code end} must be effectively identical
+ * to the external parameter list.
+ * </ul>
+ * <p>
+ * The resulting loop handle's result type and parameter signature are determined as follows:<ul>
+ * <li>The loop handle's result type is the result type {@code V} of the body.
+ * <li>The loop handle's parameter types are the types {@code (A...)},
+ * from the external parameter list.
+ * </ul>
* <p>
* Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
- * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
- * passed to the loop.
+ * the second loop variable as well as the result type of the loop; and {@code A...}/{@code a...} represent
+ * arguments passed to the loop.
+ * <blockquote><pre>{@code
+ * int start(A...);
+ * int end(A...);
+ * V init(A...);
+ * V body(V, int, A...);
+ * V countedLoop(A... a...) {
+ * int e = end(a...);
+ * int s = start(a...);
+ * V v = init(a...);
+ * for (int i = s; i < e; ++i) {
+ * v = body(v, i, a...);
+ * }
+ * return v;
+ * }
+ * }</pre></blockquote>
+ *
+ * <p>
+ * @apiNote The implementation of this method can be expressed as follows:
* <blockquote><pre>{@code
- * int iterations(A);
- * V init(A);
- * V body(int, V, A);
- * V countedLoop(A a) {
- * int end = iterations(a);
- * V v = init(a);
- * for (int i = 0; i < end; ++i) {
- * v = body(i, v, a);
+ * MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
+ * MethodHandle returnVar = dropArguments(identity(init.type().returnType()), 0, int.class, int.class);
+ * // assume MH_increment and MH_predicate are handles to implementation-internal methods with
+ * // the following semantics:
+ * // MH_increment: (int limit, int counter) -> counter + 1
+ * // MH_predicate: (int limit, int counter) -> counter < limit
+ * Class<?> counterType = start.type().returnType(); // int
+ * Class<?> returnType = body.type().returnType();
+ * MethodHandle incr = MH_increment, pred = MH_predicate, retv = null;
+ * if (returnType != void.class) { // ignore the V variable
+ * incr = dropArguments(incr, 1, returnType); // (limit, v, i) => (limit, i)
+ * pred = dropArguments(pred, 1, returnType); // ditto
+ * retv = dropArguments(identity(returnType), 0, counterType); // ignore limit
+ * }
+ * body = dropArguments(body, 0, counterType); // ignore the limit variable
+ * MethodHandle[]
+ * loopLimit = { end, null, pred, retv }, // limit = end(); i < limit || return v
+ * bodyClause = { init, body }, // v = init(); v = body(v, i)
+ * indexVar = { start, incr }; // i = start(); i = i + 1
+ * return loop(loopLimit, bodyClause, indexVar);
+ * }
+ * }</pre></blockquote>
+ *
+ * @param start a non-{@code null} handle to return the start value of the loop counter, which must be {@code int}.
+ * See above for other constraints.
+ * @param end a non-{@code null} handle to return the end value of the loop counter (the loop will run to
+ * {@code end-1}). The result type must be {@code int}. See above for other constraints.
+ * @param init optional initializer, providing the initial value of the loop variable.
+ * May be {@code null}, implying a default initial value. See above for other constraints.
+ * @param body body of the loop, which may not be {@code null}.
+ * It controls the loop parameters and result type in the standard case (see above for details).
+ * It must accept its own return type (if non-void) plus an {@code int} parameter (for the counter),
+ * and may accept any number of additional types.
+ * See above for other constraints.
+ *
+ * @return a method handle representing the loop.
+ * @throws NullPointerException if any of the {@code start}, {@code end}, or {@code body} handles is {@code null}.
+ * @throws IllegalArgumentException if any argument violates the rules formulated above.
+ *
+ * @see #countedLoop(MethodHandle, MethodHandle, MethodHandle)
+ * @since 9
+ */
+ public static MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
+ countedLoopChecks(start, end, init, body);
+ Class<?> counterType = start.type().returnType(); // int, but who's counting?
+ Class<?> limitType = end.type().returnType(); // yes, int again
+ Class<?> returnType = body.type().returnType();
+ MethodHandle incr = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopStep);
+ MethodHandle pred = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopPred);
+ MethodHandle retv = null;
+ if (returnType != void.class) {
+ incr = dropArguments(incr, 1, returnType); // (limit, v, i) => (limit, i)
+ pred = dropArguments(pred, 1, returnType); // ditto
+ retv = dropArguments(identity(returnType), 0, counterType);
+ }
+ body = dropArguments(body, 0, counterType); // ignore the limit variable
+ MethodHandle[]
+ loopLimit = { end, null, pred, retv }, // limit = end(); i < limit || return v
+ bodyClause = { init, body }, // v = init(); v = body(v, i)
+ indexVar = { start, incr }; // i = start(); i = i + 1
+ return loop(loopLimit, bodyClause, indexVar);
+ }
+
+ private static void countedLoopChecks(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
+ Objects.requireNonNull(start);
+ Objects.requireNonNull(end);
+ Objects.requireNonNull(body);
+ Class<?> counterType = start.type().returnType();
+ if (counterType != int.class) {
+ MethodType expected = start.type().changeReturnType(int.class);
+ throw misMatchedTypes("start function", start.type(), expected);
+ } else if (end.type().returnType() != counterType) {
+ MethodType expected = end.type().changeReturnType(counterType);
+ throw misMatchedTypes("end function", end.type(), expected);
+ }
+ MethodType bodyType = body.type();
+ Class<?> returnType = bodyType.returnType();
+ List<Class<?>> innerList = bodyType.parameterList();
+ // strip leading V value if present
+ int vsize = (returnType == void.class ? 0 : 1);
+ if (vsize != 0 && (innerList.size() == 0 || innerList.get(0) != returnType)) {
+ // argument list has no "V" => error
+ MethodType expected = bodyType.insertParameterTypes(0, returnType);
+ throw misMatchedTypes("body function", bodyType, expected);
+ } else if (innerList.size() <= vsize || innerList.get(vsize) != counterType) {
+ // missing I type => error
+ MethodType expected = bodyType.insertParameterTypes(vsize, counterType);
+ throw misMatchedTypes("body function", bodyType, expected);
+ }
+ List<Class<?>> outerList = innerList.subList(vsize + 1, innerList.size());
+ if (outerList.isEmpty()) {
+ // special case; take lists from end handle
+ outerList = end.type().parameterList();
+ innerList = bodyType.insertParameterTypes(vsize + 1, outerList).parameterList();
+ }
+ MethodType expected = methodType(counterType, outerList);
+ if (!start.type().effectivelyIdenticalParameters(0, outerList)) {
+ throw misMatchedTypes("start parameter types", start.type(), expected);
+ }
+ if (end.type() != start.type() &&
+ !end.type().effectivelyIdenticalParameters(0, outerList)) {
+ throw misMatchedTypes("end parameter types", end.type(), expected);
+ }
+ if (init != null) {
+ MethodType initType = init.type();
+ if (initType.returnType() != returnType ||
+ !initType.effectivelyIdenticalParameters(0, outerList)) {
+ throw misMatchedTypes("loop initializer", initType, methodType(returnType, outerList));
+ }
+ }
+ }
+
+ /**
+ * Constructs a loop that ranges over the values produced by an {@code Iterator<T>}.
+ * This is a convenience wrapper for the {@linkplain #loop(MethodHandle[][]) generic loop combinator}.
+ * <p>
+ * The iterator itself will be determined by the evaluation of the {@code iterator} handle.
+ * Each value it produces will be stored in a loop iteration variable of type {@code T}.
+ * <p>
+ * If the {@code body} handle returns a non-{@code void} type {@code V}, a leading loop iteration variable
+ * of that type is also present. This variable is initialized using the optional {@code init} handle,
+ * or to the {@linkplain #empty default value} of type {@code V} if that handle is {@code null}.
+ * <p>
+ * In each iteration, the iteration variables are passed to an invocation of the {@code body} handle.
+ * A non-{@code void} value returned from the body (of type {@code V}) updates the leading
+ * iteration variable.
+ * The result of the loop handle execution will be the final {@code V} value of that variable
+ * (or {@code void} if there is no {@code V} variable).
+ * <p>
+ * The following rules hold for the argument handles:<ul>
+ * <li>The {@code body} handle must not be {@code null}; its type must be of the form
+ * {@code (V T A...)V}, where {@code V} is non-{@code void}, or else {@code (T A...)void}.
+ * (In the {@code void} case, we assign the type {@code void} to the name {@code V},
+ * and we will write {@code (V T A...)V} with the understanding that a {@code void} type {@code V}
+ * is quietly dropped from the parameter list, leaving {@code (T A...)V}.)
+ * <li>The parameter list {@code (V T A...)} of the body contributes to a list
+ * of types called the <em>internal parameter list</em>.
+ * It will constrain the parameter lists of the other loop parts.
+ * <li>As a special case, if the body contributes only {@code V} and {@code T} types,
+ * with no additional {@code A} types, then the internal parameter list is extended by
+ * the argument types {@code A...} of the {@code iterator} handle; if it is {@code null} the
+ * single type {@code Iterable} is added and constitutes the {@code A...} list.
+ * <li>If the iteration variable types {@code (V T)} are dropped from the internal parameter list, the resulting shorter
+ * list {@code (A...)} is called the <em>external parameter list</em>.
+ * <li>The body return type {@code V}, if non-{@code void}, determines the type of an
+ * additional state variable of the loop.
+ * The body must both accept a leading parameter and return a value of this type {@code V}.
+ * <li>If {@code init} is non-{@code null}, it must have return type {@code V}.
+ * Its parameter list (of some <a href="MethodHandles.html#astar">form {@code (A*)}</a>) must be
+ * <a href="MethodHandles.html#effid">effectively identical</a>
+ * to the external parameter list {@code (A...)}.
+ * <li>If {@code init} is {@code null}, the loop variable will be initialized to its
+ * {@linkplain #empty default value}.
+ * <li>If the {@code iterator} handle is non-{@code null}, it must have the return
+ * type {@code java.util.Iterator} or a subtype thereof.
+ * The iterator it produces when the loop is executed will be assumed
+ * to yield values which can be converted to type {@code T}.
+ * <li>The parameter list of an {@code iterator} that is non-{@code null} (of some form {@code (A*)}) must be
+ * effectively identical to the external parameter list {@code (A...)}.
+ * <li>If {@code iterator} is {@code null} it defaults to a method handle which behaves
+ * like {@link java.lang.Iterable#iterator()}. In that case, the internal parameter list
+ * {@code (V T A...)} must have at least one {@code A} type, and the default iterator
+ * handle parameter is adjusted to accept the leading {@code A} type, as if by
+ * the {@link MethodHandle#asType asType} conversion method.
+ * The leading {@code A} type must be {@code Iterable} or a subtype thereof, or an array type.
+ * This conversion step, done at loop construction time, must not throw a {@code WrongMethodTypeException}.
+ * </ul>
+ * <p>
+ * The type {@code T} may be either a primitive or reference.
+ * Since type {@code Iterator<T>} is erased in the method handle representation to the raw type {@code Iterator},
+ * the {@code iteratedLoop} combinator adjusts the leading argument type for {@code body} to {@code Object}
+ * as if by the {@link MethodHandle#asType asType} conversion method.
+ * Therefore, if an iterator of the wrong type appears as the loop is executed, runtime exceptions may occur
+ * as the result of dynamic conversions performed by {@link MethodHandle#asType(MethodType)}.
+ * <p>
+ * The resulting loop handle's result type and parameter signature are determined as follows:<ul>
+ * <li>The loop handle's result type is the result type {@code V} of the body.
+ * <li>The loop handle's parameter types are the types {@code (A...)},
+ * from the external parameter list.
+ * </ul>
+ * <p>
+ * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
+ * the loop variable as well as the result type of the loop; {@code T}/{@code t}, that of the elements of the
+ * structure the loop iterates over, and {@code A...}/{@code a...} represent arguments passed to the loop.
+ * <blockquote><pre>{@code
+ * Iterator<T> iterator(A...); // defaults to Iterable::iterator
+ * V init(A...);
+ * V body(V,T,A...);
+ * V iteratedLoop(A... a...) {
+ * Iterator<T> it = iterator(a...);
+ * V v = init(a...);
+ * for (T t : it) {
+ * v = body(v, t, a...);
* }
* return v;
* }
@@ -4567,243 +5383,164 @@
* <p>
* @apiNote Example:
* <blockquote><pre>{@code
- * // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s;
- * // => a variation on a well known theme
- * static String start(String arg) { return arg; }
- * static String step(int counter, String v, String arg) { return "na " + v; }
- * // assume MH_start and MH_step are handles to the two methods above
- * MethodHandle fit13 = MethodHandles.constant(int.class, 13);
- * MethodHandle loop = MethodHandles.countedLoop(fit13, MH_start, MH_step);
- * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
- * }</pre></blockquote>
- *
- * <p>
- * @implSpec The implementation of this method is equivalent to:
- * <blockquote><pre>{@code
- * MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) {
- * return countedLoop(null, iterations, init, body); // null => constant zero
- * }
- * }</pre></blockquote>
- *
- * @param iterations a handle to return the number of iterations this loop should run.
- * @param init initializer for additional loop state. This determines the loop's result type.
- * Passing {@code null} or a {@code void} init function will make the loop's result type
- * {@code void}.
- * @param body the body of the loop, which must not be {@code null}.
- * It must accept an initial {@code int} parameter (for the counter), and then any
- * additional loop-local variable plus loop parameters.
- *
- * @return a method handle representing the loop.
- * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
- *
- * @since 9
- */
- public static MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) {
- return countedLoop(null, iterations, init, body);
- }
-
- /**
- * Constructs a loop that counts over a range of numbers. The loop counter is an {@code int} that will be
- * initialized to the {@code int} value returned from the evaluation of the {@code start} handle and run to the
- * value returned from {@code end} (exclusively) with a step width of 1. The counter value is passed to the {@code
- * body} function in each iteration; it has to accept an initial {@code int} parameter
- * for that. The result of the loop execution is the final value of the additional local state
- * obtained by running {@code init}.
- * This is a
- * convenience wrapper for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}.
- * <p>
- * The constraints for the {@code init} and {@code body} handles are the same as for {@link
- * #countedLoop(MethodHandle, MethodHandle, MethodHandle)}. Additionally, the {@code start} and {@code end} handles
- * must return an {@code int} and accept the same parameters as {@code init}.
- * <p>
- * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
- * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument
- * passed to the loop.
- * <blockquote><pre>{@code
- * int start(A);
- * int end(A);
- * V init(A);
- * V body(int, V, A);
- * V countedLoop(A a) {
- * int s = start(a);
- * int e = end(a);
- * V v = init(a);
- * for (int i = s; i < e; ++i) {
- * v = body(i, v, a);
- * }
- * return v;
- * }
- * }</pre></blockquote>
- *
- * <p>
- * @implSpec The implementation of this method is equivalent to:
- * <blockquote><pre>{@code
- * MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
- * MethodHandle returnVar = dropArguments(identity(init.type().returnType()), 0, int.class, int.class);
- * // assume MH_increment and MH_lessThan are handles to x+1 and x<y of type int,
- * // assume MH_decrement is a handle to x-1 of type int
- * MethodHandle[]
- * indexVar = {start, MH_increment}, // i = start; i = i+1
- * loopLimit = {end, null,
- * filterArgument(MH_lessThan, 0, MH_decrement), returnVar}, // i-1<end
- * bodyClause = {init,
- * filterArgument(dropArguments(body, 1, int.class), 0, MH_decrement}; // v = body(i-1, v)
- * return loop(indexVar, loopLimit, bodyClause);
- * }
- * }</pre></blockquote>
- *
- * @param start a handle to return the start value of the loop counter.
- * If it is {@code null}, a constant zero is assumed.
- * @param end a non-{@code null} handle to return the end value of the loop counter (the loop will run to {@code end-1}).
- * @param init initializer for additional loop state. This determines the loop's result type.
- * Passing {@code null} or a {@code void} init function will make the loop's result type
- * {@code void}.
- * @param body the body of the loop, which must not be {@code null}.
- * It must accept an initial {@code int} parameter (for the counter), and then any
- * additional loop-local variable plus loop parameters.
- *
- * @return a method handle representing the loop.
- * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
- *
- * @since 9
- */
- public static MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
- Class<?> resultType;
- MethodHandle actualInit;
- if (init == null) {
- resultType = body == null ? void.class : body.type().returnType();
- actualInit = empty(methodType(resultType));
- } else {
- resultType = init.type().returnType();
- actualInit = init;
- }
- MethodHandle defaultResultHandle = resultType == void.class ? zero(void.class) : identity(resultType);
- MethodHandle actualBody = body == null ? dropArguments(defaultResultHandle, 0, int.class) : body;
- MethodHandle returnVar = dropArguments(defaultResultHandle, 0, int.class, int.class);
- MethodHandle actualEnd = end == null ? constant(int.class, 0) : end;
- MethodHandle decr = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_decrementCounter);
- MethodHandle[] indexVar = {start, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopStep)};
- MethodHandle[] loopLimit = {actualEnd, null,
- filterArgument(MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopPred), 0, decr),
- returnVar};
- MethodHandle[] bodyClause = {actualInit, filterArgument(dropArguments(actualBody, 1, int.class), 0, decr)};
- return loop(indexVar, loopLimit, bodyClause);
- }
-
- /**
- * Constructs a loop that ranges over the elements produced by an {@code Iterator<T>}.
- * The iterator will be produced by the evaluation of the {@code iterator} handle.
- * This handle must have {@link java.util.Iterator} as its return type.
- * If this handle is passed as {@code null} the method {@link Iterable#iterator} will be used instead,
- * and will be applied to a leading argument of the loop handle.
- * Each value produced by the iterator is passed to the {@code body}, which must accept an initial {@code T} parameter.
- * The result of the loop execution is the final value of the additional local state
- * obtained by running {@code init}.
- * <p>
- * This is a convenience wrapper for the
- * {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}, and the constraints imposed on the {@code body}
- * handle follow directly from those described for the latter.
- * <p>
- * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of
- * the loop variable as well as the result type of the loop; {@code T}/{@code t}, that of the elements of the
- * structure the loop iterates over, and {@code A}/{@code a}, that of the argument passed to the loop.
- * <blockquote><pre>{@code
- * Iterator<T> iterator(A); // defaults to Iterable::iterator
- * V init(A);
- * V body(T,V,A);
- * V iteratedLoop(A a) {
- * Iterator<T> it = iterator(a);
- * V v = init(a);
- * for (T t : it) {
- * v = body(t, v, a);
- * }
- * return v;
- * }
- * }</pre></blockquote>
- * <p>
- * The type {@code T} may be either a primitive or reference.
- * Since type {@code Iterator<T>} is erased in the method handle representation to the raw type
- * {@code Iterator}, the {@code iteratedLoop} combinator adjusts the leading argument type for {@code body}
- * to {@code Object} as if by the {@link MethodHandle#asType asType} conversion method.
- * Therefore, if an iterator of the wrong type appears as the loop is executed,
- * runtime exceptions may occur as the result of dynamic conversions performed by {@code asType}.
- * <p>
- * @apiNote Example:
- * <blockquote><pre>{@code
- * // reverse a list
- * static List<String> reverseStep(String e, List<String> r, List<String> l) {
+ * // get an iterator from a list
+ * static List<String> reverseStep(List<String> r, String e) {
* r.add(0, e);
* return r;
* }
- * static List<String> newArrayList(List<String> l) { return new ArrayList<>(); }
- * // assume MH_reverseStep, MH_newArrayList are handles to the above methods
+ * static List<String> newArrayList() { return new ArrayList<>(); }
+ * // assume MH_reverseStep and MH_newArrayList are handles to the above methods
* MethodHandle loop = MethodHandles.iteratedLoop(null, MH_newArrayList, MH_reverseStep);
* List<String> list = Arrays.asList("a", "b", "c", "d", "e");
* List<String> reversedList = Arrays.asList("e", "d", "c", "b", "a");
* assertEquals(reversedList, (List<String>) loop.invoke(list));
* }</pre></blockquote>
* <p>
- * @implSpec The implementation of this method is equivalent to (excluding error handling):
+ * @apiNote The implementation of this method can be expressed approximately as follows:
* <blockquote><pre>{@code
* MethodHandle iteratedLoop(MethodHandle iterator, MethodHandle init, MethodHandle body) {
- * // assume MH_next and MH_hasNext are handles to methods of Iterator
- * Class<?> itype = iterator.type().returnType();
- * Class<?> ttype = body.type().parameterType(0);
- * MethodHandle returnVar = dropArguments(identity(init.type().returnType()), 0, itype);
+ * // assume MH_next, MH_hasNext, MH_startIter are handles to methods of Iterator/Iterable
+ * Class<?> returnType = body.type().returnType();
+ * Class<?> ttype = body.type().parameterType(returnType == void.class ? 0 : 1);
* MethodHandle nextVal = MH_next.asType(MH_next.type().changeReturnType(ttype));
+ * MethodHandle retv = null, step = body, startIter = iterator;
+ * if (returnType != void.class) {
+ * // the simple thing first: in (I V A...), drop the I to get V
+ * retv = dropArguments(identity(returnType), 0, Iterator.class);
+ * // body type signature (V T A...), internal loop types (I V A...)
+ * step = swapArguments(body, 0, 1); // swap V <-> T
+ * }
+ * if (startIter == null) startIter = MH_getIter;
* MethodHandle[]
- * iterVar = {iterator, null, MH_hasNext, returnVar}, // it = iterator(); while (it.hasNext)
- * bodyClause = {init, filterArgument(body, 0, nextVal)}; // v = body(t, v, a);
+ * iterVar = { startIter, null, MH_hasNext, retv }, // it = iterator; while (it.hasNext())
+ * bodyClause = { init, filterArguments(step, 0, nextVal) }; // v = body(v, t, a)
* return loop(iterVar, bodyClause);
* }
* }</pre></blockquote>
*
- * @param iterator a handle to return the iterator to start the loop.
- * The handle must have {@link java.util.Iterator} as its return type.
- * Passing {@code null} will make the loop call {@link Iterable#iterator()} on the first
- * incoming value.
- * @param init initializer for additional loop state. This determines the loop's result type.
- * Passing {@code null} or a {@code void} init function will make the loop's result type
- * {@code void}.
- * @param body the body of the loop, which must not be {@code null}.
- * It must accept an initial {@code T} parameter (for the iterated values), and then any
- * additional loop-local variable plus loop parameters.
+ * @param iterator an optional handle to return the iterator to start the loop.
+ * If non-{@code null}, the handle must return {@link java.util.Iterator} or a subtype.
+ * See above for other constraints.
+ * @param init optional initializer, providing the initial value of the loop variable.
+ * May be {@code null}, implying a default initial value. See above for other constraints.
+ * @param body body of the loop, which may not be {@code null}.
+ * It controls the loop parameters and result type in the standard case (see above for details).
+ * It must accept its own return type (if non-void) plus a {@code T} parameter (for the iterated values),
+ * and may accept any number of additional types.
+ * See above for other constraints.
*
* @return a method handle embodying the iteration loop functionality.
- * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure
+ * @throws NullPointerException if the {@code body} handle is {@code null}.
+ * @throws IllegalArgumentException if any argument violates the above requirements.
*
* @since 9
*/
public static MethodHandle iteratedLoop(MethodHandle iterator, MethodHandle init, MethodHandle body) {
- checkIteratedLoop(iterator, body);
- Class<?> resultType = init == null ?
- body == null ? void.class : body.type().returnType() :
- init.type().returnType();
- boolean voidResult = resultType == void.class;
-
- MethodHandle initIterator;
- if (iterator == null) {
- MethodHandle initit = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_initIterator);
- initIterator = initit.asType(initit.type().changeParameterType(0,
- body.type().parameterType(voidResult ? 1 : 2)));
- } else {
- initIterator = iterator.asType(iterator.type().changeReturnType(Iterator.class));
+ Class<?> iterableType = iteratedLoopChecks(iterator, init, body);
+ Class<?> returnType = body.type().returnType();
+ MethodHandle hasNext = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iteratePred);
+ MethodHandle nextRaw = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iterateNext);
+ MethodHandle startIter;
+ MethodHandle nextVal;
+ {
+ MethodType iteratorType;
+ if (iterator == null) {
+ // derive argument type from body, if available, else use Iterable
+ startIter = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_initIterator);
+ iteratorType = startIter.type().changeParameterType(0, iterableType);
+ } else {
+ // force return type to the internal iterator class
+ iteratorType = iterator.type().changeReturnType(Iterator.class);
+ startIter = iterator;
+ }
+ Class<?> ttype = body.type().parameterType(returnType == void.class ? 0 : 1);
+ MethodType nextValType = nextRaw.type().changeReturnType(ttype);
+
+ // perform the asType transforms under an exception transformer, as per spec.:
+ try {
+ startIter = startIter.asType(iteratorType);
+ nextVal = nextRaw.asType(nextValType);
+ } catch (WrongMethodTypeException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ MethodHandle retv = null, step = body;
+ if (returnType != void.class) {
+ // the simple thing first: in (I V A...), drop the I to get V
+ retv = dropArguments(identity(returnType), 0, Iterator.class);
+ // body type signature (V T A...), internal loop types (I V A...)
+ step = swapArguments(body, 0, 1); // swap V <-> T
}
- Class<?> ttype = body.type().parameterType(0);
-
- MethodHandle returnVar =
- dropArguments(voidResult ? zero(void.class) : identity(resultType), 0, Iterator.class);
- MethodHandle initnx = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iterateNext);
- MethodHandle nextVal = initnx.asType(initnx.type().changeReturnType(ttype));
-
- MethodHandle[] iterVar = {initIterator, null, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iteratePred),
- returnVar};
- MethodHandle[] bodyClause = {init, filterArgument(body, 0, nextVal)};
-
+ MethodHandle[]
+ iterVar = { startIter, null, hasNext, retv },
+ bodyClause = { init, filterArgument(step, 0, nextVal) };
return loop(iterVar, bodyClause);
}
+ private static Class<?> iteratedLoopChecks(MethodHandle iterator, MethodHandle init, MethodHandle body) {
+ Objects.requireNonNull(body);
+ MethodType bodyType = body.type();
+ Class<?> returnType = bodyType.returnType();
+ List<Class<?>> innerList = bodyType.parameterList();
+ // strip leading V value if present
+ int vsize = (returnType == void.class ? 0 : 1);
+ if (vsize != 0 && (innerList.size() == 0 || innerList.get(0) != returnType)) {
+ // argument list has no "V" => error
+ MethodType expected = bodyType.insertParameterTypes(0, returnType);
+ throw misMatchedTypes("body function", bodyType, expected);
+ } else if (innerList.size() <= vsize) {
+ // missing T type => error
+ MethodType expected = bodyType.insertParameterTypes(vsize, Object.class);
+ throw misMatchedTypes("body function", bodyType, expected);
+ }
+ //Class<?> elementType = innerList.get(vsize); // do not need this
+ List<Class<?>> outerList = innerList.subList(vsize + 1, innerList.size());
+ if (outerList.isEmpty()) {
+ // special case; take lists from iterator handle
+ outerList = ((iterator != null)
+ ? iterator.type().parameterList()
+ : Arrays.asList(Iterable.class));
+ innerList = bodyType.insertParameterTypes(vsize + 1, outerList).parameterList();
+ }
+ if (iterator != null) {
+ MethodType itype = iterator.type();
+ if (!Iterator.class.isAssignableFrom(itype.returnType())) {
+ throw newIllegalArgumentException("iteratedLoop first argument must have Iterator return type");
+ }
+ if (!itype.effectivelyIdenticalParameters(0, outerList)) {
+ MethodType expected = methodType(itype.returnType(), outerList);
+ throw misMatchedTypes("iterator parameters", itype, expected);
+ }
+ }
+ if (init != null) {
+ MethodType initType = init.type();
+ if (initType.returnType() != returnType ||
+ !initType.effectivelyIdenticalParameters(0, outerList)) {
+ throw misMatchedTypes("loop initializer", initType, methodType(returnType, outerList));
+ }
+ }
+ Class<?> iterableType = outerList.isEmpty() ? null : outerList.get(0);
+ if (iterableType != null && !Iterable.class.isAssignableFrom(iterableType) && !iterableType.isArray()) {
+ throw newIllegalArgumentException(
+ "inferred first loop argument must be an array or inherit from Iterable: " + iterableType);
+ }
+ return iterableType; // help the caller a bit
+ }
+
+ /*non-public*/ static MethodHandle swapArguments(MethodHandle mh, int i, int j) {
+ // there should be a better way to uncross my wires
+ int arity = mh.type().parameterCount();
+ int[] order = new int[arity];
+ for (int k = 0; k < arity; k++) order[k] = k;
+ order[i] = j; order[j] = i;
+ Class<?>[] types = mh.type().parameterArray();
+ Class<?> ti = types[i]; types[i] = types[j]; types[j] = ti;
+ MethodType swapType = methodType(mh.type().returnType(), types);
+ return permuteArguments(mh, swapType, order);
+ }
+
/**
* Makes a method handle that adapts a {@code target} method handle by wrapping it in a {@code try-finally} block.
* Another method handle, {@code cleanup}, represents the functionality of the {@code finally} block. Any exception
@@ -4885,7 +5622,7 @@
List<Class<?>> cleanupParamTypes = cleanup.type().parameterList();
Class<?> rtype = target.type().returnType();
- checkTryFinally(target, cleanup);
+ tryFinallyChecks(target, cleanup);
// Match parameter lists: if the cleanup has a shorter parameter list than the target, add ignored arguments.
// The cleanup parameter list (minus the leading Throwable and result parameters) must be a sublist of the
@@ -4896,210 +5633,22 @@
return MethodHandleImpl.makeTryFinally(target.asFixedArity(), cleanup.asFixedArity(), rtype, targetParamTypes);
}
- /**
- * Adapts a target method handle by pre-processing some of its arguments, starting at a given position, and then
- * calling the target with the result of the pre-processing, inserted into the original sequence of arguments just
- * before the folded arguments.
- * <p>
- * This method is closely related to {@link #foldArguments(MethodHandle, MethodHandle)}, but allows to control the
- * position in the parameter list at which folding takes place. The argument controlling this, {@code pos}, is a
- * zero-based index. The aforementioned method {@link #foldArguments(MethodHandle, MethodHandle)} assumes position
- * 0.
- * <p>
- * @apiNote Example:
- * <blockquote><pre>{@code
- import static java.lang.invoke.MethodHandles.*;
- import static java.lang.invoke.MethodType.*;
- ...
- MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
- "println", methodType(void.class, String.class))
- .bindTo(System.out);
- MethodHandle cat = lookup().findVirtual(String.class,
- "concat", methodType(String.class, String.class));
- assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
- MethodHandle catTrace = foldArguments(cat, 1, trace);
- // also prints "jum":
- assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
- * }</pre></blockquote>
- * <p>Here is pseudocode for the resulting adapter. In the code, {@code T}
- * represents the result type of the {@code target} and resulting adapter.
- * {@code V}/{@code v} represent the type and value of the parameter and argument
- * of {@code target} that precedes the folding position; {@code V} also is
- * the result type of the {@code combiner}. {@code A}/{@code a} denote the
- * types and values of the {@code N} parameters and arguments at the folding
- * position. {@code Z}/{@code z} and {@code B}/{@code b} represent the types
- * and values of the {@code target} parameters and arguments that precede and
- * follow the folded parameters and arguments starting at {@code pos},
- * respectively.
- * <blockquote><pre>{@code
- * // there are N arguments in A...
- * T target(Z..., V, A[N]..., B...);
- * V combiner(A...);
- * T adapter(Z... z, A... a, B... b) {
- * V v = combiner(a...);
- * return target(z..., v, a..., b...);
- * }
- * // and if the combiner has a void return:
- * T target2(Z..., A[N]..., B...);
- * void combiner2(A...);
- * T adapter2(Z... z, A... a, B... b) {
- * combiner2(a...);
- * return target2(z..., a..., b...);
- * }
- * }</pre></blockquote>
- * <p>
- * <em>Note:</em> The resulting adapter is never a {@linkplain MethodHandle#asVarargsCollector
- * variable-arity method handle}, even if the original target method handle was.
- *
- * @param target the method handle to invoke after arguments are combined
- * @param pos the position at which to start folding and at which to insert the folding result; if this is {@code
- * 0}, the effect is the same as for {@link #foldArguments(MethodHandle, MethodHandle)}.
- * @param combiner method handle to call initially on the incoming arguments
- * @return method handle which incorporates the specified argument folding logic
- * @throws NullPointerException if either argument is null
- * @throws IllegalArgumentException if {@code combiner}'s return type
- * is non-void and not the same as the argument type at position {@code pos} of
- * the target signature, or if the {@code N} argument types at position {@code pos}
- * of the target signature
- * (skipping one matching the {@code combiner}'s return type)
- * are not identical with the argument types of {@code combiner}
- *
- * @see #foldArguments(MethodHandle, MethodHandle)
- * @since 9
- */
- public static MethodHandle foldArguments(MethodHandle target, int pos, MethodHandle combiner) {
- MethodType targetType = target.type();
- MethodType combinerType = combiner.type();
- Class<?> rtype = foldArgumentChecks(pos, targetType, combinerType);
- BoundMethodHandle result = target.rebind();
- boolean dropResult = rtype == void.class;
- LambdaForm lform = result.editor().foldArgumentsForm(1 + pos, dropResult, combinerType.basicType());
- MethodType newType = targetType;
- if (!dropResult) {
- newType = newType.dropParameterTypes(pos, pos + 1);
- }
- result = result.copyWithExtendL(newType, lform, combiner);
- return result;
- }
-
- /**
- * As {@see foldArguments(MethodHandle, int, MethodHandle)}, but with the
- * added capability of selecting the arguments from the targets parameters
- * to call the combiner with. This allows us to avoid some simple cases of
- * permutations and padding the combiner with dropArguments to select the
- * right argument, which may ultimately produce fewer intermediaries.
- */
- static MethodHandle foldArguments(MethodHandle target, int pos, MethodHandle combiner, int ... argPositions) {
- MethodType targetType = target.type();
- MethodType combinerType = combiner.type();
- Class<?> rtype = foldArgumentChecks(pos, targetType, combinerType, argPositions);
- BoundMethodHandle result = target.rebind();
- boolean dropResult = rtype == void.class;
- LambdaForm lform = result.editor().foldArgumentsForm(1 + pos, dropResult, combinerType.basicType(), argPositions);
- MethodType newType = targetType;
- if (!dropResult) {
- newType = newType.dropParameterTypes(pos, pos + 1);
- }
- result = result.copyWithExtendL(newType, lform, combiner);
- return result;
- }
-
- private static void checkLoop0(MethodHandle[][] clauses) {
- if (clauses == null || clauses.length == 0) {
- throw newIllegalArgumentException("null or no clauses passed");
- }
- if (Stream.of(clauses).anyMatch(Objects::isNull)) {
- throw newIllegalArgumentException("null clauses are not allowed");
- }
- if (Stream.of(clauses).anyMatch(c -> c.length > 4)) {
- throw newIllegalArgumentException("All loop clauses must be represented as MethodHandle arrays with at most 4 elements.");
- }
- }
-
- private static void checkLoop1a(int i, MethodHandle in, MethodHandle st) {
- if (in.type().returnType() != st.type().returnType()) {
- throw misMatchedTypes("clause " + i + ": init and step return types", in.type().returnType(),
- st.type().returnType());
- }
- }
-
- private static List<Class<?>> buildCommonSuffix(List<MethodHandle> init, List<MethodHandle> step, List<MethodHandle> pred, List<MethodHandle> fini, int cpSize) {
- final List<Class<?>> empty = List.of();
- final List<MethodHandle> nonNullInits = init.stream().filter(Objects::nonNull).collect(Collectors.toList());
- if (nonNullInits.isEmpty()) {
- final List<Class<?>> longest = Stream.of(step, pred, fini).flatMap(List::stream).filter(Objects::nonNull).
- // take only those that can contribute to a common suffix because they are longer than the prefix
- map(MethodHandle::type).filter(t -> t.parameterCount() > cpSize).map(MethodType::parameterList).
- reduce((p, q) -> p.size() >= q.size() ? p : q).orElse(empty);
- return longest.size() == 0 ? empty : longest.subList(cpSize, longest.size());
- } else {
- return nonNullInits.stream().map(MethodHandle::type).map(MethodType::parameterList).
- reduce((p, q) -> p.size() >= q.size() ? p : q).get();
- }
- }
-
- private static void checkLoop1b(List<MethodHandle> init, List<Class<?>> commonSuffix) {
- if (init.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::parameterList).
- anyMatch(pl -> !pl.equals(commonSuffix.subList(0, pl.size())))) {
- throw newIllegalArgumentException("found non-effectively identical init parameter type lists: " + init +
- " (common suffix: " + commonSuffix + ")");
- }
- }
-
- private static void checkLoop1cd(List<MethodHandle> pred, List<MethodHandle> fini, Class<?> loopReturnType) {
- if (fini.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType).
- anyMatch(t -> t != loopReturnType)) {
- throw newIllegalArgumentException("found non-identical finalizer return types: " + fini + " (return type: " +
- loopReturnType + ")");
- }
-
- if (!pred.stream().filter(Objects::nonNull).findFirst().isPresent()) {
- throw newIllegalArgumentException("no predicate found", pred);
- }
- if (pred.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType).
- anyMatch(t -> t != boolean.class)) {
- throw newIllegalArgumentException("predicates must have boolean return type", pred);
- }
- }
-
- private static void checkLoop2(List<MethodHandle> step, List<MethodHandle> pred, List<MethodHandle> fini, List<Class<?>> commonParameterSequence) {
- final int cpSize = commonParameterSequence.size();
- if (Stream.of(step, pred, fini).flatMap(List::stream).filter(Objects::nonNull).map(MethodHandle::type).
- map(MethodType::parameterList).
- anyMatch(pl -> pl.size() > cpSize || !pl.equals(commonParameterSequence.subList(0, pl.size())))) {
- throw newIllegalArgumentException("found non-effectively identical parameter type lists:\nstep: " + step +
- "\npred: " + pred + "\nfini: " + fini + " (common parameter sequence: " + commonParameterSequence + ")");
- }
- }
-
- private static void checkIteratedLoop(MethodHandle iterator, MethodHandle body) {
- if (null != iterator && !Iterator.class.isAssignableFrom(iterator.type().returnType())) {
- throw newIllegalArgumentException("iteratedLoop first argument must have Iterator return type");
- }
- if (null == body) {
- throw newIllegalArgumentException("iterated loop body must not be null");
- }
- }
-
- private static void checkTryFinally(MethodHandle target, MethodHandle cleanup) {
+ private static void tryFinallyChecks(MethodHandle target, MethodHandle cleanup) {
Class<?> rtype = target.type().returnType();
if (rtype != cleanup.type().returnType()) {
throw misMatchedTypes("target and return types", cleanup.type().returnType(), rtype);
}
- List<Class<?>> cleanupParamTypes = cleanup.type().parameterList();
- if (!Throwable.class.isAssignableFrom(cleanupParamTypes.get(0))) {
+ MethodType cleanupType = cleanup.type();
+ if (!Throwable.class.isAssignableFrom(cleanupType.parameterType(0))) {
throw misMatchedTypes("cleanup first argument and Throwable", cleanup.type(), Throwable.class);
}
- if (rtype != void.class && cleanupParamTypes.get(1) != rtype) {
+ if (rtype != void.class && cleanupType.parameterType(1) != rtype) {
throw misMatchedTypes("cleanup second argument and target return type", cleanup.type(), rtype);
}
// The cleanup parameter list (minus the leading Throwable and result parameters) must be a sublist of the
// target parameter list.
int cleanupArgIndex = rtype == void.class ? 1 : 2;
- List<Class<?>> cleanupArgSuffix = cleanupParamTypes.subList(cleanupArgIndex, cleanupParamTypes.size());
- List<Class<?>> targetParamTypes = target.type().parameterList();
- if (targetParamTypes.size() < cleanupArgSuffix.size() ||
- !cleanupArgSuffix.equals(targetParamTypes.subList(0, cleanupParamTypes.size() - cleanupArgIndex))) {
+ if (!cleanupType.effectivelyIdenticalParameters(cleanupArgIndex, target.type().parameterList())) {
throw misMatchedTypes("cleanup parameters after (Throwable,result) and target parameter list prefix",
cleanup.type(), target.type());
}
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java Wed Sep 28 03:18:01 2016 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java Wed Sep 28 14:02:21 2016 +0200
@@ -809,6 +809,28 @@
return sj.toString();
}
+ /** True if my parameter list is effectively identical to the given full list,
+ * after skipping the given number of my own initial parameters.
+ * In other words, after disregarding {@code skipPos} parameters,
+ * my remaining parameter list is no longer than the {@code fullList}, and
+ * is equal to the same-length initial sublist of {@code fullList}.
+ */
+ /*non-public*/
+ boolean effectivelyIdenticalParameters(int skipPos, List<Class<?>> fullList) {
+ int myLen = ptypes.length, fullLen = fullList.size();
+ if (skipPos > myLen || myLen - skipPos > fullLen)
+ return false;
+ List<Class<?>> myList = Arrays.asList(ptypes);
+ if (skipPos != 0) {
+ myList = myList.subList(skipPos, myLen);
+ myLen -= skipPos;
+ }
+ if (fullLen == myLen)
+ return myList.equals(fullList);
+ else
+ return myList.equals(fullList.subList(0, myLen));
+ }
+
/** True if the old return type can always be viewed (w/o casting) under new return type,
* and the new parameters can be viewed (w/o casting) under the old parameter types.
*/
--- a/jdk/test/java/lang/invoke/CountedLoopIterationCountsTest.java Wed Sep 28 03:18:01 2016 +0000
+++ b/jdk/test/java/lang/invoke/CountedLoopIterationCountsTest.java Wed Sep 28 14:02:21 2016 +0200
@@ -71,7 +71,7 @@
}
}
- static int step(int counter, int stepCount) {
+ static int step(int stepCount, int counter) {
return stepCount + 1;
}
--- a/jdk/test/java/lang/invoke/JavaDocExamplesTest.java Wed Sep 28 03:18:01 2016 +0000
+++ b/jdk/test/java/lang/invoke/JavaDocExamplesTest.java Wed Sep 28 14:02:21 2016 +0200
@@ -703,6 +703,66 @@
}}
}
+ static int inc(int i) { return i + 1; } // drop acc, k
+ static int mult(int i, int acc) { return i * acc; } //drop k
+ static boolean cmp(int i, int k) { return i < k; }
+
+ @Test public void testSimplerLoop() throws Throwable {
+ MethodHandle MH_inc, MH_mult, MH_cmp;
+ Class<?> I = int.class;
+ MH_inc = LOOKUP.findStatic(THIS_CLASS, "inc", methodType(I, I));
+ MH_mult = LOOKUP.findStatic(THIS_CLASS, "mult", methodType(I, I, I));
+ MH_cmp = LOOKUP.findStatic(THIS_CLASS, "cmp", methodType(boolean.class, I, I));
+ {{
+{} /// JAVADOC
+// simplified implementation of the factorial function as a loop handle
+// null initializer for counter, should initialize to 0
+MethodHandle MH_one = MethodHandles.constant(int.class, 1);
+MethodHandle MH_pred = MethodHandles.dropArguments(MH_cmp, 1, int.class); // drop acc
+MethodHandle MH_fin = MethodHandles.dropArguments(MethodHandles.identity(int.class), 0, int.class); // drop i
+MethodHandle[] counterClause = new MethodHandle[]{null, MH_inc};
+MethodHandle[] accumulatorClause = new MethodHandle[]{MH_one, MH_mult, MH_pred, MH_fin};
+MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
+assertEquals(720, loop.invoke(6));
+{}
+ }}
+ }
+
+ // for testFacLoop
+{}
+static class FacLoop {
+ final int k;
+ FacLoop(int k) { this.k = k; }
+ int inc(int i) { return i + 1; }
+ int mult(int i, int acc) { return i * acc; }
+ boolean pred(int i) { return i < k; }
+ int fin(int i, int acc) { return acc; }
+}
+{}
+
+ // assume MH_inc, MH_mult, and MH_pred are handles to the above methods
+ @Test public void testFacLoop() throws Throwable {
+ MethodHandle MH_FacLoop, MH_inc, MH_mult, MH_pred, MH_fin;
+ Class<?> I = int.class;
+ MH_FacLoop = LOOKUP.findConstructor(FacLoop.class, methodType(void.class, I));
+ MH_inc = LOOKUP.findVirtual(FacLoop.class, "inc", methodType(I, I));
+ MH_mult = LOOKUP.findVirtual(FacLoop.class, "mult", methodType(I, I, I));
+ MH_pred = LOOKUP.findVirtual(FacLoop.class, "pred", methodType(boolean.class, I));
+ MH_fin = LOOKUP.findVirtual(FacLoop.class, "fin", methodType(I, I, I));
+ {{
+{} /// JAVADOC
+// instance-based implementation of the factorial function as a loop handle
+// null initializer for counter, should initialize to 0
+MethodHandle MH_one = MethodHandles.constant(int.class, 1);
+MethodHandle[] instanceClause = new MethodHandle[]{MH_FacLoop};
+MethodHandle[] counterClause = new MethodHandle[]{null, MH_inc};
+MethodHandle[] accumulatorClause = new MethodHandle[]{MH_one, MH_mult, MH_pred, MH_fin};
+MethodHandle loop = MethodHandles.loop(instanceClause, counterClause, accumulatorClause);
+assertEquals(5040, loop.invoke(7));
+{}
+ }}
+ }
+
static List<String> initZip(Iterator<String> a, Iterator<String> b) { return new ArrayList<>(); }
static boolean zipPred(List<String> zip, Iterator<String> a, Iterator<String> b) { return a.hasNext() && b.hasNext(); }
static List<String> zipStep(List<String> zip, Iterator<String> a, Iterator<String> b) {
@@ -749,36 +809,81 @@
}}
}
- static String start(String arg) { return arg; }
- static String step(int counter, String v, String arg) { return "na " + v; }
+ static String step(String v, int counter, String start_) { return "na " + v; } //#0
+ static String step(String v, int counter ) { return "na " + v; } //#1
+ static String step(String v, int counter, int iterations_, String pre, String start_) { return pre + " " + v; } //#2
+ static String step3(String v, int counter, String pre) { return pre + " " + v; } //#3
@Test public void testCountedLoop() throws Throwable {
- MethodHandle MH_start, MH_step;
- Class<?> S = String.class;
- MH_start = LOOKUP.findStatic(THIS_CLASS, "start", methodType(S, S));
- MH_step = LOOKUP.findStatic(THIS_CLASS, "step", methodType(S, int.class, S, S));
+ MethodHandle MH_step;
+ Class<?> S = String.class, I = int.class;
+ // Theme:
+ MH_step = LOOKUP.findStatic(THIS_CLASS, "step", methodType(S, S, I, S));
{{
{} /// JAVADOC
// String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s;
// => a variation on a well known theme
MethodHandle fit13 = MethodHandles.constant(int.class, 13);
-MethodHandle loop = MethodHandles.countedLoop(fit13, MH_start, MH_step);
+MethodHandle start = MethodHandles.identity(String.class);
+MethodHandle loop = MethodHandles.countedLoop(fit13, start, MH_step); // (v, i, _) -> "na " + v
assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
{}
}}
+ // Variation #1:
+ MH_step = LOOKUP.findStatic(THIS_CLASS, "step", methodType(S, S, I));
+ {{
+{} /// JAVADOC
+// String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s;
+// => a variation on a well known theme
+MethodHandle count = MethodHandles.dropArguments(MethodHandles.identity(int.class), 1, String.class);
+MethodHandle start = MethodHandles.dropArguments(MethodHandles.identity(String.class), 0, int.class);
+MethodHandle loop = MethodHandles.countedLoop(count, start, MH_step); // (v, i) -> "na " + v
+assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke(13, "Lambdaman!"));
+{}
+ assertEquals("na na Lambdaman!", loop.invoke(2, "Lambdaman!"));
+ assertEquals("Lambdaman!", loop.invoke(0, "Lambdaman!"));
+ assertEquals("Lambdaman!", loop.invoke(-1, "Lambdaman!"));
+ assertEquals("Lambdaman!", loop.invoke(Integer.MIN_VALUE, "Lambdaman!"));
+ }}
+ // Variation #2:
+ MH_step = LOOKUP.findStatic(THIS_CLASS, "step", methodType(S, S, I, I, S, S));
+ {{
+{} /// JAVADOC
+// String s = "Lambdaman!", t = "na"; for (int i = 0; i < 13; ++i) { s = t + " " + s; } return s;
+// => a variation on a well known theme
+MethodHandle count = MethodHandles.identity(int.class);
+MethodHandle start = MethodHandles.dropArguments(MethodHandles.identity(String.class), 0, int.class, String.class);
+MethodHandle loop = MethodHandles.countedLoop(count, start, MH_step); // (v, i, _, pre, _) -> pre + " " + v
+assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke(13, "na", "Lambdaman!"));
+{}
+ }}
+ // Variation #3:
+ MH_step = LOOKUP.findStatic(THIS_CLASS, "step3", methodType(S, S, I, S));
+ {{
+{} /// JAVADOC
+// String s = "Lambdaman!", t = "na"; for (int i = 0; i < 13; ++i) { s = t + " " + s; } return s;
+// => a variation on a well known theme
+MethodType loopType = methodType(String.class, String.class, int.class, String.class);
+MethodHandle count = MethodHandles.dropArgumentsToMatch(MethodHandles.identity(int.class), 0, loopType.parameterList(), 1);
+MethodHandle start = MethodHandles.dropArgumentsToMatch(MethodHandles.identity(String.class), 0, loopType.parameterList(), 2);
+MethodHandle body = MethodHandles.dropArgumentsToMatch(MH_step, 2, loopType.parameterList(), 0);
+MethodHandle loop = MethodHandles.countedLoop(count, start, body); // (v, i, pre, _, _) -> pre + " " + v
+assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("na", 13, "Lambdaman!"));
+{}
+ }}
}
- static List<String> reverseStep(String e, List<String> r, List<String> l) {
+ static List<String> reverseStep(List<String> r, String e) {
r.add(0, e);
return r;
}
- static List<String> newArrayList(List<String> l) { return new ArrayList<>(); }
+ static List<String> newArrayList() { return new ArrayList<>(); }
@Test public void testIteratedLoop() throws Throwable {
MethodHandle MH_newArrayList, MH_reverseStep;
- Class<?> L = List.class;
- MH_newArrayList = LOOKUP.findStatic(THIS_CLASS, "newArrayList", methodType(L, L));
- MH_reverseStep = LOOKUP.findStatic(THIS_CLASS, "reverseStep", methodType(L, String.class, L, L));
+ Class<?> L = List.class, S = String.class;
+ MH_newArrayList = LOOKUP.findStatic(THIS_CLASS, "newArrayList", methodType(L));
+ MH_reverseStep = LOOKUP.findStatic(THIS_CLASS, "reverseStep", methodType(L, L, S));
{{
{} /// JAVADOC
// reverse a list
--- a/jdk/test/java/lang/invoke/LoopCombinatorTest.java Wed Sep 28 03:18:01 2016 +0000
+++ b/jdk/test/java/lang/invoke/LoopCombinatorTest.java Wed Sep 28 14:02:21 2016 +0200
@@ -28,6 +28,7 @@
* @bug 8150635
* @bug 8150956
* @bug 8150957
+ * @bug 8151179
* @bug 8152667
* @bug 8153637
* @bug 8154751
@@ -146,6 +147,16 @@
assertEquals(120, loop.invoke(new LoopWithVirtuals(), 5));
}
+ @Test
+ public static void testLoopOmitPred() throws Throwable {
+ // construct a loop to calculate factorial that omits a predicate
+ MethodHandle[] counterClause = new MethodHandle[]{null, Fac.MH_inc, null, Fac.MH_fin};
+ MethodHandle[] accumulatorClause = new MethodHandle[]{Fac.MH_one, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin};
+ MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
+ assertEquals(Fac.MT_fac, loop.type());
+ assertEquals(120, loop.invoke(5));
+ }
+
@DataProvider
static Object[][] negativeTestData() {
MethodHandle i0 = MethodHandles.constant(int.class, 0);
@@ -153,7 +164,8 @@
MethodHandle id = MethodHandles.dropArguments(i0, 0, int.class, double.class);
MethodHandle i3 = MethodHandles.dropArguments(i0, 0, int.class, int.class, int.class);
List<MethodHandle> inits = Arrays.asList(ii, id, i3);
- List<Class<?>> ints = Arrays.asList(int.class, int.class, int.class);
+ List<Class<?>> ints3 = Arrays.asList(int.class, int.class, int.class);
+ List<Class<?>> ints4 = Arrays.asList(int.class, int.class, int.class, int.class);
List<MethodHandle> finis = Arrays.asList(Fac.MH_fin, Fac.MH_inc, Counted.MH_step);
List<MethodHandle> preds1 = Arrays.asList(null, null, null);
List<MethodHandle> preds2 = Arrays.asList(null, Fac.MH_fin, null);
@@ -174,7 +186,7 @@
"clause 0: init and step return types must match: int != void"},
{new MethodHandle[][]{{ii}, {id}, {i3}},
"found non-effectively identical init parameter type lists: " + inits +
- " (common suffix: " + ints + ")"},
+ " (common suffix: " + ints3 + ")"},
{new MethodHandle[][]{{null, Fac.MH_inc, null, Fac.MH_fin}, {null, Fac.MH_inc, null, Fac.MH_inc},
{null, Counted.MH_start, null, Counted.MH_step}},
"found non-identical finalizer return types: " + finis + " (return type: int)"},
@@ -185,11 +197,11 @@
{new MethodHandle[][]{{Fac.MH_zero, Fac.MH_inc}, {Fac.MH_one, eek, Fac.MH_pred, Fac.MH_fin},
{null, Fac.MH_dot}},
"found non-effectively identical parameter type lists:\nstep: " + nesteps +
- "\npred: " + nepreds + "\nfini: " + nefinis + " (common parameter sequence: " + ints + ")"},
+ "\npred: " + nepreds + "\nfini: " + nefinis + " (common parameter sequence: " + ints3 + ")"},
{new MethodHandle[][]{{null, LoopWithVirtuals.MH_inc},
{LoopWithVirtuals.MH_one, LoopWithVirtuals.MH_mult, LoopWithVirtuals.MH_pred, LoopWithVirtuals.MH_fin}},
"found non-effectively identical parameter type lists:\nstep: " + lvsteps +
- "\npred: " + lvpreds + "\nfini: " + lvfinis + " (common parameter sequence: " + ints + ")"}
+ "\npred: " + lvpreds + "\nfini: " + lvfinis + " (common parameter sequence: " + ints4 + ")"}
};
}
@@ -207,7 +219,7 @@
public static void testLoopNegative(MethodHandle[][] clauses, String expectedMessage) throws Throwable {
boolean caught = false;
try {
- MH_loop.invokeWithArguments(clauses);
+ MH_loop.invokeWithArguments((Object[]) clauses);
} catch (IllegalArgumentException iae) {
assertEquals(expectedMessage, iae.getMessage());
caught = true;
@@ -215,12 +227,100 @@
assertTrue(caught);
}
- @Test
- public static void testWhileLoop() throws Throwable {
+ @Test(dataProvider = "whileLoopTestData")
+ public static void testWhileLoop(MethodHandle MH_zero,
+ MethodHandle MH_pred,
+ MethodHandle MH_step,
+ String messageOrNull) throws Throwable {
// int i = 0; while (i < limit) { ++i; } return i; => limit
- MethodHandle loop = MethodHandles.whileLoop(While.MH_zero, While.MH_pred, While.MH_step);
- assertEquals(While.MT_while, loop.type());
- assertEquals(23, loop.invoke(23));
+ try {
+ MethodHandle loop = MethodHandles.whileLoop(MH_zero, MH_pred, MH_step);
+ assert messageOrNull == null;
+ if (MH_step.type().equals(While.MH_step.type()))
+ assertEquals(While.MT_while, loop.type());
+ assertEquals(MH_step.type().dropParameterTypes(0, 1), loop.type());
+ while (loop.type().parameterCount() > 1) loop = snip(loop);
+ assertEquals(23, loop.invoke(23));
+ } catch (IllegalArgumentException iae) {
+ assert messageOrNull != null;
+ assertEqualsFIXME(messageOrNull, iae.getMessage());
+ }
+ }
+
+ static void assertEqualsFIXME(String expect, String actual) {
+ if (!expect.equals(actual)) {
+ // just issue a warning
+ System.out.println("*** "+actual+"\n != "+expect);
+ }
+ }
+
+ @DataProvider
+ static Object[][] whileLoopTestData() {
+ MethodHandle
+ zeroI = While.MH_zero,
+ zeroX = snip(zeroI),
+ zeroIB = slap(zeroI, byte.class),
+ predII = While.MH_pred,
+ predIX = snip(predII),
+ predIIB = slap(predII, byte.class),
+ stepII = While.MH_step,
+ stepIX = snip(stepII),
+ stepIIB = slap(stepII, byte.class)
+ ;
+ return new Object[][] {
+ // normal while loop clauses, perhaps with effectively-identical reductions
+ {zeroI, predII, stepII, null},
+ {zeroX, predII, stepII, null},
+ {null, predII, stepII, null},
+ // expanded while loop clauses
+ {zeroIB, predIIB, stepIIB, null},
+ {zeroI, predIIB, stepIIB, null},
+ {null, predIIB, stepIIB, null},
+ {zeroIB, predII, stepIIB, null},
+ {zeroX, predII, stepIIB, null},
+ {null, predII, stepIIB, null},
+ // short step clauses cause errors
+ {zeroI, predII, stepIX, "loop predicate must match: (int,int)boolean != (int)boolean"},
+ {zeroIB, predIX, stepIX, "loop initializer must match: (int,byte)int != ()int"},
+ // bad body type
+ {zeroI, predII, tweak(stepII, -1, char.class), "body function must match: (int,int)char != (char,int,int)char"},
+ {zeroI, predII, tweak(stepII, 0, char.class), "body function must match: (char,int)int != (int,char,int)int"},
+ // bad pred type
+ {zeroI, tweak(predII, -1, char.class), stepII, "loop predicate must match: (int,int)char != (int,int)boolean"},
+ {zeroI, tweak(predII, 0, char.class), stepII, "loop predicate must match: (char,int)boolean != (int,int)boolean"},
+ // bad init type
+ {tweak(zeroI, -1, char.class), predII, stepII, "loop initializer must match: (int)char != (int)int"},
+ {tweak(zeroI, 0, char.class), predII, stepII, "loop initializer must match: (char)int != (int)int"},
+ };
+ }
+
+ // tweak the type of an MH
+ static MethodHandle tweak(MethodHandle mh, int argPos, Class<?> type) {
+ MethodType mt = mh.type();
+ if (argPos == -1)
+ mt = mt.changeReturnType(type);
+ else
+ mt = mt.changeParameterType(argPos, type);
+ return MethodHandles.explicitCastArguments(mh, mt);
+ }
+ // snip off an MH argument, hard-wiring to zero
+ static MethodHandle snip(MethodHandle mh, int argPos) {
+ if (argPos < 0) return null; // special case for optional args
+ Class<?> argType = mh.type().parameterType(argPos);
+ Object zero;
+ try {
+ zero = MethodHandles.zero(argType).invoke();
+ } catch (Throwable ex) {
+ throw new AssertionError(ex);
+ }
+ return MethodHandles.insertArguments(mh, argPos, zero);
+ }
+ static MethodHandle snip(MethodHandle mh) {
+ return snip(mh, mh.type().parameterCount()-1);
+ }
+ // slap on an extra type on the end of the MH
+ static MethodHandle slap(MethodHandle mh, Class<?> addType) {
+ return MethodHandles.dropArguments(mh, mh.type().parameterCount(), addType);
}
@Test
@@ -231,22 +331,42 @@
assertEquals("a", loop.invoke());
}
- @Test
- public static void testDoWhileLoop() throws Throwable {
+ @Test(dataProvider = "whileLoopTestData")
+ public static void testDoWhileLoop(MethodHandle MH_zero,
+ MethodHandle MH_pred,
+ MethodHandle MH_step,
+ String messageOrNull) throws Throwable {
// int i = 0; do { ++i; } while (i < limit); return i; => limit
- MethodHandle loop = MethodHandles.doWhileLoop(While.MH_zero, While.MH_step, While.MH_pred);
- assertEquals(While.MT_while, loop.type());
- assertEquals(23, loop.invoke(23));
+ try {
+ MethodHandle loop = MethodHandles.doWhileLoop(MH_zero, MH_step, MH_pred);
+ assert messageOrNull == null;
+ if (MH_step.type().equals(While.MH_step.type()))
+ assertEquals(While.MT_while, loop.type());
+ assertEquals(MH_step.type().dropParameterTypes(0, 1), loop.type());
+ while (loop.type().parameterCount() > 1) loop = snip(loop);
+ assertEquals(23, loop.invoke(23));
+ } catch (IllegalArgumentException iae) {
+ assert messageOrNull != null;
+ if (!messageOrNull.equals(iae.getMessage())) {
+ // just issue a warning
+ System.out.println("*** "+messageOrNull+"\n != "+iae.getMessage());
+ }
+ }
}
@Test
- public static void testDoWhileNullInit() throws Throwable {
- While w = new While();
- int v = 5;
- MethodHandle loop = MethodHandles.doWhileLoop(null, While.MH_voidBody.bindTo(w), While.MH_voidPred.bindTo(w));
- assertEquals(While.MT_void, loop.type());
- loop.invoke(v);
- assertEquals(v, w.i);
+ public static void testDoWhileBadInit() throws Throwable {
+ boolean caught = false;
+ try {
+ While w = new While();
+ MethodHandle loop = MethodHandles.doWhileLoop(MethodHandles.empty(methodType(char.class)),
+ While.MH_voidBody.bindTo(w),
+ While.MH_voidPred.bindTo(w));
+ } catch (IllegalArgumentException iae) {
+ assertEquals("loop initializer must match: ()char != (int)void", iae.getMessage());
+ caught = true;
+ }
+ assertTrue(caught);
}
@Test
@@ -260,13 +380,18 @@
}
@Test
- public static void testWhileNullInit() throws Throwable {
- While w = new While();
- int v = 5;
- MethodHandle loop = MethodHandles.whileLoop(null, While.MH_voidPred.bindTo(w), While.MH_voidBody.bindTo(w));
- assertEquals(While.MT_void, loop.type());
- loop.invoke(v);
- assertEquals(v, w.i);
+ public static void testWhileBadInit() throws Throwable {
+ boolean caught = false;
+ try {
+ While w = new While();
+ MethodHandle loop = MethodHandles.whileLoop(MethodHandles.empty(methodType(void.class, char.class)),
+ While.MH_voidPred.bindTo(w),
+ While.MH_voidBody.bindTo(w));
+ } catch (IllegalArgumentException iae) {
+ assertEquals("loop initializer must match: (char)void != (int)void", iae.getMessage());
+ caught = true;
+ }
+ assertTrue(caught);
}
@Test
@@ -291,10 +416,26 @@
assertEquals(v, w.i);
}
+ @DataProvider
+ static Object[][] nullArgs() {
+ MethodHandle c = MethodHandles.constant(int.class, 1);
+ return new Object[][]{{null, c}, {c, null}};
+ }
+
+ @Test(dataProvider = "nullArgs", expectedExceptions = NullPointerException.class)
+ public static void testWhileNullArgs(MethodHandle pred, MethodHandle body) {
+ MethodHandles.whileLoop(null, pred, body);
+ }
+
+ @Test(dataProvider = "nullArgs", expectedExceptions = NullPointerException.class)
+ public static void testDoWhileNullArgs(MethodHandle body, MethodHandle pred) {
+ MethodHandles.whileLoop(null, body, pred);
+ }
+
@Test
public static void testCountedLoop() throws Throwable {
// String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s; => a variation on a well known theme
- MethodHandle fit13 = MethodHandles.constant(int.class, 13);
+ MethodHandle fit13 = MethodHandles.dropArguments(MethodHandles.constant(int.class, 13), 0, String.class);
MethodHandle loop = MethodHandles.countedLoop(fit13, Counted.MH_start, Counted.MH_step);
assertEquals(Counted.MT_counted, loop.type());
assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
@@ -303,9 +444,25 @@
@Test
public static void testCountedLoopVoidInit() throws Throwable {
MethodHandle fit5 = MethodHandles.constant(int.class, 5);
- MethodHandle loop = MethodHandles.countedLoop(fit5, MethodHandles.zero(void.class), Counted.MH_printHello);
- assertEquals(Counted.MT_countedPrinting, loop.type());
- loop.invoke();
+ for (int i = 0; i < 8; i++) {
+ MethodHandle zero = MethodHandles.zero(void.class);
+ MethodHandle init = fit5;
+ MethodHandle body = Counted.MH_printHello;
+ boolean useNull = (i & 1) != 0, addInitArg = (i & 2) != 0, addBodyArg = (i & 4) != 0;
+ if (useNull) zero = null;
+ if (addInitArg) init = MethodHandles.dropArguments(init, 0, int.class);
+ if (addBodyArg) body = MethodHandles.dropArguments(body, 1, int.class);
+ System.out.println("testCountedLoopVoidInit i="+i+" : "+Arrays.asList(init, zero, body));
+ MethodHandle loop = MethodHandles.countedLoop(init, zero, body);
+ MethodType expectedType = Counted.MT_countedPrinting;
+ if (addInitArg || addBodyArg)
+ expectedType = expectedType.insertParameterTypes(0, int.class);
+ assertEquals(expectedType, loop.type());
+ if (addInitArg || addBodyArg)
+ loop.invoke(99);
+ else
+ loop.invoke();
+ }
}
@Test
@@ -327,7 +484,7 @@
loop.invoke();
}
- @Test
+ @Test(expectedExceptions = NullPointerException.class)
public static void testCountedLoopNullBody() throws Throwable {
MethodHandle h5 = MethodHandles.constant(int.class, 5);
MethodHandle h13 = MethodHandles.constant(int.class, 13);
@@ -336,14 +493,14 @@
assertEquals(13, loop.invoke());
}
- @Test
+ @Test(expectedExceptions = NullPointerException.class)
public static void testCountedLoopNullIterations() throws Throwable {
MethodHandle loop = MethodHandles.countedLoop(null, null, null);
assertEquals(methodType(void.class), loop.type());
loop.invoke();
}
- @Test
+ @Test(expectedExceptions = NullPointerException.class)
public static void testCountedLoopNullInitAndBody() throws Throwable {
MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, 5), null, null);
assertEquals(methodType(void.class), loop.type());
@@ -352,45 +509,63 @@
@DataProvider
static Object[][] countedLoopBodyParameters() {
+ Class<?> V = String.class, I = int.class, A = List.class;
+ // return types are of these forms:
+ // {count = int(A...), init = V(A...), body = V(V, I, A...)}
return new Object[][] {
- {methodType(String.class), methodType(String.class, int.class)},
- {methodType(String.class, List.class), methodType(String.class, int.class)},
- {methodType(String.class, List.class), methodType(String.class, int.class, String.class)}
+ // body leads determining A...
+ {methodType(I), methodType(V), methodType(V, V, I)},
+ {methodType(I), methodType(V), methodType(V, V, I, A)},
+ {methodType(I,A), methodType(V), methodType(V, V, I, A)},
+ {methodType(I), methodType(V,A), methodType(V, V, I, A)},
+ // body leads, with void V
+ {methodType(I), methodType(void.class), methodType(void.class, I)},
+ {methodType(I), methodType(void.class), methodType(void.class, I, A)},
+ {methodType(I,A), methodType(void.class), methodType(void.class, I, A)},
+ {methodType(I), methodType(void.class,A), methodType(void.class, I, A)},
+ // count leads determining A..., but only if body drops all A...
+ {methodType(I,A), methodType(V), methodType(V, V, I)},
+ {methodType(I,A), methodType(V,A), methodType(V, V, I)},
+ // count leads, with void V
+ {methodType(I,A), methodType(void.class), methodType(void.class, I)},
+ {methodType(I,A), methodType(void.class,A), methodType(void.class, I)},
};
}
@Test(dataProvider = "countedLoopBodyParameters")
- public static void testCountedLoopBodyParameters(MethodType initType, MethodType bodyType) throws Throwable {
- MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, 5),
- MethodHandles.empty(initType), MethodHandles.empty(bodyType));
- assertEquals(initType, loop.type());
+ public static void testCountedLoopBodyParameters(MethodType countType, MethodType initType, MethodType bodyType) throws Throwable {
+ MethodHandle loop = MethodHandles.countedLoop(
+ MethodHandles.empty(countType),
+ initType == null ? null : MethodHandles.empty(initType),
+ MethodHandles.empty(bodyType));
+ // The rule: If body takes the minimum number of parameters, then take what countType offers.
+ // The initType has to just roll with whatever the other two agree on.
+ int innerParams = (bodyType.returnType() == void.class ? 1 : 2);
+ MethodType expectType = bodyType.dropParameterTypes(0, innerParams);
+ if (expectType.parameterCount() == 0)
+ expectType = expectType.insertParameterTypes(0, countType.parameterList());
+ assertEquals(expectType, loop.type());
}
- @DataProvider
- static Object[][] countedLoopTypes() {
- return new Object[][]{{void.class}, {int.class}, {Object.class}, {String.class}, {List.class}};
- }
-
- @Test(dataProvider = "countedLoopTypes")
- public static void testCountedLoopBodyParametersNullInit(Class<?> t) throws Throwable {
- MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, 5), null,
- MethodHandles.empty(methodType(t, int.class)));
- assertEquals(methodType(t), loop.type());
- loop.invoke();
+ @Test(dataProvider = "countedLoopBodyParameters")
+ public static void testCountedLoopBodyParametersNullInit(MethodType countType, MethodType initType, MethodType bodyType) throws Throwable {
+ testCountedLoopBodyParameters(countType, null, bodyType);
}
@Test
- public static void testCountedLoopStateDefinedByBody() throws Throwable {
- MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, 5), null, Counted.MH_stateBody);
+ public static void testCountedLoopStateInitializedToNull() throws Throwable {
+ MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, 5),
+ MethodHandles.empty(methodType(String.class)), Counted.MH_stateBody);
assertEquals(Counted.MT_bodyDeterminesState, loop.type());
assertEquals("sssssnull01234", loop.invoke());
}
@Test
public static void testCountedLoopArgsDefinedByIterations() throws Throwable {
- MethodHandle loop = MethodHandles.countedLoop(
- MethodHandles.dropArguments(MethodHandles.constant(int.class, 3), 0, String.class),
- null, Counted.MH_append);
+ MethodHandle iterations =
+ MethodHandles.dropArguments(MethodHandles.constant(int.class, 3), 0, String.class);
+ MethodHandle loop = MethodHandles.countedLoop(iterations,
+ MethodHandles.empty(iterations.type().changeReturnType(String.class)), Counted.MH_append);
assertEquals(Counted.MT_iterationsDefineArgs, loop.type());
assertEquals("hello012", loop.invoke("hello"));
}
@@ -420,7 +595,8 @@
@Test
public static void testCountedLoopEmpty() throws Throwable {
// for (int i = 0; i < 5; ++i) { /* empty */ }
- MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, 5), null, null);
+ MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, 5), null,
+ MethodHandles.empty(methodType(void.class, int.class)));
assertEquals(methodType(void.class), loop.type());
loop.invoke();
}
@@ -429,11 +605,45 @@
public static void testCountedRangeLoopEmpty() throws Throwable {
// for (int i = -5; i < 5; ++i) { /* empty */ }
MethodHandle loop = MethodHandles.countedLoop(MethodHandles.constant(int.class, -5),
- MethodHandles.constant(int.class, 5), null, null);
+ MethodHandles.constant(int.class, 5), null, MethodHandles.empty(methodType(void.class, int.class)));
assertEquals(methodType(void.class), loop.type());
loop.invoke();
}
+ @DataProvider
+ static Object[][] countedLoopNegativeData() {
+ MethodHandle dummy = MethodHandles.zero(void.class);
+ MethodHandle one = MethodHandles.constant(int.class, 1);
+ MethodHandle oneString = MethodHandles.dropArguments(one, 0, String.class);
+ MethodHandle oneDouble = MethodHandles.dropArguments(one, 0, double.class);
+ return new Object[][]{
+ {dummy, one, dummy, dummy, String.format("start/end must return int %s, %s", dummy, one)},
+ {one, dummy, dummy, dummy, String.format("start/end must return int %s, %s", one, dummy)},
+ {oneString, oneDouble, dummy, dummy,
+ String.format("start and end parameter types must match: %s != %s", oneString.type(),
+ oneDouble.type())},
+ {oneString, oneString, dummy, dummy,
+ String.format("start/end and init parameter types must match: %s != %s", oneString.type(),
+ dummy.type())},
+ {one, one, null, dummy, String.format("actual and expected body signatures must match: %s != %s",
+ dummy.type(), dummy.type().appendParameterTypes(int.class))}
+ };
+ }
+
+ @Test(dataProvider = "countedLoopNegativeData")
+ public static void testCountedLoopNegative(MethodHandle start, MethodHandle end, MethodHandle init,
+ MethodHandle body, String msg) {
+ if (true) return; //%%%FIXME%%%%
+ boolean caught = false;
+ try {
+ MethodHandles.countedLoop(start, end, init, body);
+ } catch (IllegalArgumentException iae) {
+ assertEquals(msg, iae.getMessage());
+ caught = true;
+ }
+ assertTrue(caught);
+ }
+
@Test
public static void testIterateSum() throws Throwable {
// Integer[] a = new Integer[]{1,2,3,4,5,6}; int sum = 0; for (int e : a) { sum += e; } return sum; => 21
@@ -442,50 +652,106 @@
assertEquals(21, loop.invoke(new Integer[]{1, 2, 3, 4, 5, 6}));
}
- @Test
- public static void testIterateReverse() throws Throwable {
- MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_reverseInit, Iterate.MH_reverseStep);
- assertEquals(Iterate.MT_reverse, loop.type());
- List<String> list = Arrays.asList("a", "b", "c", "d", "e");
- List<String> reversedList = Arrays.asList("e", "d", "c", "b", "a");
- assertEquals(reversedList, (List<String>) loop.invoke(list));
+ @DataProvider
+ static Object[][] iteratorInits() {
+ return new Object[][]{{Iterate.MH_iteratorFromList}, {Iterate.MH_iteratorFromIterable}, {null}};
+ }
+
+ @Test(dataProvider = "iteratorInits")
+ public static void testIterateReverse(MethodHandle iterator) throws Throwable {
+ // this test uses List as its loop state type; don't try to change that
+ if (iterator != null)
+ iterator = iterator.asType(iterator.type().changeParameterType(0, List.class));
+ for (int i = 0; i < 4; i++) {
+ MethodHandle init = Iterate.MH_reverseInit, body = Iterate.MH_reverseStep;
+ boolean snipInit = (i & 1) != 0, snipBody = (i & 2) != 0;
+ if (snipInit) init = snip(init);
+ if (snipBody) body = snip(body);
+ if (!snipInit && snipBody && iterator == null) {
+ // Body does not determine (A...), so the default guy just picks Iterable.
+ // If body insisted on (List), the default guy would adjust himself.
+ // Init has no authority to change the (A...), so must patch init.
+ // All according to plan!
+ init = slap(snip(init), Iterable.class);
+ }
+ System.out.println("testIterateReverse i="+i+" : "+Arrays.asList(iterator, init, body));
+ MethodHandle loop = MethodHandles.iteratedLoop(iterator, init, body);
+ MethodType expectedType = Iterate.MT_reverse;
+ if (iterator == null && i >= 2)
+ expectedType = expectedType.changeParameterType(0, Iterable.class);
+ assertEquals(expectedType, loop.type());
+ List<String> list = Arrays.asList("a", "b", "c", "d", "e");
+ List<String> reversedList = Arrays.asList("e", "d", "c", "b", "a");
+ assertEquals(reversedList, (List<String>) loop.invoke(list));
+ }
}
- @Test
- public static void testIterateLength() throws Throwable {
- MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_lengthInit, Iterate.MH_lengthStep);
- assertEquals(Iterate.MT_length, loop.type());
- List<Double> list = Arrays.asList(23.0, 148.0, 42.0);
- assertEquals(list.size(), (int) loop.invoke(list));
+ @Test(dataProvider = "iteratorInits")
+ public static void testIterateLength(MethodHandle iterator) throws Throwable {
+ MethodHandle body = Iterate.MH_lengthStep;
+ MethodHandle init = Iterate.MH_lengthInit;
+ MethodType expectedType = Iterate.MT_length;
+ int barity = body.type().parameterCount();
+ Class<?> iteratorSource = iterator == null ? null : iterator.type().parameterType(0);
+ if (iterator != null && iteratorSource != body.type().parameterType(barity-1)) {
+ // adjust body to accept the other type
+ body = body.asType(body.type().changeParameterType(barity-1, iteratorSource));
+ init = init.asType(init.type().changeParameterType(0, iteratorSource));
+ expectedType = expectedType.changeParameterType(0, iteratorSource);
+ }
+ for (;; init = snip(init)) {
+ System.out.println("testIterateLength.init = "+init);
+ MethodHandle loop = MethodHandles.iteratedLoop(iterator, init, body);
+ assertEquals(expectedType, loop.type());
+ List<Double> list = Arrays.asList(23.0, 148.0, 42.0);
+ assertEquals(list.size(), (int) loop.invoke(list));
+ if (init == null) break;
+ }
}
- @Test
- public static void testIterateMap() throws Throwable {
- MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_mapInit, Iterate.MH_mapStep);
- assertEquals(Iterate.MT_map, loop.type());
- List<String> list = Arrays.asList("Hello", "world", "!");
- List<String> upList = Arrays.asList("HELLO", "WORLD", "!");
- assertEquals(upList, (List<String>) loop.invoke(list));
+ @Test(dataProvider = "iteratorInits")
+ public static void testIterateMap(MethodHandle iterator) throws Throwable {
+ MethodHandle body = Iterate.MH_mapStep;
+ MethodHandle init = Iterate.MH_mapInit;
+ MethodType expectedType = Iterate.MT_map;
+ int barity = body.type().parameterCount();
+ Class<?> iteratorSource = iterator == null ? null : iterator.type().parameterType(0);
+ if (iterator != null && iteratorSource != body.type().parameterType(barity-1)) {
+ // adjust body to accept the other type
+ body = body.asType(body.type().changeParameterType(barity-1, iteratorSource));
+ init = init.asType(init.type().changeParameterType(0, iteratorSource));
+ expectedType = expectedType.changeParameterType(0, iteratorSource);
+ }
+ for (; init != null; init = snip(init)) {
+ System.out.println("testIterateMap.init = "+init);
+ MethodHandle loop = MethodHandles.iteratedLoop(iterator, init, body);
+ assertEquals(expectedType, loop.type());
+ List<String> list = Arrays.asList("Hello", "world", "!");
+ List<String> upList = Arrays.asList("HELLO", "WORLD", "!");
+ assertEquals(upList, (List<String>) loop.invoke(list));
+ }
}
- @Test
- public static void testIteratePrint() throws Throwable {
- MethodHandle loop = MethodHandles.iteratedLoop(null, null, Iterate.MH_printStep);
- assertEquals(Iterate.MT_print, loop.type());
+ @Test(dataProvider = "iteratorInits")
+ public static void testIteratePrint(MethodHandle iterator) throws Throwable {
+ MethodHandle body = Iterate.MH_printStep;
+ MethodType expectedType = Iterate.MT_print;
+ int barity = body.type().parameterCount();
+ Class<?> iteratorSource = iterator == null ? null : iterator.type().parameterType(0);
+ if (iterator != null && iteratorSource != body.type().parameterType(barity-1)) {
+ // adjust body to accept the other type
+ body = body.asType(body.type().changeParameterType(barity-1, iteratorSource));
+ expectedType = expectedType.changeParameterType(0, iteratorSource);
+ }
+ MethodHandle loop = MethodHandles.iteratedLoop(iterator, null, body);
+ assertEquals(expectedType, loop.type());
loop.invoke(Arrays.asList("hello", "world"));
}
- @Test
+ @Test(expectedExceptions = NullPointerException.class)
public static void testIterateNullBody() {
- boolean caught = false;
- try {
- MethodHandles.iteratedLoop(MethodHandles.empty(methodType(Iterator.class, int.class)),
- MethodHandles.identity(int.class), null);
- } catch (IllegalArgumentException iae) {
- assertEquals("iterated loop body must not be null", iae.getMessage());
- caught = true;
- }
- assertTrue(caught);
+ MethodHandles.iteratedLoop(MethodHandles.empty(methodType(Iterator.class, int.class)),
+ MethodHandles.identity(int.class), null);
}
@DataProvider
@@ -500,15 +766,18 @@
try {
MethodHandles.iteratedLoop(MethodHandles.empty(v), null, MethodHandles.empty(v));
} catch(IllegalArgumentException iae) {
- assertEquals("iteratedLoop first argument must have Iterator return type", iae.getMessage());
+ assertEqualsFIXME("iteratedLoop first argument must have Iterator return type", iae.getMessage());
caught = true;
}
assertTrue(caught);
}
- @Test
- public static void testIterateVoidInit() throws Throwable {
- MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_voidInit, Iterate.MH_printStep);
+ @Test(dataProvider = "iteratorInits")
+ public static void testIterateVoidInit(MethodHandle iterator) throws Throwable {
+ // this test uses List as its loop state type; don't try to change that
+ if (iterator != null)
+ iterator = iterator.asType(iterator.type().changeParameterType(0, List.class));
+ MethodHandle loop = MethodHandles.iteratedLoop(iterator, Iterate.MH_voidInit, Iterate.MH_printStep);
assertEquals(Iterate.MT_print, loop.type());
loop.invoke(Arrays.asList("hello", "world"));
}
@@ -516,60 +785,79 @@
@DataProvider
static Object[][] iterateParameters() {
MethodType i = methodType(int.class);
- MethodType sil_i = methodType(int.class, String.class, int.class, List.class);
+ MethodType sil_v = methodType(void.class, String.class, int.class, List.class);
+ MethodType isl_i = methodType(int.class, int.class, String.class, List.class);
+ MethodType isli_i = methodType(int.class, int.class, String.class, List.class, int.class);
MethodType sl_v = methodType(void.class, String.class, List.class);
+ MethodType sli_v = methodType(void.class, String.class, List.class, int.class);
MethodType l_it = methodType(Iterator.class, List.class);
+ MethodType li_i = methodType(int.class, List.class, int.class);
MethodType li_it = methodType(Iterator.class, List.class, int.class);
+ MethodType il_it = methodType(Iterator.class, int.class, List.class);
MethodType l_i = methodType(int.class, List.class);
- MethodType _it = methodType(Iterator.class);
- MethodType si_i = methodType(int.class, String.class, int.class);
- MethodType s_i = methodType(int.class, String.class);
return new Object[][]{
- {null, null, sl_v},
- {null, i, sil_i},
- {null, l_i, sil_i},
- {l_it, null, sl_v},
- {l_it, i, sil_i},
- {li_it, l_i, sil_i},
- {l_it, null, sil_i},
- {li_it, null, sl_v},
- {_it, l_i, si_i},
- {_it, l_i, s_i}
+ {l_it, null, sl_v, ""},
+ {l_it, l_i, isl_i, ""},
+ {l_it, null, sl_v, ""},
+ {li_it, li_i, isli_i, ""},
+ {il_it, null, sil_v, "inferred first loop argument must inherit from Iterable: int"},
+ {li_it, null, sli_v, ""},
+ {sl_v, null, sl_v, "iteratedLoop first argument must have Iterator return type"},
+ {li_it, l_it, sl_v,
+ String.format("iterator and init parameter lists must match: %s != %s", li_it, l_it)},
+ {li_it, li_i, isl_i,
+ String.format("body types (regard parameter types after index 0, and result type) must match: %s != %s",
+ isl_i, isl_i.dropParameterTypes(0, 1).appendParameterTypes(int.class))}
};
}
@Test(dataProvider = "iterateParameters")
- public static void testIterateParameters(MethodType it, MethodType in, MethodType bo) throws Throwable {
+ public static void testIterateParameters(MethodType it, MethodType in, MethodType bo, String msg) {
+ boolean negative = !msg.isEmpty();
MethodHandle iterator = it == null ? null : MethodHandles.empty(it);
MethodHandle init = in == null ? null : MethodHandles.empty(in);
- MethodHandle loop = MethodHandles.iteratedLoop(iterator, init, MethodHandles.empty(bo));
- MethodType lt = loop.type();
- if (it == null && in == null) {
- assertEquals(bo.dropParameterTypes(0, 1), lt);
- } else if (it == null) {
- if (in.parameterCount() == 0) {
- assertEquals(bo.dropParameterTypes(0, in.returnType() == void.class ? 1 : 2), lt);
- } else {
- assertEquals(methodType(bo.returnType(), in.parameterArray()), lt);
+ boolean caught = false;
+ MethodHandle loop = null;
+ try {
+ loop = MethodHandles.iteratedLoop(iterator, init, MethodHandles.empty(bo));
+ } catch (Throwable t) {
+ if (!negative) {
+ throw t;
}
- } else if (in == null) {
- assertEquals(methodType(bo.returnType(), it.parameterArray()), lt);
- } else if (it.parameterCount() > in.parameterCount()) {
- assertEquals(methodType(bo.returnType(), it.parameterArray()), lt);
- } else if (it.parameterCount() < in.parameterCount()) {
- assertEquals(methodType(bo.returnType(), in.parameterArray()), lt);
+ assertEqualsFIXME(msg, t.getMessage());
+ caught = true;
+ }
+ if (negative) {
+ assertTrue(caught);
} else {
- // both it, in present; with equal parameter list lengths
- assertEquals(it.parameterList(), lt.parameterList());
- assertEquals(in.parameterList(), lt.parameterList());
- assertEquals(bo.returnType(), lt.returnType());
+ MethodType lt = loop.type();
+ if (it == null && in == null) {
+ assertEquals(bo.dropParameterTypes(0, 1), lt);
+ } else if (it == null) {
+ if (in.parameterCount() == 0) {
+ assertEquals(bo.dropParameterTypes(0, in.returnType() == void.class ? 1 : 2), lt);
+ } else {
+ assertEquals(methodType(bo.returnType(), in.parameterArray()), lt);
+ }
+ } else if (in == null) {
+ assertEquals(methodType(bo.returnType(), it.parameterArray()), lt);
+ } else if (it.parameterCount() > in.parameterCount()) {
+ assertEquals(methodType(bo.returnType(), it.parameterArray()), lt);
+ } else if (it.parameterCount() < in.parameterCount()) {
+ assertEquals(methodType(bo.returnType(), in.parameterArray()), lt);
+ } else {
+ // both it, in present; with equal parameter list lengths
+ assertEquals(it.parameterList(), lt.parameterList());
+ assertEquals(in.parameterList(), lt.parameterList());
+ assertEquals(bo.returnType(), lt.returnType());
+ }
}
}
@Test
public static void testIteratorSubclass() throws Throwable {
MethodHandle loop = MethodHandles.iteratedLoop(MethodHandles.empty(methodType(BogusIterator.class, List.class)),
- null, MethodHandles.empty(methodType(void.class, String.class)));
+ null, MethodHandles.empty(methodType(void.class, String.class, List.class)));
assertEquals(methodType(void.class, List.class), loop.type());
}
@@ -892,7 +1180,7 @@
return arg;
}
- static String step(int counter, String v, String arg) {
+ static String step(String v, int counter) {
return "na " + v;
}
@@ -904,15 +1192,15 @@
System.out.print("hello");
}
- static int addCounter(int counter, int x) {
+ static int addCounter(int x, int counter) {
return x + counter;
}
- static String stateBody(int counter, String s) {
+ static String stateBody(String s, int counter) {
return "s" + s + counter;
}
- static String append(int counter, String localState, String loopArg) {
+ static String append(String localState, int counter, String loopArg) {
if (null == localState) {
return loopArg + counter;
}
@@ -922,12 +1210,12 @@
static final Class<Counted> COUNTED = Counted.class;
static final MethodType MT_start = methodType(String.class, String.class);
- static final MethodType MT_step = methodType(String.class, int.class, String.class, String.class);
+ static final MethodType MT_step = methodType(String.class, String.class, int.class);
static final MethodType MT_stepUpdateArray = methodType(void.class, int.class, int[].class);
static final MethodType MT_printHello = methodType(void.class, int.class);
static final MethodType MT_addCounter = methodType(int.class, int.class, int.class);
- static final MethodType MT_stateBody = methodType(String.class, int.class, String.class);
- static final MethodType MT_append = methodType(String.class, int.class, String.class, String.class);
+ static final MethodType MT_stateBody = methodType(String.class, String.class, int.class);
+ static final MethodType MT_append = methodType(String.class, String.class, int.class, String.class);
static final MethodHandle MH_13;
static final MethodHandle MH_m5;
@@ -984,7 +1272,7 @@
return new ArrayList<>();
}
- static List<String> reverseStep(String e, List<String> r, List<String> l) {
+ static List<String> reverseStep(List<String> r, String e, List<String> l) {
r.add(0, e);
return r;
}
@@ -993,7 +1281,7 @@
return 0;
}
- static int lengthStep(Object o, int len, List<Double> l) {
+ static int lengthStep(int len, Object o, List<Double> l) {
return len + 1;
}
@@ -1001,7 +1289,7 @@
return new ArrayList<>();
}
- static List<String> mapStep(String e, List<String> r, List<String> l) {
+ static List<String> mapStep(List<String> r, String e, List<String> l) {
r.add(e.toUpperCase());
return r;
}
@@ -1010,10 +1298,18 @@
System.out.print(s);
}
- static void voidInit() {
+ static void voidInit(List<String> l) {
// empty
}
+ static ListIterator<?> iteratorFromList(List<?> l) {
+ return l.listIterator();
+ }
+
+ static Iterator<?> iteratorFromIterable(Iterable<?> l) {
+ return l.iterator();
+ }
+
static final Class<Iterate> ITERATE = Iterate.class;
static final MethodType MT_sumIterator = methodType(Iterator.class, Integer[].class);
@@ -1024,12 +1320,15 @@
static final MethodType MT_mapInit = methodType(List.class, List.class);
static final MethodType MT_sumStep = methodType(int.class, int.class, int.class, Integer[].class);
- static final MethodType MT_reverseStep = methodType(List.class, String.class, List.class, List.class);
- static final MethodType MT_lengthStep = methodType(int.class, Object.class, int.class, List.class);
- static final MethodType MT_mapStep = methodType(List.class, String.class, List.class, List.class);
+ static final MethodType MT_reverseStep = methodType(List.class, List.class, String.class, List.class);
+ static final MethodType MT_lengthStep = methodType(int.class, int.class, Object.class, List.class);
+ static final MethodType MT_mapStep = methodType(List.class, List.class, String.class, List.class);
static final MethodType MT_printStep = methodType(void.class, String.class, List.class);
- static final MethodType MT_voidInit = methodType(void.class);
+ static final MethodType MT_voidInit = methodType(void.class, List.class);
+
+ static final MethodType MT_iteratorFromList = methodType(ListIterator.class, List.class);
+ static final MethodType MT_iteratorFromIterable = methodType(Iterator.class, Iterable.class);
static final MethodHandle MH_sumIterator;
static final MethodHandle MH_sumInit;
@@ -1047,6 +1346,9 @@
static final MethodHandle MH_voidInit;
+ static final MethodHandle MH_iteratorFromList;
+ static final MethodHandle MH_iteratorFromIterable;
+
static final MethodType MT_sum = methodType(int.class, Integer[].class);
static final MethodType MT_reverse = methodType(List.class, List.class);
static final MethodType MT_length = methodType(int.class, List.class);
@@ -1066,6 +1368,8 @@
MH_mapStep = LOOKUP.findStatic(ITERATE, "mapStep", MT_mapStep);
MH_printStep = LOOKUP.findStatic(ITERATE, "printStep", MT_printStep);
MH_voidInit = LOOKUP.findStatic(ITERATE, "voidInit", MT_voidInit);
+ MH_iteratorFromList = LOOKUP.findStatic(ITERATE, "iteratorFromList", MT_iteratorFromList);
+ MH_iteratorFromIterable = LOOKUP.findStatic(ITERATE, "iteratorFromIterable", MT_iteratorFromIterable);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}