I have encountered an issue compiling a lambda using a closure in a nested lambda.
public class CustomTests {
public static <T, R> R getOrNull(final T instance, @NotNull final Func1<T, R> getter) {
if (instance == null) {
return null;
}
return getter.apply(instance);
}
@Test
public void nestedLambdaWithClosureCompilation() {
final Type<Ctx> ctxType = Type.of(Ctx.class);
final Type<Ctx.Item> itemType = Type.of(Ctx.Item.class);
final MethodInfo getOrNullMethod =
Type.of(CustomTests.class).getMethod("getOrNull", BindingFlags.PublicStatic, Types.Object, Type.of(Func1.class));
// Reference expression: (Ctx ctx) -> getOrNull(ctx, x -> getOrNull(ctx.items.get("key_2"), k -> k.value))
// 1. Create parameter [(Ctx ctx)]
final ParameterExpression ctxParameter = Expression.parameter(ctxType, "ctx");
// 2. Create value lambda getter [(Ctx.Item k) -> k.value]
final ParameterExpression kParameter = Expression.parameter(itemType, "k");
final MemberExpression itemGetterBody = Expression.field(kParameter, itemType.getField("value"));
final Type<Func1<Ctx.Item, Object>> itemGetterDelegateType = Type.of(Func1.class).makeGenericType(itemType, Types.Object);
final LambdaExpression<Func1<Ctx.Item, Object>> itemGetter = Expression.lambda(itemGetterDelegateType, itemGetterBody, kParameter);
// 3. Create item accessor [ctx.items.get("key_2")]
final MemberExpression itemsAccessor = Expression.field(ctxParameter, ctxType.getField("items"));
final MethodCallExpression getValueByKey =
Expression.call(itemsAccessor, itemsAccessor.getType().getMethod("get", Types.String), Expression.constant("key_2"));
// 4. Create conditional getter [getOrNull(ctx.items.get("key_2"), k -> k.value)]
final MethodCallExpression getValueConditional =
Expression.call(getOrNullMethod.makeGenericMethod(getValueByKey.getType(), Types.Object), getValueByKey, itemGetter);
// 5. Create conditional getter as lambda getter [(Ctx x) -> getOrNull(ctx.items.get("key_2"), k -> k.value)] (A closure is used instead of the parameter)
final ParameterExpression xParameter = Expression.parameter(ctxType, "x");
final Type<Func1<Ctx, Object>> getValueConditionalDelegateType = Type.of(Func1.class).makeGenericType(ctxType, Types.Object);
final LambdaExpression<Func1<Ctx, Object>> getValueConditionalLambda =
Expression.lambda(getValueConditionalDelegateType, getValueConditional, xParameter);
// 6. Create lambda [getOrNull(ctx, x -> getOrNull(ctx.items.get("key_2"), k -> k.value))]
final MethodCallExpression body = Expression.call(getOrNullMethod.makeGenericMethod(ctxType, Types.Object), ctxParameter, getValueConditionalLambda);
final LambdaExpression<Func1<Ctx, Object>> lambda =
Expression.lambda(Type.of(Func1.class).<Func1<Ctx, Object>>makeGenericType(ctxType, Types.Object), body, ctxParameter);
final Func1<Ctx, Object> delegate = lambda.compile();
assertNotNull(delegate);
}
private static class Ctx {
public Map<String, Item> items = new LinkedHashMap<>();
public Double number = 23.3;
public Ctx() {
items.put("key_2", new Item());
}
public static class Item {
public Integer value = 55;
}
}
}