1 /*
2 * Copyright (c) 2024, 2026, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.incubator.code;
27
28 import com.sun.tools.javac.api.JavacScope;
29 import com.sun.tools.javac.api.JavacTrees;
30 import com.sun.tools.javac.code.Symbol.ClassSymbol;
31 import com.sun.tools.javac.comp.Attr;
32 import com.sun.tools.javac.model.JavacElements;
33 import com.sun.tools.javac.processing.JavacProcessingEnvironment;
34 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
35 import com.sun.tools.javac.tree.TreeMaker;
36 import com.sun.tools.javac.util.Context;
37 import jdk.incubator.code.dialect.core.CoreType;
38 import jdk.incubator.code.dialect.java.JavaOp;
39 import jdk.incubator.code.internal.ReflectMethods;
40 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
41 import jdk.incubator.code.dialect.core.FunctionType;
42 import jdk.incubator.code.dialect.java.MethodRef;
43 import jdk.incubator.code.extern.OpWriter;
44 import jdk.internal.access.SharedSecrets;
45
46 import javax.annotation.processing.ProcessingEnvironment;
47 import javax.lang.model.element.ExecutableElement;
48 import javax.lang.model.element.Modifier;
49 import java.lang.reflect.Method;
50 import java.lang.reflect.Proxy;
51 import java.util.*;
52 import java.util.function.BiFunction;
53
54 /**
55 * An operation modeling a unit of program behavior.
56 * <p>
57 * An operation might model the addition of two integers, or a method invocation expression.
58 * Alternatively an operation may model something more complex like method declarations, lambda expressions, or
59 * try statements. In such cases an operation will contain one or more bodies modeling the nested structure.
60 * <p>
61 * An operation when initially constructed is unattached from any parent block.
62 * An unattached operation's state and descendant elements are all immutable except for the operation's
63 * {@link #result result}, {@link #parent parent} block, and {@link #location}, which are all initially {@code null}.
64 * <p>
65 * An unattached operation can be attached to a block by {@link Block.Builder#op(Op) appending} it to a block that is
66 * being built. Once attached the operation has a permanently non-{@code null} operation {@link #result} that can be
67 * used as an operand of subsequent constructed operations.
68 * After the block is built the operation has a permanently non-{@code null} {@link #parent}. Before then, the block
69 * being built is not observable through this operation and any attempt to access the block results in an exception.
70 * <p>
71 * An unattached operation can be built as a {@link #isRoot() root} operation. Once built the operation's
72 * {@link #result result} and {@link #parent parent} are always {@code null}.
73 * <p>
74 * The {@link #location} may be {@link #setLocation set} while the operation is unattached or attached to a block being
75 * built.
76 * <p>
77 * An operation can only be constructed with operands whose declaring block is being built, otherwise construction fails
78 * with an exception.
79 */
80 public non-sealed abstract class Op implements CodeElement<Op, Body> {
81
82 /**
83 * An operation characteristic indicating the operation is pure and has no side effects.
84 */
85 public interface Pure {
86 }
87
88 /**
89 * An operation characteristic indicating the operation has one or more bodies.
90 */
91 public interface Nested {
92 /**
93 * {@return the bodies of the nested operation.}
94 */
95 List<Body> bodies();
96 }
97
98 /**
99 * An operation characteristic indicating the operation represents a loop
100 */
101 public interface Loop extends Nested {
102 /**
103 * {@return the body of the loop operation.}
104 */
105 Body loopBody();
106 }
107
108 /**
109 * An operation characteristic indicating the operation has one or more bodies,
110 * all of which are isolated and capture no values.
111 */
112 public interface Isolated extends Nested {
113 }
114
115 /**
116 * An operation characteristic indicating the operation is invokable.
117 */
118 public interface Invokable extends Nested {
119 /**
120 * {@return the body of the invokable operation.}
121 */
122 Body body();
123
124 /**
125 * {@return the invokable operation's signature, represented as a function type.}
126 * @implSpec
127 * The default implementation returns the signature of the invokable operation's body.
128 */
129 default FunctionType invokableSignature() {
130 return body().bodySignature();
131 }
132
133 /**
134 * {@return the entry block parameters of this operation's body}
135 * @implSpec
136 * The default implementation returns the entry block's parameters of the invokable operation's body.
137 */
138 default List<Block.Parameter> parameters() {
139 return body().entryBlock().parameters();
140 }
141
142 /**
143 * Computes values captured by this invokable operation's body.
144 * @implSpec
145 * The default implementation returns an empty unmodifiable list.
146 *
147 * @return the captured values.
148 * @see Body#capturedValues()
149 */
150 default List<Value> capturedValues() {
151 return List.of();
152 }
153 }
154
155 /**
156 * An operation characteristic indicating the operation can replace itself with a lowered form.
157 */
158 // @@@ Hide this abstraction within JavaOp?
159 public interface Lowerable {
160
161 /**
162 * Lowers this operation into the block builder, commonly replacing nested structure
163 * with interconnected basic blocks. The previous lowering code transformation
164 * is used to compose with a lowering transformation that is applied to bodies
165 * of this operation, ensuring lowering is applied consistently to nested content.
166 *
167 * @param b the block builder
168 * @param opT the previous lowering code transformation, may be {@code null}
169 * @return the block builder to use for further building
170 */
171 Block.Builder lower(Block.Builder b, CodeTransformer opT);
172
173 /**
174 * Returns a composed code transformer that composes with an operation transformer function adapted to lower
175 * operations.
176 * <p>
177 * This method behaves as if it returns the result of the following expression:
178 * {@snippet lang = java:
179 * CodeTransformer.andThen(before, lowering(before, f));
180 *}
181 *
182 * @param before the code transformer to apply before
183 * @param f the operation transformer function to apply after
184 * @return the composed code transformer
185 */
186 static CodeTransformer andThenLowering(CodeTransformer before, BiFunction<Block.Builder, Op, Block.Builder> f) {
187 return CodeTransformer.andThen(before, lowering(before, f));
188 }
189
190 /**
191 * Returns an adapted operation transformer function that adapts an operation transformer function
192 * {@code f} to also transform lowerable operations.
193 * <p>
194 * The adapted operation transformer function first applies a block builder and operation
195 * to the operation transformer function {@code f}.
196 * If the result is not {@code null} then the result is returned.
197 * Otherwise, if the operation is a lowerable operation then the result of applying the
198 * block builder and code transformer {@code before} to {@link Lowerable#lower lower}
199 * of the lowerable operation is returned.
200 * Otherwise, the operation is copied by applying it to {@link Block.Builder#op op} of the block builder,
201 * and the block builder is returned.
202 *
203 * @param before the code transformer to apply for lowering
204 * @param f the operation transformer function to apply after
205 * @return the adapted operation transformer function
206 */
207 static BiFunction<Block.Builder, Op, Block.Builder> lowering(CodeTransformer before, BiFunction<Block.Builder, Op, Block.Builder> f) {
208 return (block, op) -> {
209 Block.Builder b = f.apply(block, op);
210 if (b == null) {
211 if (op instanceof Lowerable lop) {
212 block = lop.lower(block, before);
213 } else {
214 block.op(op);
215 }
216 } else {
217 block = b;
218 }
219 return block;
220 };
221 }
222 }
223
224 /**
225 * An operation characteristic indicating the operation is a terminating operation
226 * occurring as the last operation in a block.
227 * <p>
228 * A terminating operation passes control to either another block within the same parent body
229 * or to that parent body.
230 */
231 public interface Terminating {
232 }
233
234 /**
235 * An operation characteristic indicating the operation is a body terminating operation
236 * occurring as the last operation in a block.
237 * <p>
238 * A body terminating operation passes control back to its nearest ancestor body.
239 */
240 public interface BodyTerminating extends Terminating {
241 }
242
243 /**
244 * An operation characteristic indicating the operation is a block terminating operation
245 * occurring as the last operation in a block.
246 * <p>
247 * The operation has one or more successors to other blocks within the same parent body, and passes
248 * control to one of those blocks.
249 */
250 public interface BlockTerminating extends Terminating {
251 /**
252 * {@return the list of successor blocks associated with this block terminating operation}
253 */
254 List<Block.Reference> successors();
255 }
256
257 /**
258 * A value that is the result of an operation.
259 */
260 public static final class Result extends Value {
261
262 /**
263 * If assigned to an operation result, it indicates the operation is a root operation
264 */
265 private static final Result ROOT_RESULT = new Result();
266
267 final Op op;
268
269 private Result() {
270 // Constructor for instance of ROOT_RESULT
271 super(null, null);
272 this.op = null;
273 }
274
275 Result(Block block, Op op) {
276 super(block, op.resultType());
277
278 this.op = op;
279 }
280
281 @Override
282 public String toString() {
283 return "%result@" + Integer.toHexString(hashCode());
284 }
285
286 @Override
287 public SequencedSet<Value> dependsOn() {
288 SequencedSet<Value> depends = new LinkedHashSet<>(op.operands());
289 if (op instanceof Terminating) {
290 op.successors().stream().flatMap(h -> h.arguments().stream()).forEach(depends::add);
291 }
292
293 return Collections.unmodifiableSequencedSet(depends);
294 }
295
296 /**
297 * {@return the result's declaring operation.}
298 */
299 public Op op() {
300 return op;
301 }
302 }
303
304 /**
305 * Source location information for an operation.
306 *
307 * @param sourceRef the reference to the source, {@code null} if absent
308 * @param line the line in the source
309 * @param column the column in the source
310 */
311 public record Location(String sourceRef, int line, int column) {
312
313 /**
314 * The location value, {@code null}, indicating no location information.
315 */
316 public static final Location NO_LOCATION = null;
317
318 /**
319 * Constructions a location with line and column only.
320 *
321 * @param line the line in the source
322 * @param column the column in the source
323 */
324 public Location(int line, int column) {
325 this(null, line, column);
326 }
327 }
328
329 // Set when op is attached to a block or root, otherwise null when unattached
330 // @@@ stable value?
331 Result result;
332
333 // null if not specified
334 // @@@ stable value?
335 Location location;
336
337 final List<Value> operands;
338
339 /**
340 * Constructs an operation from a given operation.
341 * <p>
342 * The constructor defers to the {@link Op#Op(List) operands} constructor passing a list of values computed, in
343 * order, by mapping the given operation's operands using the code context. The constructor also assigns the new
344 * operation's location to the given operation's location, if any.
345 *
346 * @param that the given operation.
347 * @param cc the code context.
348 */
349 protected Op(Op that, CodeContext cc) {
350 List<Value> outputOperands = cc.getValues(that.operands);
351 // Values should be guaranteed to connect to blocks being built since
352 // the context only allows such mappings, assert for clarity
353 assert outputOperands.stream().noneMatch(Value::isBuilt);
354 this.operands = List.copyOf(outputOperands);
355 this.location = that.location;
356 }
357
358 /**
359 * Copies this operation and transforms its bodies, if any.
360 * <p>
361 * Bodies are {@link Body#transform(CodeContext, CodeTransformer) transformed} with the given code context and
362 * code transformer.
363 * @apiNote
364 * To copy an operation use the {@link CodeTransformer#COPYING_TRANSFORMER copying transformer}.
365 *
366 * @param cc the code context.
367 * @param ot the code transformer.
368 * @return the transformed operation.
369 * @see CodeTransformer#COPYING_TRANSFORMER
370 */
371 public abstract Op transform(CodeContext cc, CodeTransformer ot);
372
373 /**
374 * Constructs an operation with a list of operands.
375 *
376 * @param operands the list of operands, a copy of the list is performed if required.
377 * @throws IllegalArgumentException if an operand's declaring block is built.
378 */
379 protected Op(List<? extends Value> operands) {
380 for (Value operand : operands) {
381 if (operand.isBuilt()) {
382 throw new IllegalArgumentException("Operand's declaring block is built: " + operand);
383 }
384 }
385 this.operands = List.copyOf(operands);
386 }
387
388 /**
389 * Sets the originating source location of this operation, if this operation is not built.
390 *
391 * @param l the location, the {@link Location#NO_LOCATION} value indicates the location is not specified.
392 * @throws IllegalStateException if this operation is built.
393 */
394 public final void setLocation(Location l) {
395 // @@@ Fail if location != null?
396 if (isRoot() || (result != null && result.block.isBuilt())) {
397 throw new IllegalStateException("Built operation");
398 }
399
400 location = l;
401 }
402
403 /**
404 * {@return the originating source location of this operation, otherwise {@code null} if not specified}
405 */
406 public final Location location() {
407 return location;
408 }
409
410 /**
411 * Returns this operation's parent block, otherwise {@code null} if this operation is unattached to a block or a
412 * root operation.
413 * <p>
414 * The operation's parent block is the same as the operation result's {@link Value#declaringBlock declaring block}.
415 *
416 * @return operation's parent block, or {@code null} if this operation is unattached or a root operation.
417 * @throws IllegalStateException if this operation is attached and the parent block is being built and is not
418 * observable.
419 * @see Value#declaringBlock()
420 */
421 @Override
422 public final Block parent() {
423 if (isRoot() || result == null) {
424 return null;
425 }
426
427 if (!result.block.isBuilt()) {
428 throw new IllegalStateException("Parent block is being built and is not observable");
429 }
430
431 return result.block;
432 }
433
434 @Override
435 public final List<Body> children() {
436 return bodies();
437 }
438
439 /**
440 * {@return the operation's bodies, as an unmodifiable list}
441 * @implSpec this implementation returns an unmodifiable empty list.
442 * @see #children()
443 */
444 public List<Body> bodies() {
445 return List.of();
446 }
447
448 /**
449 * {@return the operation's result type}
450 */
451 public abstract CodeType resultType();
452
453
454 /**
455 * {@return the operation's result, or {@code null} if this operation is unattached or a
456 * root operation.}
457 */
458 public final Result result() {
459 return result == Result.ROOT_RESULT ? null : result;
460 }
461
462 /**
463 * {@return the operation's operands, as an unmodifiable list}
464 */
465 public final List<Value> operands() {
466 return operands;
467 }
468
469 /**
470 * {@return the operation's successors, as an unmodifiable list}
471 * @implSpec this implementation returns an unmodifiable empty list.
472 */
473 public List<Block.Reference> successors() {
474 return List.of();
475 }
476
477 /**
478 * Returns the operation's signature, represented as a function type.
479 * <p>
480 * The signature's return type is the operation's result type and its parameter types are the
481 * operation's operand types, in order.
482 *
483 * @return the operation's signature
484 */
485 public final FunctionType opSignature() {
486 List<CodeType> operandTypes = operands.stream().map(Value::type).toList();
487 return CoreType.functionType(resultType(), operandTypes);
488 }
489
490 /**
491 * Computes values captured by this operation. A captured value is a value that is used
492 * but not declared by any descendant operation of this operation.
493 * <p>
494 * The order of the captured values is first use encountered in depth
495 * first search of this operation's descendant operations.
496 *
497 * @return the list of captured values, modifiable
498 * @throws IllegalStateException if an encountered block is being built and is not observable.
499 * @see Body#capturedValues()
500 */
501 public final List<Value> capturedValues() {
502 Set<Value> cvs = new LinkedHashSet<>();
503
504 Deque<Body> bodyStack = new ArrayDeque<>();
505 for (Body childBody : bodies()) {
506 Body.capturedValues(cvs, bodyStack, childBody);
507 }
508 return new ArrayList<>(cvs);
509 }
510
511 /**
512 * Builds this operation to become a built root operation. After this operation is built its
513 * {@link #result result} and {@link #parent parent} will always be {@code null}.
514 * <p>
515 * This method is idempotent.
516 *
517 * @throws IllegalStateException if this operation is attached to a block.
518 * @see #isRoot()
519 */
520 public final void buildAsRoot() {
521 if (result == Result.ROOT_RESULT) {
522 return;
523 }
524 if (result != null) {
525 throw new IllegalStateException("Operation is attached to a block");
526 }
527 result = Result.ROOT_RESULT;
528 }
529
530 /**
531 * {@return {@code true} if this operation is a root operation.}
532 * @see #buildAsRoot()
533 * @see #isAttached()
534 * */
535 public final boolean isRoot() {
536 return result == Result.ROOT_RESULT;
537 }
538
539 /**
540 * {@return {@code true} if this operation is attached to a block.}
541 * @see #buildAsRoot()
542 * @see #isRoot()
543 * */
544 public final boolean isAttached() {
545 return !isRoot() && result != null;
546 }
547
548 /**
549 * Externalizes this operation's name as a string.
550 * @implSpec this implementation returns the result of the expression {@code this.getClass().getName()}.
551 *
552 * @return the operation name
553 */
554 public String externalizeOpName() {
555 return this.getClass().getName();
556 }
557
558 /**
559 * Externalizes this operation's specific state as a map of attributes.
560 *
561 * <p>A null attribute value is represented by the constant
562 * value {@link jdk.incubator.code.extern.ExternalizedOp#NULL_ATTRIBUTE_VALUE}.
563 * @implSpec this implementation returns an unmodifiable empty map.
564 *
565 * @return the operation's externalized state, as an unmodifiable map
566 */
567 public Map<String, Object> externalize() {
568 return Map.of();
569 }
570
571 /**
572 * Returns the code model text for this operation.
573 * <p>
574 * The format of codel model text is unspecified.
575 *
576 * @return the code model text for this operation.
577 * @apiNote Code model text is designed to be human-readable and is intended for debugging, testing,
578 * and comprehension.
579 * @see OpWriter#toText(Op, OpWriter.Option...)
580 */
581 public final String toText() {
582 return OpWriter.toText(this);
583 }
584
585
586 /**
587 * Returns a quoted instance containing the code model of a reflectable lambda expression or method reference.
588 * <p>
589 * The quoted instance also contains a mapping from {@link Value values} in the code model that model final, or
590 * effectively final, variables used but not declared in the lambda expression to their corresponding run time
591 * values. Such run time values are commonly referred to as captured values.
592 * <p>
593 * Repeated invocations of this method will return a quoted instance containing the same instance of the code model.
594 * Therefore, code elements (and more generally code items) contained within the code model can be reliably compared
595 * using object identity.
596 *
597 * @param fiInstance a functional interface instance that is the result of a reflectable lambda expression or
598 * method reference.
599 * @return the quoted instance containing the code model, or an empty optional if the functional interface instance
600 * is not the result of a reflectable lambda expression or method reference.
601 * @throws UnsupportedOperationException if the Java version used at compile time to generate and store the code
602 * model is not the same as the Java version used at runtime to load the code model.
603 * @apiNote if the functional interface instance is a proxy instance, then the quoted code model is unavailable and
604 * this method returns an empty optional.
605 */
606 public static Optional<Quoted<JavaOp.LambdaOp>> ofLambda(Object fiInstance) {
607 Object oq = fiInstance;
608 if (Proxy.isProxyClass(oq.getClass())) {
609 // @@@ The interpreter implements interpretation of
610 // lambdas using a proxy whose invocation handler
611 // supports the internal protocol to access the quoted instance
612 oq = Proxy.getInvocationHandler(oq);
613 }
614
615 Method method;
616 try {
617 method = oq.getClass().getDeclaredMethod("__internal_quoted");
618 } catch (NoSuchMethodException e) {
619 return Optional.empty();
620 }
621 method.setAccessible(true);
622
623 Quoted<?> q;
624 try {
625 q = (Quoted<?>) method.invoke(oq);
626 } catch (ReflectiveOperationException e) {
627 // op method may throw UOE in case java compile time version doesn't match runtime version
628 if (e.getCause() instanceof UnsupportedOperationException uoe) {
629 throw uoe;
630 }
631 throw new RuntimeException(e);
632 }
633 if (!(q.op() instanceof JavaOp.LambdaOp)) {
634 // This can only happen if the stored model is invalid
635 throw new RuntimeException("Invalid code model for lambda expression : " + q);
636 }
637 @SuppressWarnings("unchecked")
638 Quoted<JavaOp.LambdaOp> lq = (Quoted<JavaOp.LambdaOp>) q;
639 return Optional.of(lq);
640 }
641
642 /**
643 * Returns the code model of a reflectable method.
644 * <p>
645 * Repeated invocations of this method will return the same instance of the code model. Therefore,
646 * code elements (and more generally code items) contained within the code model can be reliably compared using
647 * object identity.
648 *
649 * @param method the method.
650 * @return the code model, or an empty optional if the method is not reflectable.
651 * @throws UnsupportedOperationException if the Java version used at compile time to generate and store the code
652 * model is not the same as the Java version used at runtime to load the code model.
653 */
654 // @@@ Make caller sensitive with the same access control as invoke
655 // and throwing IllegalAccessException
656 // @CallerSensitive
657 @SuppressWarnings("unchecked")
658 public static Optional<FuncOp> ofMethod(Method method) {
659 return (Optional<FuncOp>)SharedSecrets.getJavaLangReflectAccess()
660 .setCodeModelIfNeeded(method, Op::createCodeModel);
661 }
662
663 private static Optional<FuncOp> createCodeModel(Method method) {
664 char[] sig = MethodRef.method(method).toString().toCharArray();
665 for (int i = 0; i < sig.length; i++) {
666 switch (sig[i]) {
667 case '.', ';', '[', '/': sig[i] = '$';
668 }
669 }
670 String opMethodName = new String(sig);
671 Method opMethod;
672 try {
673 // @@@ Use method handle with full power mode
674 opMethod = method.getDeclaringClass().getDeclaredMethod(opMethodName);
675 } catch (NoSuchMethodException e) {
676 return Optional.empty();
677 }
678 opMethod.setAccessible(true);
679 try {
680 FuncOp funcOp = (FuncOp) opMethod.invoke(null);
681 return Optional.of(funcOp);
682 } catch (ReflectiveOperationException e) {
683 // op method may throw UOE in case java compile time version doesn't match runtime version
684 if (e.getCause() instanceof UnsupportedOperationException uoe) {
685 throw uoe;
686 }
687 throw new RuntimeException(e);
688 }
689 }
690
691 /**
692 * Returns the code model of provided executable element (if any).
693 * <p>
694 * If the executable element has a code model then it will be an instance of
695 * {@code java.lang.reflect.code.op.CoreOps.FuncOp}.
696 * Note: due to circular dependencies we cannot refer to the type explicitly.
697 *
698 * @param processingEnvironment the annotation processing environment
699 * @param e the executable element.
700 * @return the code model of the provided executable element (if any).
701 */
702 public static Optional<FuncOp> ofElement(ProcessingEnvironment processingEnvironment, ExecutableElement e) {
703 if (e.getModifiers().contains(Modifier.ABSTRACT) ||
704 e.getModifiers().contains(Modifier.NATIVE)) {
705 return Optional.empty();
706 }
707
708 Context context = ((JavacProcessingEnvironment)processingEnvironment).getContext();
709 ReflectMethods reflectMethods = ReflectMethods.instance(context);
710 Attr attr = Attr.instance(context);
711 JavacElements elements = JavacElements.instance(context);
712 JavacTrees javacTrees = JavacTrees.instance(context);
713 TreeMaker make = TreeMaker.instance(context);
714 try {
715 JCMethodDecl methodTree = (JCMethodDecl)elements.getTree(e);
716 JavacScope scope = javacTrees.getScope(javacTrees.getPath(e));
717 ClassSymbol enclosingClass = (ClassSymbol) scope.getEnclosingClass();
718 FuncOp op = attr.runWithAttributedMethod(scope.getEnv(), methodTree,
719 attribBlock -> {
720 try {
721 return reflectMethods.getMethodBody(enclosingClass, methodTree, attribBlock, make);
722 } catch (Throwable ex) {
723 // this might happen if the source code contains errors
724 return null;
725 }
726 });
727 return Optional.ofNullable(op);
728 } catch (RuntimeException ex) { // ReflectMethods.UnsupportedASTException
729 // some other error occurred when attempting to attribute the method
730 // @@@ better report of error
731 ex.printStackTrace();
732 return Optional.empty();
733 }
734 }
735 }