1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test
 26  * @run testng TestLambdaOps
 27  */
 28 
 29 import org.testng.Assert;
 30 import org.testng.annotations.DataProvider;
 31 import org.testng.annotations.Test;
 32 
 33 import java.lang.reflect.code.*;
 34 import java.lang.reflect.code.op.CoreOp;
 35 import java.lang.reflect.code.op.CoreOp.FuncOp;
 36 import java.lang.reflect.code.op.CoreOp.LambdaOp;
 37 import java.lang.reflect.code.type.MethodRef;
 38 import java.lang.reflect.code.interpreter.Interpreter;
 39 import java.lang.invoke.MethodHandles;
 40 import java.lang.reflect.Method;
 41 import java.lang.runtime.CodeReflection;
 42 import java.util.*;
 43 import java.util.function.BiFunction;
 44 import java.util.function.Function;
 45 import java.util.function.IntSupplier;
 46 import java.util.function.IntUnaryOperator;
 47 import java.util.stream.Stream;
 48 
 49 import static java.lang.reflect.code.op.CoreOp.*;
 50 import static java.lang.reflect.code.op.CoreOp.constant;
 51 import static java.lang.reflect.code.type.FunctionType.functionType;
 52 import static java.lang.reflect.code.type.JavaType.INT;
 53 import static java.lang.reflect.code.type.JavaType.type;
 54 
 55 @Test
 56 public class TestLambdaOps {
 57     static class Builder {
 58         static final MethodRef ACCEPT_METHOD = MethodRef.method(type(Builder.class), "accept",
 59                 INT, CoreOp.QuotedOp.QUOTED_TYPE);
 60 
 61         static int accept(Quoted l) {
 62             Assert.assertEquals(1, l.capturedValues().size());
 63             Assert.assertEquals(1, l.capturedValues().values().iterator().next());
 64 
 65             int r = (int) Interpreter.invoke(MethodHandles.lookup(), (Op & Op.Invokable) l.op(),
 66                     l.capturedValues(), 42);
 67             return r;
 68         }
 69     }
 70 
 71     @Test
 72     public void testQuotedWithCapture() {
 73         // functional type = (int)int
 74         FuncOp f = func("f", functionType(INT, INT))
 75                 .body(block -> {
 76                     Block.Parameter i = block.parameters().get(0);
 77 
 78                     // functional type = (int)int
 79                     // op type = ()Quoted<LambdaOp>
 80                     QuotedOp qop = quoted(block.parentBody(), qblock -> {
 81                         return lambda(qblock.parentBody(),
 82                                 functionType(INT, INT), type(IntUnaryOperator.class))
 83                                 .body(lblock -> {
 84                                     Block.Parameter li = lblock.parameters().get(0);
 85 
 86                                     lblock.op(_return(
 87                                             // capture i from function's body
 88                                             lblock.op(add(i, li))
 89                                     ));
 90                                 });
 91                     });
 92                     Op.Result lquoted = block.op(qop);
 93 
 94                     Op.Result or = block.op(invoke(Builder.ACCEPT_METHOD, lquoted));
 95                     block.op(_return(or));
 96                 });
 97 
 98         f.writeTo(System.out);
 99 
100         int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
101         Assert.assertEquals(ir, 43);
102     }
103 
104     static final MethodRef INT_UNARY_OPERATOR_METHOD = MethodRef.method(
105             IntUnaryOperator.class, "applyAsInt",
106             int.class, int.class);
107 
108     @Test
109     public void testWithCapture() {
110         // functional type = (int)int
111         FuncOp f = func("f", functionType(INT, INT))
112                 .body(block -> {
113                     Block.Parameter i = block.parameters().get(0);
114 
115                     // functional type = (int)int
116                     // op type = ()IntUnaryOperator
117                     //   captures i
118                     LambdaOp lambda = lambda(block.parentBody(),
119                             functionType(INT, INT), type(IntUnaryOperator.class))
120                             .body(lblock -> {
121                                 Block.Parameter li = lblock.parameters().get(0);
122 
123                                 lblock.op(_return(
124                                         lblock.op(add(i, li))));
125                             });
126                     Op.Result fi = block.op(lambda);
127 
128                     Op.Result fortyTwo = block.op(constant(INT, 42));
129                     Op.Result or = block.op(invoke(INT_UNARY_OPERATOR_METHOD, fi, fortyTwo));
130                     block.op(_return(or));
131                 });
132 
133         f.writeTo(System.out);
134 
135         int ir = (int) Interpreter.invoke(MethodHandles.lookup(), f, 1);
136         Assert.assertEquals(ir, 43);
137     }
138 
139     static int f(int i) {
140         IntUnaryOperator fi = li -> {
141             return i + li;
142         };
143 
144         int fortyTwo = 42;
145         int or = fi.applyAsInt(fortyTwo);
146         return or;
147     }
148 
149     @Test
150     public void testQuotableModel() {
151         Quotable quotable = (Runnable & Quotable) () -> {};
152         Op qop = quotable.quoted().op();
153         Op top = qop.ancestorBody().parentOp().ancestorBody().parentOp();
154         Assert.assertTrue(top instanceof CoreOp.FuncOp);
155 
156         CoreOp.FuncOp fop = (CoreOp.FuncOp) top;
157         Assert.assertEquals(type(Quoted.class), fop.invokableType().returnType());
158     }
159 
160     @FunctionalInterface
161     public interface QuotableIntSupplier extends IntSupplier, Quotable {
162     }
163 
164     @CodeReflection
165     static QuotableIntSupplier quote(int i) {
166         QuotableIntSupplier s = () -> i;
167         return s;
168     }
169 
170     @Test
171     public void testQuote() {
172         FuncOp g = getFuncOp("quote");
173         g.writeTo(System.out);
174 
175         {
176             QuotableIntSupplier op = (QuotableIntSupplier) Interpreter.invoke(MethodHandles.lookup(), g, 42);
177             Assert.assertEquals(op.getAsInt(), 42);
178 
179             Quoted q = op.quoted();
180             q.op().writeTo(System.out);
181             Assert.assertEquals(q.capturedValues().size(), 1);
182             Assert.assertEquals(((Var<?>)q.capturedValues().values().iterator().next()).value(), 42);
183 
184             int r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), q.capturedValues(), List.of());
185             Assert.assertEquals(r, 42);
186 
187             Map<Value, Object> cvs = Map.of(
188                     q.capturedValues().keySet().iterator().next(),
189                     CoreOp.Var.of(0)
190             );
191             r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), cvs, List.of());
192             Assert.assertEquals(r, 0);
193         }
194 
195         {
196             QuotableIntSupplier op = quote(42);
197             Assert.assertEquals(op.getAsInt(), 42);
198 
199             Quoted q = op.quoted();
200             q.op().writeTo(System.out);
201             System.out.print(q.capturedValues().values());
202             Assert.assertEquals(q.capturedValues().size(), 1);
203             Assert.assertEquals(((Var<?>)q.capturedValues().values().iterator().next()).value(), 42);
204 
205             int r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), q.capturedValues(), List.of());
206             Assert.assertEquals(r, 42);
207 
208             Map<Value, Object> cvs = Map.of(
209                     q.capturedValues().keySet().iterator().next(),
210                     CoreOp.Var.of(0)
211             );
212             r = (int) Interpreter.invoke(MethodHandles.lookup(), (LambdaOp) q.op(), cvs, List.of());
213             Assert.assertEquals(r, 0);
214         }
215     }
216 
217 
218     interface QuotableIntUnaryOperator extends IntUnaryOperator, Quotable {}
219 
220     interface QuotableFunction<T, R> extends Function<T, R>, Quotable {}
221 
222     interface QuotableBiFunction<T, U, R> extends BiFunction<T, U, R>, Quotable {}
223 
224     @DataProvider
225     Iterator<Quotable> methodRefLambdas() {
226         return List.of(
227                 (QuotableIntUnaryOperator) TestLambdaOps::m1,
228                 (QuotableIntUnaryOperator) TestLambdaOps::m2,
229                 (QuotableFunction<Integer, Integer>) TestLambdaOps::m1,
230                 (QuotableFunction<Integer, Integer>) TestLambdaOps::m2,
231                 (QuotableIntUnaryOperator) this::m3,
232                 (QuotableBiFunction<TestLambdaOps, Integer, Integer>) TestLambdaOps::m4
233         ).iterator();
234     }
235 
236     @Test(dataProvider = "methodRefLambdas")
237     public void testIsMethodReference(Quotable q) {
238         Quoted quoted = q.quoted();
239         CoreOp.LambdaOp lop = (CoreOp.LambdaOp) quoted.op();
240         Assert.assertTrue(lop.methodReference().isPresent());
241     }
242 
243     static int m1(int i) {
244         return i;
245     }
246 
247     static Integer m2(Integer i) {
248         return i;
249     }
250 
251     int m3(int i) {
252         return i;
253     }
254 
255     static int m4(TestLambdaOps tl, int i) {
256         return i;
257     }
258 
259 
260     static CoreOp.FuncOp getFuncOp(String name) {
261         Optional<Method> om = Stream.of(TestLambdaOps.class.getDeclaredMethods())
262                 .filter(m -> m.getName().equals(name))
263                 .findFirst();
264 
265         Method m = om.get();
266         return m.getCodeModel().get();
267     }
268 }