1 /*
2 * Copyright (c) 2025, 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.internal;
27
28 import com.sun.source.tree.LambdaExpressionTree;
29 import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
30 import com.sun.tools.javac.code.Kinds.Kind;
31 import com.sun.tools.javac.code.Symbol;
32 import com.sun.tools.javac.code.Symbol.ClassSymbol;
33 import com.sun.tools.javac.code.Symbol.MethodSymbol;
34 import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
35 import com.sun.tools.javac.code.Symbol.VarSymbol;
36 import com.sun.tools.javac.code.Symtab;
37 import com.sun.tools.javac.code.Type;
38 import com.sun.tools.javac.code.Type.ArrayType;
39 import com.sun.tools.javac.code.Type.IntersectionClassType;
40 import com.sun.tools.javac.code.Type.MethodType;
41 import com.sun.tools.javac.code.Type.StructuralTypeMapping;
42 import com.sun.tools.javac.code.Type.TypeVar;
43 import com.sun.tools.javac.code.Type.UnionClassType;
44 import com.sun.tools.javac.code.TypeTag;
45 import com.sun.tools.javac.code.Types;
46 import com.sun.tools.javac.comp.AttrContext;
47 import com.sun.tools.javac.comp.CaptureScanner;
48 import com.sun.tools.javac.comp.DeferredAttr.FilterScanner;
49 import com.sun.tools.javac.comp.Env;
50 import com.sun.tools.javac.comp.Flow;
51 import com.sun.tools.javac.comp.Lower;
52 import com.sun.tools.javac.comp.CodeReflectionTransformer;
53 import com.sun.tools.javac.comp.TypeEnvs;
54 import com.sun.tools.javac.jvm.ByteCodes;
55 import com.sun.tools.javac.jvm.Gen;
56 import com.sun.tools.javac.tree.JCTree;
57 import com.sun.tools.javac.tree.JCTree.JCAnnotation;
58 import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
59 import com.sun.tools.javac.tree.JCTree.JCAssign;
60 import com.sun.tools.javac.tree.JCTree.JCBinary;
61 import com.sun.tools.javac.tree.JCTree.JCBlock;
62 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
63 import com.sun.tools.javac.tree.JCTree.JCExpression;
64 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
65 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression;
66 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression.CodeReflectionInfo;
67 import com.sun.tools.javac.tree.JCTree.JCIdent;
68 import com.sun.tools.javac.tree.JCTree.JCLambda;
69 import com.sun.tools.javac.tree.JCTree.JCLiteral;
70 import com.sun.tools.javac.tree.JCTree.JCMemberReference;
71 import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
72 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
73 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
74 import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
75 import com.sun.tools.javac.tree.JCTree.JCNewArray;
76 import com.sun.tools.javac.tree.JCTree.JCNewClass;
77 import com.sun.tools.javac.tree.JCTree.JCReturn;
78 import com.sun.tools.javac.tree.JCTree.JCTypeCast;
79 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
80 import com.sun.tools.javac.tree.JCTree.JCAssert;
81 import com.sun.tools.javac.tree.JCTree.Tag;
82 import com.sun.tools.javac.tree.TreeInfo;
83 import com.sun.tools.javac.tree.TreeMaker;
84 import com.sun.tools.javac.util.Assert;
85 import com.sun.tools.javac.util.Context;
86 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
87 import com.sun.tools.javac.util.ListBuffer;
88 import com.sun.tools.javac.util.Log;
89 import com.sun.tools.javac.util.Name;
90 import com.sun.tools.javac.util.Names;
91 import com.sun.tools.javac.util.Options;
92 import jdk.incubator.code.*;
93 import jdk.incubator.code.extern.DialectFactory;
94 import jdk.incubator.code.dialect.core.*;
95 import jdk.incubator.code.dialect.java.*;
96 import jdk.incubator.code.dialect.java.WildcardType.BoundKind;
97
98 import javax.lang.model.element.Modifier;
99 import javax.tools.JavaFileObject;
100 import java.lang.constant.ClassDesc;
101 import java.util.*;
102 import java.util.List;
103 import java.util.function.Function;
104 import java.util.function.Supplier;
105
106 import static com.sun.tools.javac.code.Flags.*;
107 import static com.sun.tools.javac.code.Kinds.Kind.MTH;
108 import static com.sun.tools.javac.code.Kinds.Kind.TYP;
109 import static com.sun.tools.javac.code.Kinds.Kind.VAR;
110 import static com.sun.tools.javac.code.TypeTag.BOT;
111 import static com.sun.tools.javac.code.TypeTag.CLASS;
112 import static com.sun.tools.javac.code.TypeTag.METHOD;
113 import static com.sun.tools.javac.code.TypeTag.NONE;
114 import static com.sun.tools.javac.main.Option.G_CUSTOM;
115
116 import java.io.IOException;
117 import java.io.OutputStream;
118 import java.lang.classfile.ClassFile;
119 import java.lang.classfile.ClassTransform;
120 import java.lang.classfile.attribute.InnerClassInfo;
121 import java.lang.classfile.attribute.InnerClassesAttribute;
122 import java.lang.classfile.attribute.NestHostAttribute;
123 import java.lang.invoke.MethodHandles;
124 import javax.tools.JavaFileManager;
125 import javax.tools.StandardLocation;
126 import jdk.incubator.code.bytecode.BytecodeGenerator;
127
128 import static com.sun.tools.javac.resources.CompilerProperties.Notes.*;
129 import static com.sun.tools.javac.resources.CompilerProperties.Warnings.*;
130
131 /**
132 * This a tree translator that adds the code model to all method declaration marked
133 * with the {@code Reflect} annotation. The model is expressed using the code
134 * reflection API (see {@code jdk.incubator.code}).
135 */
136 public class ReflectMethods extends TreeTranslatorPrev {
137 protected static final Context.Key<ReflectMethods> reflectMethodsKey = new Context.Key<>();
138
139 public static ReflectMethods instance(Context context) {
140 ReflectMethods instance = context.get(reflectMethodsKey);
141 if (instance == null)
142 instance = new ReflectMethods(context);
143 return instance;
144 }
145
146 private final Types types;
147 private final Names names;
148 private final Symtab syms;
149 private final Gen gen;
150 private final Log log;
151 private final Lower lower;
152 private final TypeEnvs typeEnvs;
153 private final Flow flow;
154 private final CodeReflectionSymbols crSyms;
155 private final boolean dumpIR;
156 private final boolean lineDebugInfo;
157 private final boolean reflectAll;
158
159 private TreeMaker make;
160 private ListBuffer<JCTree> opMethodDecls;
161 private SequencedMap<String, Op> ops;
162 private Symbol.ClassSymbol currentClassSym;
163 private Symbol.ClassSymbol codeModelsClassSym;
164 private int lambdaCount;
165 private boolean codeReflectionEnabled = false;
166 private final Map<Symbol, List<Symbol>> localCaptures = new HashMap<>();
167
168 @SuppressWarnings("this-escape")
169 protected ReflectMethods(Context context) {
170 context.put(reflectMethodsKey, this);
171 Options options = Options.instance(context);
172 dumpIR = options.isSet("dumpIR");
173 lineDebugInfo =
174 options.isUnset(G_CUSTOM) ||
175 options.isSet(G_CUSTOM, "lines");
176 reflectAll = options.isSet("reflectAll");
177 names = Names.instance(context);
178 syms = Symtab.instance(context);
179 types = Types.instance(context);
180 gen = Gen.instance(context);
181 log = Log.instance(context);
182 lower = Lower.instance(context);
183 typeEnvs = TypeEnvs.instance(context);
184 flow = Flow.instance(context);
185 crSyms = new CodeReflectionSymbols(context);
186 }
187
188 @Override
189 public void visitVarDef(JCVariableDecl tree) {
190 boolean prevCodeReflectionEnabled = codeReflectionEnabled;
191 try {
192 codeReflectionEnabled = codeReflectionEnabled ||
193 tree.sym.attribute(crSyms.codeReflectionType.tsym) != null;
194 super.visitVarDef(tree);
195 } finally {
196 codeReflectionEnabled = prevCodeReflectionEnabled;
197 }
198 }
199
200 @Override
201 public void visitMethodDef(JCMethodDecl tree) {
202 boolean isReflectable = isReflectable(tree);
203 if (isReflectable) {
204 if (currentClassSym.type.getEnclosingType().hasTag(CLASS) || currentClassSym.isDirectlyOrIndirectlyLocal()) {
205 // Reflectable methods in local classes are not supported
206 log.warning(tree, ReflectableMethodInnerClass(currentClassSym.enclClass()));
207 super.visitMethodDef(tree);
208 return;
209 } else {
210 // if the method is annotated, scan it
211 BodyScanner bodyScanner = new BodyScanner(tree);
212 CoreOp.FuncOp funcOp;
213 try {
214 funcOp = bodyScanner.scanMethod();
215 } catch (Exception e) {
216 if (reflectAll) {
217 // log as warning for debugging purposses when reflectAll enabled
218 log.warning(tree, ReflectableMethodUnsupported(currentClassSym.enclClass(), e.toString()));
219 super.visitMethodDef(tree);
220 return;
221 }
222 throw e;
223 }
224 if (dumpIR) {
225 // dump the method IR if requested
226 log.note(ReflectableMethodIrDump(tree.sym.enclClass(), tree.sym, funcOp.toText()));
227 }
228 // create a static method that returns the op
229 Name methodName = methodName(symbolToMethodRef(tree.sym));
230 opMethodDecls.add(opMethodDecl(methodName));
231 ops.put(methodName.toString(), funcOp);
232 }
233 }
234 boolean prevCodeReflectionEnabled = codeReflectionEnabled;
235 try {
236 codeReflectionEnabled = isReflectable;
237 super.visitMethodDef(tree);
238 } finally {
239 codeReflectionEnabled = prevCodeReflectionEnabled;
240 }
241 }
242
243 @Override
244 public void visitModuleDef(JCModuleDecl that) {
245 // do nothing
246 }
247
248 @Override
249 public void visitClassDef(JCClassDecl tree) {
250 ListBuffer<JCTree> prevOpMethodDecls = opMethodDecls;
251 SequencedMap<String, Op> prevOps = ops;
252 Symbol.ClassSymbol prevClassSym = currentClassSym;
253 Symbol.ClassSymbol prevCodeModelsClassSym = codeModelsClassSym;
254 int prevLambdaCount = lambdaCount;
255 JavaFileObject prev = log.useSource(tree.sym.sourcefile);
256 computeCapturesIfNeeded(tree);
257 try {
258 lambdaCount = 0;
259 currentClassSym = tree.sym;
260 opMethodDecls = new ListBuffer<>();
261 codeModelsClassSym = new ClassSymbol(0, names.fromString("$CM"), currentClassSym);
262 ops = new LinkedHashMap<>();
263 super.visitClassDef(tree);
264 if (!ops.isEmpty()) {
265 tree.defs = tree.defs.prependList(opMethodDecls.toList());
266 tree = new JCReflectMethodsClassDecl(tree, ops);
267 currentClassSym.members().enter(codeModelsClassSym);
268 }
269 } finally {
270 lambdaCount = prevLambdaCount;
271 opMethodDecls = prevOpMethodDecls;
272 ops = prevOps;
273 currentClassSym = prevClassSym;
274 codeModelsClassSym = prevCodeModelsClassSym;
275 result = tree;
276 log.useSource(prev);
277 }
278 }
279
280 void computeCapturesIfNeeded(JCClassDecl tree) {
281 if (tree.sym.isDirectlyOrIndirectlyLocal() && !localCaptures.containsKey(tree.sym)) {
282 // we need to keep track of captured locals using same strategy as Lower
283 class FreeVarScanner extends Lower.FreeVarCollector {
284 FreeVarScanner() {
285 lower.super(tree);
286 }
287
288 @Override
289 protected void addFreeVars(ClassSymbol c) {
290 localCaptures.getOrDefault(c, List.of())
291 .forEach(s -> addFreeVar((VarSymbol)s));
292 }
293 }
294 FreeVarScanner fvs = new FreeVarScanner();
295 localCaptures.put(tree.sym, List.copyOf(fvs.analyzeCaptures()));
296 }
297 }
298
299 @Override
300 public void visitLambda(JCLambda tree) {
301 boolean isReflectable = isReflectable(tree);
302 if (isReflectable) {
303 if (currentClassSym.type.getEnclosingType().hasTag(CLASS) || currentClassSym.isDirectlyOrIndirectlyLocal()) {
304 // Reflectable lambdas in local classes are not supported
305 log.warning(tree, ReflectableLambdaInnerClass(currentClassSym.enclClass()));
306 super.visitLambda(tree);
307 return;
308 }
309
310 // quoted lambda - scan it
311 BodyScanner bodyScanner = new BodyScanner(tree);
312 CoreOp.FuncOp funcOp;
313 try {
314 funcOp = bodyScanner.scanLambda();
315 } catch (Exception e) {
316 if (reflectAll) {
317 log.warning(tree, ReflectableLambdaUnsupported(currentClassSym.enclClass(), e.toString()));
318 super.visitLambda(tree);
319 return;
320 }
321 throw e;
322 }
323 if (dumpIR) {
324 // dump the method IR if requested
325 log.note(ReflectableLambdaIrDump(funcOp.toText()));
326 }
327 // create a static method that returns the FuncOp representing the lambda
328 Name lambdaName = lambdaName();
329 JCMethodDecl opMethod = opMethodDecl(lambdaName);
330 opMethodDecls.add(opMethod);
331 ops.put(lambdaName.toString(), funcOp);
332
333 // leave the lambda in place, but also leave a trail for LambdaToMethod
334 tree.codeReflectionInfo = new CodeReflectionInfo(opMethod.sym, crSyms.reflectableLambdaMetafactory);
335 }
336 boolean prevCodeReflectionEnabled = codeReflectionEnabled;
337 try {
338 codeReflectionEnabled = isReflectable;
339 super.visitLambda(tree);
340 } finally {
341 codeReflectionEnabled = prevCodeReflectionEnabled;
342 }
343 }
344
345 @Override
346 public void visitReference(JCMemberReference tree) {
347 MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
348 JCLambda lambdaTree = memberReferenceToLambda.lambda();
349
350 if (isReflectable(tree)) {
351 if (currentClassSym.type.getEnclosingType().hasTag(CLASS)) {
352 // Reflectable method references in local classes are not supported
353 log.warning(tree, ReflectableMrefInnerClass(currentClassSym.enclClass()));
354 super.visitReference(tree);
355 return;
356 }
357
358 // quoted lambda - scan it
359 BodyScanner bodyScanner = new BodyScanner(lambdaTree);
360 CoreOp.FuncOp funcOp = bodyScanner.scanLambda();
361 if (dumpIR) {
362 // dump the method IR if requested
363 log.note(ReflectableMrefIrDump(funcOp.toText()));
364 }
365 // create a method that returns the FuncOp representing the lambda
366 Name lambdaName = lambdaName();
367 ops.put(lambdaName.toString(), funcOp);
368 JCMethodDecl opMethod = opMethodDecl(lambdaName);
369 opMethodDecls.add(opMethod);
370 tree.codeReflectionInfo = new CodeReflectionInfo(opMethod.sym, crSyms.reflectableLambdaMetafactory);
371 }
372 super.visitReference(tree);
373 }
374
375 Name lambdaName() {
376 return names.fromString("lambda").append('$', names.fromString(String.valueOf(lambdaCount++)));
377 }
378
379 Name methodName(MethodRef method) {
380 char[] sigCh = method.toString().toCharArray();
381 for (int i = 0; i < sigCh.length; i++) {
382 switch (sigCh[i]) {
383 case '.', ';', '[', '/' -> sigCh[i] = '$';
384 }
385 }
386 return names.fromChars(sigCh, 0, sigCh.length);
387 }
388
389 // @@@ Retain enum for when we might add another storage to test
390 // and compare
391 private enum CodeModelStorageOption {
392 CODE_BUILDER;
393
394 public static CodeModelStorageOption parse(String s) {
395 if (s == null) {
396 return CodeModelStorageOption.CODE_BUILDER;
397 }
398 return CodeModelStorageOption.valueOf(s);
399 }
400 }
401
402 private JCMethodDecl opMethodDecl(Name methodName) {
403 var mt = new MethodType(com.sun.tools.javac.util.List.nil(), crSyms.opType,
404 com.sun.tools.javac.util.List.nil(), syms.methodClass);
405 var ms = new MethodSymbol(PRIVATE | STATIC | SYNTHETIC, methodName, mt, currentClassSym);
406 currentClassSym.members().enter(ms);
407
408 // Create the method body calling the synthetic inner class method of the same name
409 var body = make.Return(make.App(make.Ident(new MethodSymbol(PRIVATE | STATIC | SYNTHETIC, methodName, mt, codeModelsClassSym))));
410 var md = make.MethodDef(ms, make.Block(0, com.sun.tools.javac.util.List.of(body)));
411 return md;
412 }
413
414 public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) {
415 // note that this method does NOT support recursion.
416 this.make = make;
417 return translate(cdef);
418 }
419
420 public CoreOp.FuncOp getMethodBody(Symbol.ClassSymbol classSym, JCMethodDecl methodDecl, JCBlock attributedBody, TreeMaker make) {
421 // if the method is annotated, scan it
422 // Called from JavacElements::getBody
423 try {
424 this.make = make;
425 currentClassSym = classSym;
426 BodyScanner bodyScanner = new BodyScanner(methodDecl);
427 return bodyScanner.scanMethod(attributedBody);
428 } finally {
429 currentClassSym = null;
430 this.make = null;
431 }
432 }
433
434 static class BodyStack {
435 final BodyStack parent;
436
437 // Tree associated with body
438 final JCTree tree;
439
440 // Body to add blocks
441 final Body.Builder body;
442 // Current block to add operations
443 Block.Builder block;
444
445 // Map of symbols (method arguments and local variables) to varOp values
446 final Map<Symbol, Value> localToOp;
447
448 // Label
449 Map.Entry<String, Op.Result> label;
450
451 BodyStack(BodyStack parent, JCTree tree, FunctionType bodyType) {
452 this.parent = parent;
453
454 this.tree = tree;
455
456 this.body = Body.Builder.of(parent != null ? parent.body : null, bodyType);
457 this.block = body.entryBlock();
458
459 this.localToOp = new LinkedHashMap<>(); // order is important for captured values
460 }
461
462 public void setLabel(String labelName, Op.Result labelValue) {
463 if (label != null) {
464 throw new IllegalStateException("Label already defined: " + labelName);
465 }
466 label = Map.entry(labelName, labelValue);
467 }
468 }
469
470 class BodyScanner extends TreeScannerPrev {
471 private final JCTree tree;
472 private final Name name;
473 private final BodyStack top;
474 private BodyStack stack;
475 private Op lastOp;
476 private Value result;
477 private Type pt = Type.noType;
478 private final boolean isLambdaReflectable;
479 private Type bodyTarget;
480
481 BodyScanner(JCMethodDecl tree) {
482 this.tree = tree;
483 this.name = tree.name;
484 this.isLambdaReflectable = false;
485
486 List<TypeElement> parameters = new ArrayList<>();
487 int blockArgOffset = 0;
488 // Instance methods model "this" as an additional argument occurring
489 // before all other arguments.
490 // @@@ Inner classes.
491 // We need to capture all "this", in nested order, as arguments.
492 if (!tree.getModifiers().getFlags().contains(Modifier.STATIC)) {
493 parameters.add(typeToTypeElement(tree.sym.owner.type));
494 blockArgOffset++;
495 }
496 tree.sym.type.getParameterTypes().stream().map(ReflectMethods.this::typeToTypeElement).forEach(parameters::add);
497
498 FunctionType bodyType = CoreType.functionType(
499 typeToTypeElement(tree.sym.type.getReturnType()), parameters);
500
501 this.stack = this.top = new BodyStack(null, tree.body, bodyType);
502
503 // @@@ this as local variable? (it can never be stored to)
504 for (int i = 0 ; i < tree.params.size() ; i++) {
505 Op.Result paramOp = append(CoreOp.var(
506 tree.params.get(i).name.toString(),
507 top.block.parameters().get(blockArgOffset + i)));
508 top.localToOp.put(tree.params.get(i).sym, paramOp);
509 }
510
511 bodyTarget = tree.sym.type.getReturnType();
512 }
513
514 BodyScanner(JCLambda tree) {
515 this.tree = tree;
516 this.name = names.fromString("quotedLambda");
517 this.isLambdaReflectable = true;
518
519 ReflectableLambdaCaptureScanner lambdaCaptureScanner =
520 new ReflectableLambdaCaptureScanner(tree);
521
522 List<VarSymbol> capturedSymbols = lambdaCaptureScanner.analyzeCaptures();
523 int blockParamOffset = 0;
524
525 ListBuffer<Type> capturedTypes = new ListBuffer<>();
526 if (lambdaCaptureScanner.capturesThis) {
527 capturedTypes.add(currentClassSym.type);
528 blockParamOffset++;
529 }
530 for (Symbol s : capturedSymbols) {
531 capturedTypes.add(s.type);
532 }
533
534 FunctionType mtDesc = CoreType.functionType(CoreOp.QuotedOp.QUOTED_OP_TYPE,
535 capturedTypes.toList().map(ReflectMethods.this::typeToTypeElement));
536
537 this.stack = this.top = new BodyStack(null, tree.body, mtDesc);
538
539 // add captured variables mappings
540 for (int i = 0 ; i < capturedSymbols.size() ; i++) {
541 Symbol capturedSymbol = capturedSymbols.get(i);
542 var capturedArg = top.block.parameters().get(blockParamOffset + i);
543 top.localToOp.put(capturedSymbol,
544 append(CoreOp.var(capturedSymbol.name.toString(), capturedArg)));
545 }
546
547 // add captured constant mappings
548 for (Map.Entry<Symbol, Object> constantCapture : lambdaCaptureScanner.constantCaptures.entrySet()) {
549 Symbol capturedSymbol = constantCapture.getKey();
550 var capturedArg = append(CoreOp.constant(typeToTypeElement(capturedSymbol.type),
551 constantCapture.getValue()));
552 top.localToOp.put(capturedSymbol,
553 append(CoreOp.var(capturedSymbol.name.toString(), capturedArg)));
554 }
555
556 bodyTarget = tree.target.getReturnType();
557 }
558
559 /**
560 * Compute the set of local variables captured by a reflectable lambda expression.
561 * Inspired from LambdaToMethod's LambdaCaptureScanner.
562 */
563 class ReflectableLambdaCaptureScanner extends CaptureScanner {
564 boolean capturesThis;
565 Set<ClassSymbol> seenClasses = new HashSet<>();
566 Map<Symbol, Object> constantCaptures = new HashMap<>();
567
568 ReflectableLambdaCaptureScanner(JCLambda ownerTree) {
569 super(ownerTree);
570 }
571
572 @Override
573 public void visitClassDef(JCClassDecl tree) {
574 computeCapturesIfNeeded(tree);
575 seenClasses.add(tree.sym);
576 super.visitClassDef(tree);
577 }
578
579 @Override
580 public void visitIdent(JCIdent tree) {
581 if (!tree.sym.isStatic() &&
582 tree.sym.owner.kind == TYP &&
583 (tree.sym.kind == VAR || tree.sym.kind == MTH) &&
584 !seenClasses.contains(tree.sym.owner)) {
585 // a reference to an enclosing field or method, we need to capture 'this'
586 capturesThis = true;
587 } else if (tree.sym.kind == VAR && ((VarSymbol)tree.sym).getConstValue() != null) {
588 // record the constant value associated with this
589 constantCaptures.put(tree.sym, ((VarSymbol)tree.sym).getConstValue());
590 } else {
591 // might be a local capture
592 super.visitIdent(tree);
593 }
594 }
595
596 @Override
597 public void visitSelect(JCFieldAccess tree) {
598 if (tree.sym.kind == VAR &&
599 (tree.sym.name == names._this ||
600 tree.sym.name == names._super) &&
601 !seenClasses.contains(tree.sym.type.tsym)) {
602 capturesThis = true;
603 }
604 super.visitSelect(tree);
605 }
606
607 @Override
608 public void visitNewClass(JCNewClass tree) {
609 if (tree.type.tsym.isDirectlyOrIndirectlyLocal()) {
610 for (Symbol c : localCaptures.get(tree.type.tsym)) {
611 addFreeVar((VarSymbol) c);
612 }
613 }
614 if (tree.encl == null && tree.type.tsym.hasOuterInstance()) {
615 capturesThis = true;
616 }
617 super.visitNewClass(tree);
618 }
619
620 @Override
621 public void visitAnnotation(JCAnnotation tree) {
622 // do nothing (annotation values look like captured instance fields)
623 }
624 }
625
626 void pushBody(JCTree tree, FunctionType bodyType) {
627 stack = new BodyStack(stack, tree, bodyType);
628 lastOp = null; // reset
629 }
630
631 void popBody() {
632 stack = stack.parent;
633 }
634
635 Value varOpValue(Symbol sym) {
636 BodyStack s = stack;
637 while (s != null) {
638 Value v = s.localToOp.get(sym);
639 if (v != null) {
640 return v;
641 }
642 s = s.parent;
643 }
644 throw new NoSuchElementException(sym.toString());
645 }
646
647 Value thisValue() { // @@@: outer this?
648 return top.block.parameters().get(0);
649 }
650
651 Value getLabel(String labelName) {
652 BodyStack s = stack;
653 while (s != null) {
654 if (s.label != null && s.label.getKey().equals(labelName)) {
655 return s.label.getValue();
656 }
657 s = s.parent;
658 }
659 throw new NoSuchElementException(labelName);
660 }
661
662 private DiagnosticPosition pos() {
663 JCTree current = currentNode();
664 return current != null ? current : tree;
665 }
666
667 private Op.Result append(Op op) {
668 return append(op, generateLocation(pos(), false), stack);
669 }
670
671 private Op.Result append(Op op, Location l) {
672 return append(op, l, stack);
673 }
674
675 private Op.Result append(Op op, Location l, BodyStack stack) {
676 lastOp = op;
677 op.setLocation(l);
678 return stack.block.op(op);
679 }
680
681 Location generateLocation(DiagnosticPosition pos, boolean includeSourceReference) {
682 if (!lineDebugInfo) {
683 return Location.NO_LOCATION;
684 }
685
686 int startPos = pos.getStartPosition();
687 int line = log.currentSource().getLineNumber(startPos);
688 int col = log.currentSource().getColumnNumber(startPos, false);
689 String path;
690 if (includeSourceReference) {
691 path = log.currentSource().getFile().toUri().toString();
692 } else {
693 path = null;
694 }
695 return new Location(path, line, col);
696 }
697
698 private void appendReturnOrUnreachable(JCTree body) {
699 // Append only if an existing terminating operation is not present
700 if (lastOp == null || !(lastOp instanceof Op.Terminating)) {
701 // If control can continue after the body append return.
702 // Otherwise, append unreachable.
703 if (isAliveAfter(body)) {
704 append(CoreOp.return_());
705 } else {
706 append(CoreOp.unreachable());
707 }
708 }
709 }
710
711 private boolean isAliveAfter(JCTree node) {
712 return flow.aliveAfter(typeEnvs.get(currentClassSym), node, make);
713 }
714
715 private <O extends Op & Op.Terminating> void appendTerminating(Supplier<O> sop) {
716 // Append only if an existing terminating operation is not present
717 if (lastOp == null || !(lastOp instanceof Op.Terminating)) {
718 append(sop.get());
719 }
720 }
721
722 public Value toValue(JCExpression expression, Type targetType) {
723 result = null; // reset
724 Type prevPt = pt;
725 try {
726 pt = targetType;
727 scan(expression);
728 return (result == null || targetType.hasTag(TypeTag.VOID) || targetType.hasTag(NONE)) ?
729 result : coerce(result, expression.type, targetType);
730 } finally {
731 pt = prevPt;
732 }
733 }
734
735 public Value toValue(JCExpression expression) {
736 return toValue(expression, Type.noType);
737 }
738
739 public Value toValue(JCTree.JCStatement statement) {
740 result = null; // reset
741 scan(statement);
742 return result;
743 }
744
745 Value coerce(Value sourceValue, Type sourceType, Type targetType) {
746 if (sourceType.isReference() && targetType.isReference() &&
747 !types.isSubtype(types.erasure(sourceType), types.erasure(targetType))) {
748 return append(JavaOp.cast(typeToTypeElement(targetType), sourceValue));
749 }
750 return convert(sourceValue, targetType);
751 }
752
753 Value boxIfNeeded(Value exprVal) {
754 Type source = typeElementToType(exprVal.type());
755 return source.hasTag(NONE) ?
756 exprVal : convert(exprVal, types.boxedTypeOrType(source));
757 }
758
759 Value unboxIfNeeded(Value exprVal) {
760 Type source = typeElementToType(exprVal.type());
761 return source.hasTag(NONE) ?
762 exprVal : convert(exprVal, types.unboxedTypeOrType(source));
763 }
764
765 Value convert(Value exprVal, Type target) {
766 Type source = typeElementToType(exprVal.type());
767 boolean sourcePrimitive = source.isPrimitive();
768 boolean targetPrimitive = target.isPrimitive();
769 if (target.hasTag(NONE)) {
770 return exprVal;
771 } else if (sourcePrimitive == targetPrimitive) {
772 if (!sourcePrimitive || types.isSameType(source, target)) {
773 return exprVal;
774 } else {
775 // implicit primitive conversion
776 return append(JavaOp.conv(typeToTypeElement(target), exprVal));
777 }
778 } else if (sourcePrimitive) {
779 // we need to box
780 Type unboxedTarget = types.unboxedType(target);
781 if (!unboxedTarget.hasTag(NONE)) {
782 // non-Object target
783 if (!types.isConvertible(source, unboxedTarget)) {
784 exprVal = convert(exprVal, unboxedTarget);
785 }
786 return box(exprVal, target);
787 } else {
788 // Object target
789 return box(exprVal, types.boxedClass(source).type);
790 }
791 } else {
792 // we need to unbox
793 return unbox(exprVal, source, target, types.unboxedType(source));
794 }
795 }
796
797 Value box(Value valueExpr, Type box) {
798 // Boxing is a static method e.g., java.lang.Integer::valueOf(int)java.lang.Integer
799 MethodRef boxMethod = MethodRef.method(typeToTypeElement(box), names.valueOf.toString(),
800 CoreType.functionType(typeToTypeElement(box), typeToTypeElement(types.unboxedType(box))));
801 return append(JavaOp.invoke(boxMethod, valueExpr));
802 }
803
804 Value unbox(Value valueExpr, Type box, Type primitive, Type unboxedType) {
805 if (unboxedType.hasTag(NONE)) {
806 // Object target, first downcast to correct wrapper type
807 unboxedType = primitive;
808 box = types.boxedClass(unboxedType).type;
809 valueExpr = append(JavaOp.cast(typeToTypeElement(box), valueExpr));
810 }
811 // Unboxing is a virtual method e.g., java.lang.Integer::intValue()int
812 MethodRef unboxMethod = MethodRef.method(typeToTypeElement(box),
813 unboxedType.tsym.name.append(names.Value).toString(),
814 CoreType.functionType(typeToTypeElement(unboxedType)));
815 return append(JavaOp.invoke(unboxMethod, valueExpr));
816 }
817
818 @Override
819 public void visitVarDef(JCVariableDecl tree) {
820 JavaType javaType = typeToTypeElement(tree.type);
821 if (tree.init != null) {
822 Value initOp = toValue(tree.init, tree.type);
823 result = append(CoreOp.var(tree.name.toString(), javaType, initOp));
824 } else {
825 // Uninitialized
826 result = append(CoreOp.var(tree.name.toString(), javaType));
827 }
828 stack.localToOp.put(tree.sym, result);
829 }
830
831 @Override
832 public void visitAssign(JCAssign tree) {
833 // Consume top node that applies to write access
834 JCTree lhs = TreeInfo.skipParens(tree.lhs);
835 Type target = tree.lhs.type;
836 switch (lhs.getTag()) {
837 case IDENT: {
838 JCIdent assign = (JCIdent) lhs;
839
840 // Scan the rhs, the assign expression result is its input
841 result = toValue(tree.rhs, target);
842
843 Symbol sym = assign.sym;
844 switch (sym.getKind()) {
845 case LOCAL_VARIABLE, PARAMETER -> {
846 Value varOp = varOpValue(sym);
847 append(CoreOp.varStore(varOp, result));
848 }
849 case FIELD -> {
850 FieldRef fd = symbolToFieldRef(sym, symbolSiteType(sym));
851 if (sym.isStatic()) {
852 append(JavaOp.fieldStore(fd, result));
853 } else {
854 append(JavaOp.fieldStore(fd, thisValue(), result));
855 }
856 }
857 default -> {
858 // @@@ Cannot reach here?
859 throw unsupported(tree);
860 }
861 }
862 break;
863 }
864 case SELECT: {
865 JCFieldAccess assign = (JCFieldAccess) lhs;
866
867 Value receiver = toValue(assign.selected);
868
869 // Scan the rhs, the assign expression result is its input
870 result = toValue(tree.rhs, target);
871
872 Symbol sym = assign.sym;
873 FieldRef fr = symbolToFieldRef(sym, assign.selected.type);
874 if (sym.isStatic()) {
875 append(JavaOp.fieldStore(fr, result));
876 } else {
877 append(JavaOp.fieldStore(fr, receiver, result));
878 }
879 break;
880 }
881 case INDEXED: {
882 JCArrayAccess assign = (JCArrayAccess) lhs;
883
884 Value array = toValue(assign.indexed);
885 Value index = toValue(assign.index);
886
887 // Scan the rhs, the assign expression result is its input
888 result = toValue(tree.rhs, target);
889
890 append(JavaOp.arrayStoreOp(array, index, result));
891 break;
892 }
893 default:
894 throw unsupported(tree);
895 }
896 }
897
898 @Override
899 public void visitAssignop(JCTree.JCAssignOp tree) {
900 // Capture applying rhs and operation
901 Function<Value, Value> scanRhs = (lhs) -> {
902 Type unboxedType = types.unboxedTypeOrType(tree.type);
903 Value rhs;
904 if (tree.operator.opcode == ByteCodes.string_add && tree.rhs.type.isPrimitive()) {
905 rhs = toValue(tree.rhs);
906 } else {
907 rhs = toValue(tree.rhs, unboxedType);
908 }
909 lhs = unboxIfNeeded(lhs);
910
911 Value assignOpResult = switch (tree.getTag()) {
912
913 // Arithmetic operations
914 case PLUS_ASG -> {
915 if (tree.operator.opcode == ByteCodes.string_add) {
916 yield append(JavaOp.concat(lhs, rhs));
917 } else {
918 yield append(JavaOp.add(lhs, rhs));
919 }
920 }
921 case MINUS_ASG -> append(JavaOp.sub(lhs, rhs));
922 case MUL_ASG -> append(JavaOp.mul(lhs, rhs));
923 case DIV_ASG -> append(JavaOp.div(lhs, rhs));
924 case MOD_ASG -> append(JavaOp.mod(lhs, rhs));
925
926 // Bitwise operations (including their boolean variants)
927 case BITOR_ASG -> append(JavaOp.or(lhs, rhs));
928 case BITAND_ASG -> append(JavaOp.and(lhs, rhs));
929 case BITXOR_ASG -> append(JavaOp.xor(lhs, rhs));
930
931 // Shift operations
932 case SL_ASG -> append(JavaOp.lshl(lhs, rhs));
933 case SR_ASG -> append(JavaOp.ashr(lhs, rhs));
934 case USR_ASG -> append(JavaOp.lshr(lhs, rhs));
935
936
937 default -> throw unsupported(tree);
938 };
939 return result = convert(assignOpResult, tree.type);
940 };
941
942 applyCompoundAssign(tree.lhs, scanRhs);
943 }
944
945 void applyCompoundAssign(JCTree.JCExpression lhs, Function<Value, Value> scanRhs) {
946 // Consume top node that applies to access
947 lhs = TreeInfo.skipParens(lhs);
948 switch (lhs.getTag()) {
949 case IDENT -> {
950 JCIdent assign = (JCIdent) lhs;
951
952 Symbol sym = assign.sym;
953 switch (sym.getKind()) {
954 case LOCAL_VARIABLE, PARAMETER -> {
955 Value varOp = varOpValue(sym);
956
957 Op.Result lhsOpValue = append(CoreOp.varLoad(varOp));
958 // Scan the rhs
959 Value r = scanRhs.apply(lhsOpValue);
960
961 append(CoreOp.varStore(varOp, r));
962 }
963 case FIELD -> {
964 FieldRef fr = symbolToFieldRef(sym, symbolSiteType(sym));
965
966 Op.Result lhsOpValue;
967 TypeElement resultType = typeToTypeElement(sym.type);
968 if (sym.isStatic()) {
969 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr));
970 } else {
971 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr, thisValue()));
972 }
973 // Scan the rhs
974 Value r = scanRhs.apply(lhsOpValue);
975
976 if (sym.isStatic()) {
977 append(JavaOp.fieldStore(fr, r));
978 } else {
979 append(JavaOp.fieldStore(fr, thisValue(), r));
980 }
981 }
982 default -> {
983 // @@@ Cannot reach here?
984 throw unsupported(lhs);
985 }
986 }
987 }
988 case SELECT -> {
989 JCFieldAccess assign = (JCFieldAccess) lhs;
990
991 Value receiver = toValue(assign.selected);
992
993 Symbol sym = assign.sym;
994 FieldRef fr = symbolToFieldRef(sym, assign.selected.type);
995
996 Op.Result lhsOpValue;
997 TypeElement resultType = typeToTypeElement(sym.type);
998 if (sym.isStatic()) {
999 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr));
1000 } else {
1001 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr, receiver));
1002 }
1003 // Scan the rhs
1004 Value r = scanRhs.apply(lhsOpValue);
1005
1006 if (sym.isStatic()) {
1007 append(JavaOp.fieldStore(fr, r));
1008 } else {
1009 append(JavaOp.fieldStore(fr, receiver, r));
1010 }
1011 }
1012 case INDEXED -> {
1013 JCArrayAccess assign = (JCArrayAccess) lhs;
1014
1015 Value array = toValue(assign.indexed);
1016 Value index = toValue(assign.index);
1017
1018 Op.Result lhsOpValue = append(JavaOp.arrayLoadOp(array, index));
1019 // Scan the rhs
1020 Value r = scanRhs.apply(lhsOpValue);
1021
1022 append(JavaOp.arrayStoreOp(array, index, r));
1023 }
1024 default -> throw unsupported(lhs);
1025 }
1026 }
1027
1028 @Override
1029 public void visitIdent(JCIdent tree) {
1030 // Visited only for read access
1031
1032 Symbol sym = tree.sym;
1033 switch (sym.getKind()) {
1034 case LOCAL_VARIABLE, RESOURCE_VARIABLE, BINDING_VARIABLE, PARAMETER, EXCEPTION_PARAMETER ->
1035 result = loadVar(sym);
1036 case FIELD, ENUM_CONSTANT -> {
1037 if (sym.name.equals(names._this) || sym.name.equals(names._super)) {
1038 result = thisValue();
1039 } else if (top.localToOp.containsKey(sym)) {
1040 // if field symbol is a key in top.localToOp
1041 // we expect that we're producing the model of a lambda
1042 // we also expect that the field is a constant capture and sym was mapped to VarOp result
1043 Assert.check(isLambdaReflectable);
1044 Assert.check(sym.isStatic());
1045 Assert.check(sym.isFinal());
1046 result = loadVar(sym);
1047 } else {
1048 FieldRef fr = symbolToFieldRef(sym, symbolSiteType(sym));
1049 TypeElement resultType = typeToTypeElement(sym.type);
1050 if (sym.isStatic()) {
1051 result = append(JavaOp.fieldLoad(resultType, fr));
1052 } else {
1053 result = append(JavaOp.fieldLoad(resultType, fr, thisValue()));
1054 }
1055 }
1056 }
1057 case PACKAGE, INTERFACE, CLASS, RECORD, ENUM -> {
1058 result = null;
1059 }
1060 default -> {
1061 // @@@ Cannot reach here?
1062 throw unsupported(tree);
1063 }
1064 }
1065 }
1066
1067 private Value loadVar(Symbol sym) {
1068 Value varOp = varOpValue(sym);
1069 Assert.check(varOp.type() instanceof VarType);
1070 return append(CoreOp.varLoad(varOp));
1071 }
1072
1073 @Override
1074 public void visitTypeIdent(JCTree.JCPrimitiveTypeTree tree) {
1075 result = null;
1076 }
1077
1078 @Override
1079 public void visitTypeArray(JCTree.JCArrayTypeTree tree) {
1080 result = null; // MyType[].class is handled in visitSelect just as MyType.class
1081 }
1082
1083 @Override
1084 public void visitSelect(JCFieldAccess tree) {
1085 // Visited only for read access
1086
1087 Type qualifierTarget = qualifierTarget(tree);
1088 // @@@: might cause redundant load if accessed symbol is static but the qualifier is not a type
1089 Value receiver = toValue(tree.selected);
1090
1091 if (tree.name.equals(names._class)) {
1092 result = append(CoreOp.constant(JavaType.J_L_CLASS, typeToTypeElement(tree.selected.type)));
1093 } else if (types.isArray(tree.selected.type)) {
1094 if (tree.sym.equals(syms.lengthVar)) {
1095 result = append(JavaOp.arrayLength(receiver));
1096 } else {
1097 // Should not reach here
1098 throw unsupported(tree);
1099 }
1100 } else {
1101 Symbol sym = tree.sym;
1102 switch (sym.getKind()) {
1103 case FIELD, ENUM_CONSTANT -> {
1104 if (sym.name.equals(names._this) || sym.name.equals(names._super)) {
1105 result = thisValue();
1106 } else {
1107 FieldRef fr = symbolToFieldRef(sym, qualifierTarget.hasTag(NONE) ?
1108 tree.selected.type : qualifierTarget);
1109 TypeElement resultType = typeToTypeElement(types.memberType(tree.selected.type, sym));
1110 if (sym.isStatic()) {
1111 result = append(JavaOp.fieldLoad(resultType, fr));
1112 } else {
1113 result = append(JavaOp.fieldLoad(resultType, fr, receiver));
1114 }
1115 }
1116 }
1117 case PACKAGE, INTERFACE, CLASS, RECORD, ENUM -> {
1118 result = null;
1119 }
1120 default -> {
1121 // @@@ Cannot reach here?
1122 throw unsupported(tree);
1123 }
1124 }
1125 }
1126 }
1127
1128 @Override
1129 public void visitIndexed(JCArrayAccess tree) {
1130 // Visited only for read access
1131
1132 Value array = toValue(tree.indexed);
1133
1134 Value index = toValue(tree.index, typeElementToType(JavaType.INT));
1135
1136 result = append(JavaOp.arrayLoadOp(array, index));
1137 }
1138
1139 @Override
1140 public void visitApply(JCTree.JCMethodInvocation tree) {
1141 // @@@ Symbol.externalType, for use with inner classes
1142
1143 // @@@ this.xyz(...) calls in a constructor
1144
1145 JCTree meth = TreeInfo.skipParens(tree.meth);
1146 switch (meth.getTag()) {
1147 case IDENT: {
1148 JCIdent access = (JCIdent) meth;
1149
1150 Symbol sym = access.sym;
1151 List<Value> args = new ArrayList<>();
1152 JavaOp.InvokeOp.InvokeKind ik;
1153 if (!sym.isStatic()) {
1154 ik = JavaOp.InvokeOp.InvokeKind.INSTANCE;
1155 args.add(thisValue());
1156 } else {
1157 ik = JavaOp.InvokeOp.InvokeKind.STATIC;
1158 }
1159
1160 args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
1161
1162 MethodRef mr = symbolToMethodRef(sym, symbolSiteType(sym));
1163 Value res = append(JavaOp.invoke(ik, tree.varargsElement != null,
1164 typeToTypeElement(meth.type.getReturnType()), mr, args));
1165 if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1166 result = res;
1167 }
1168 break;
1169 }
1170 case SELECT: {
1171 JCFieldAccess access = (JCFieldAccess) meth;
1172
1173 Type qualifierTarget = qualifierTarget(access);
1174 Value receiver = toValue(access.selected, qualifierTarget);
1175
1176 Symbol sym = access.sym;
1177 List<Value> args = new ArrayList<>();
1178 JavaOp.InvokeOp.InvokeKind ik;
1179 if (!sym.isStatic()) {
1180 args.add(receiver);
1181 // @@@ expr.super(...) for inner class super constructor calls
1182 ik = switch (access.selected) {
1183 case JCIdent i when i.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1184 case JCFieldAccess fa when fa.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1185 default -> JavaOp.InvokeOp.InvokeKind.INSTANCE;
1186 };
1187 } else {
1188 ik = JavaOp.InvokeOp.InvokeKind.STATIC;
1189 }
1190
1191 args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
1192
1193 MethodRef mr = symbolToMethodRef(sym, qualifierTarget.hasTag(NONE) ?
1194 access.selected.type : qualifierTarget);
1195 JavaType returnType = typeToTypeElement(meth.type.getReturnType());
1196 JavaOp.InvokeOp iop = JavaOp.invoke(ik, tree.varargsElement != null,
1197 returnType, mr, args);
1198 Value res = append(iop);
1199 if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1200 result = res;
1201 }
1202 break;
1203 }
1204 default:
1205 unsupported(meth);
1206 }
1207 }
1208
1209 List<Value> scanMethodArguments(List<JCExpression> args, Type methodType, Type varargsElement) {
1210 ListBuffer<Value> argValues = new ListBuffer<>();
1211 com.sun.tools.javac.util.List<Type> targetTypes = methodType.getParameterTypes();
1212 if (varargsElement != null) {
1213 targetTypes = targetTypes.reverse().tail;
1214 for (int i = 0 ; i < args.size() - (methodType.getParameterTypes().size() - 1) ; i++) {
1215 targetTypes = targetTypes.prepend(varargsElement);
1216 }
1217 targetTypes = targetTypes.reverse();
1218 }
1219
1220 for (JCTree.JCExpression arg : args) {
1221 argValues.add(toValue(arg, targetTypes.head));
1222 targetTypes = targetTypes.tail;
1223 }
1224 return argValues.toList();
1225 }
1226
1227 @Override
1228 public void visitReference(JCTree.JCMemberReference tree) {
1229 MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
1230 JCVariableDecl recv = memberReferenceToLambda.receiverVar();
1231 if (recv != null) {
1232 scan(recv);
1233 }
1234 scan(memberReferenceToLambda.lambda());
1235 }
1236
1237 Type qualifierTarget(JCFieldAccess tree) {
1238 Type selectedType = types.skipTypeVars(tree.selected.type, true);
1239 return selectedType.isCompound() ?
1240 tree.sym.owner.type :
1241 Type.noType;
1242 }
1243
1244 @Override
1245 public void visitTypeCast(JCTree.JCTypeCast tree) {
1246 Value v = toValue(tree.expr);
1247
1248 Type expressionType = tree.expr.type;
1249 Type type = tree.type;
1250 if (expressionType.isPrimitive() && type.isPrimitive()) {
1251 if (expressionType.equals(type)) {
1252 // Redundant cast
1253 result = v;
1254 } else {
1255 result = append(JavaOp.conv(typeToTypeElement(type), v));
1256 }
1257 } else if (expressionType.isPrimitive() || type.isPrimitive()) {
1258 result = convert(v, tree.type);
1259 } else if (!expressionType.hasTag(BOT) &&
1260 types.isAssignable(expressionType, type)) {
1261 // Redundant cast
1262 result = v;
1263 } else {
1264 // Reference cast
1265 JavaType jt = typeToTypeElement(types.erasure(type));
1266 result = append(JavaOp.cast(typeToTypeElement(type), jt, v));
1267 }
1268 }
1269
1270 @Override
1271 public void visitTypeTest(JCTree.JCInstanceOf tree) {
1272 Value target = toValue(tree.expr);
1273
1274 if (tree.pattern.getTag() != Tag.IDENT) {
1275 result = scanPattern(tree.getPattern(), target);
1276 } else {
1277 result = append(JavaOp.instanceOf(typeToTypeElement(tree.pattern.type), target));
1278 }
1279 }
1280
1281 Value scanPattern(JCTree.JCPattern pattern, Value target) {
1282 // Type of pattern
1283 JavaType patternType;
1284 if (pattern instanceof JCTree.JCBindingPattern p) {
1285 patternType = JavaOp.Pattern.bindingType(typeToTypeElement(p.type));
1286 } else if (pattern instanceof JCTree.JCRecordPattern p) {
1287 patternType = JavaOp.Pattern.recordType(typeToTypeElement(p.record.type));
1288 } else {
1289 throw unsupported(pattern);
1290 }
1291
1292 // Push pattern body
1293 pushBody(pattern, CoreType.functionType(patternType));
1294
1295 // @@@ Assumes just pattern nodes, likely will change when method patterns are supported
1296 // that have expressions for any arguments (which perhaps in turn may have pattern expressions)
1297 List<JCVariableDecl> variables = new ArrayList<>();
1298 class PatternScanner extends FilterScanner {
1299
1300 private Value result;
1301
1302 public PatternScanner() {
1303 super(Set.of(Tag.BINDINGPATTERN, Tag.RECORDPATTERN, Tag.ANYPATTERN));
1304 }
1305
1306 @Override
1307 public void visitBindingPattern(JCTree.JCBindingPattern binding) {
1308 JCVariableDecl var = binding.var;
1309 variables.add(var);
1310 boolean unnamedPatternVariable = var.name.isEmpty();
1311 String bindingName = unnamedPatternVariable ? null : var.name.toString();
1312 result = append(JavaOp.typePattern(typeToTypeElement(var.type), bindingName));
1313 }
1314
1315 @Override
1316 public void visitRecordPattern(JCTree.JCRecordPattern record) {
1317 // @@@ Is always Identifier to record?
1318 // scan(record.deconstructor);
1319
1320 List<Value> nestedValues = new ArrayList<>();
1321 for (JCTree.JCPattern jcPattern : record.nested) {
1322 // @@@ when we support ANYPATTERN, we must add result of toValue only if it's non-null
1323 // because passing null to recordPattern methods will cause an error
1324 nestedValues.add(toValue(jcPattern));
1325 }
1326
1327 result = append(JavaOp.recordPattern(symbolToRecordTypeRef(record.record), nestedValues));
1328 }
1329
1330 @Override
1331 public void visitAnyPattern(JCTree.JCAnyPattern anyPattern) {
1332 result = append(JavaOp.matchAllPattern());
1333 }
1334
1335 Value toValue(JCTree tree) {
1336 result = null;
1337 scan(tree);
1338 return result;
1339 }
1340 }
1341 // Scan pattern
1342 Value patternValue = new PatternScanner().toValue(pattern);
1343 append(CoreOp.core_yield(patternValue));
1344 Body.Builder patternBody = stack.body;
1345
1346 // Pop body
1347 popBody();
1348
1349 // Find nearest ancestor body stack element associated with a statement tree
1350 // @@@ Strengthen check of tree?
1351 BodyStack _variablesStack = stack;
1352 while (!(_variablesStack.tree instanceof JCTree.JCStatement)) {
1353 _variablesStack = _variablesStack.parent;
1354 }
1355 BodyStack variablesStack = _variablesStack;
1356
1357 // Create pattern var ops for pattern variables using the
1358 // builder associated with the nearest statement tree
1359 BodyStack previous = stack;
1360 // Temporarily position the stack to where the pattern variables are to be declared
1361 stack = variablesStack;
1362 try {
1363 for (JCVariableDecl jcVar : variables) {
1364 // @@@ use uninitialized variable
1365 Value defaultValue = append(defaultValue(jcVar.type));
1366 Value init = convert(defaultValue, jcVar.type);
1367 Op.Result op = append(CoreOp.var(jcVar.name.toString(), typeToTypeElement(jcVar.type), init));
1368 stack.localToOp.put(jcVar.sym, op);
1369 }
1370 } finally {
1371 stack = previous;
1372 }
1373
1374 // Create pattern descriptor
1375 List<JavaType> patternDescParams = variables.stream().map(var -> typeToTypeElement(var.type)).toList();
1376 FunctionType matchFuncType = CoreType.functionType(JavaType.VOID, patternDescParams);
1377
1378 // Create the match body, assigning pattern values to pattern variables
1379 Body.Builder matchBody = Body.Builder.of(patternBody.ancestorBody(), matchFuncType);
1380 Block.Builder matchBuilder = matchBody.entryBlock();
1381 for (int i = 0; i < variables.size(); i++) {
1382 Value v = matchBuilder.parameters().get(i);
1383 Value var = variablesStack.localToOp.get(variables.get(i).sym);
1384 matchBuilder.op(CoreOp.varStore(var, v));
1385 }
1386 matchBuilder.op(CoreOp.core_yield());
1387
1388 // Create the match operation
1389 return append(JavaOp.match(target, patternBody, matchBody));
1390 }
1391
1392 @Override
1393 public void visitNewClass(JCTree.JCNewClass tree) {
1394 if (tree.def != null) {
1395 scan(tree.def);
1396 }
1397
1398 // @@@ Support local classes in pre-construction contexts
1399 // this cannot happen, as constructors cannot be reflectable
1400 if (tree.type.tsym.isDirectlyOrIndirectlyLocal() && (tree.type.tsym.flags() & NOOUTERTHIS) != 0) {
1401 throw unsupported(tree);
1402 }
1403
1404 List<TypeElement> argtypes = new ArrayList<>();
1405 Type type = tree.type;
1406 List<Value> args = new ArrayList<>();
1407 if (type.tsym.hasOuterInstance()) {
1408 // Obtain outer value for inner class, and add as first argument
1409 JCTree.JCExpression encl = tree.encl;
1410 Value outerInstance;
1411 if (encl == null) {
1412 outerInstance = thisValue();
1413 } else {
1414 outerInstance = toValue(tree.encl);
1415 }
1416 args.add(outerInstance);
1417 argtypes.add(outerInstance.type());
1418 }
1419 if (tree.type.tsym.isDirectlyOrIndirectlyLocal()) {
1420 for (Symbol c : localCaptures.get(tree.type.tsym)) {
1421 args.add(loadVar(c));
1422 argtypes.add(symbolToErasedDesc(c));
1423 }
1424 }
1425
1426 // Create erased method type reference for constructor, where
1427 // the return type declares the class to instantiate
1428 // We need to manually construct the constructor reference,
1429 // as the signature of the constructor symbol is not augmented
1430 // with enclosing this and captured params.
1431 MethodRef methodRef = symbolToMethodRef(tree.constructor);
1432 argtypes.addAll(methodRef.type().parameterTypes());
1433 FunctionType constructorType = CoreType.functionType(
1434 symbolToErasedDesc(tree.constructor.owner),
1435 argtypes);
1436 MethodRef constructorRef = MethodRef.constructor(constructorType);
1437
1438 args.addAll(scanMethodArguments(tree.args, tree.constructorType, tree.varargsElement));
1439
1440 result = append(JavaOp.new_(tree.varargsElement != null, typeToTypeElement(type), constructorRef, args));
1441 }
1442
1443 @Override
1444 public void visitNewArray(JCTree.JCNewArray tree) {
1445 if (tree.elems != null) {
1446 int length = tree.elems.size();
1447 Op.Result a = append(JavaOp.newArray(
1448 typeToTypeElement(tree.type),
1449 append(CoreOp.constant(JavaType.INT, length))));
1450 int i = 0;
1451 for (JCExpression elem : tree.elems) {
1452 Value element = toValue(elem, types.elemtype(tree.type));
1453 append(JavaOp.arrayStoreOp(
1454 a,
1455 append(CoreOp.constant(JavaType.INT, i)),
1456 element));
1457 i++;
1458 }
1459
1460 result = a;
1461 } else {
1462 List<Value> indexes = new ArrayList<>();
1463 for (JCTree.JCExpression dim : tree.dims) {
1464 indexes.add(toValue(dim));
1465 }
1466
1467 JavaType arrayType = typeToTypeElement(tree.type);
1468 MethodRef constructorRef = MethodRef.constructor(arrayType,
1469 indexes.stream().map(Value::type).toList());
1470 result = append(JavaOp.new_(constructorRef, indexes));
1471 }
1472 }
1473
1474 @Override
1475 public void visitLambda(JCTree.JCLambda tree) {
1476 final FunctionType lambdaType = typeToFunctionType(types.findDescriptorType(tree.target));
1477
1478 // Push quoted body
1479 // We can either be explicitly quoted or a structural quoted expression
1480 // within some larger reflected code
1481
1482 // a reflectable lambda is going to have its model wrapped in QuotedOp
1483 // only when we are producing the model of the lambda, thus the condition (isReflectable ...)
1484 // also, a lambda contained in a reflectable lambda, will not have its model wrapped in QuotedOp,
1485 // thus the condition (... body == tree)
1486 boolean toQuote = (isLambdaReflectable && this.tree == tree);
1487 if (toQuote) {
1488 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1489 }
1490
1491 // Push lambda body
1492 pushBody(tree.body, lambdaType);
1493
1494 // Map lambda parameters to varOp values
1495 for (int i = 0; i < tree.params.size(); i++) {
1496 JCVariableDecl p = tree.params.get(i);
1497 Op.Result paramOp = append(CoreOp.var(
1498 p.name.toString(),
1499 stack.block.parameters().get(i)));
1500 stack.localToOp.put(p.sym, paramOp);
1501 }
1502
1503 // Scan the lambda body
1504 Type lambdaReturnType = tree.getDescriptorType(types).getReturnType();
1505 if (tree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) {
1506 Value exprVal = toValue(((JCExpression) tree.body), lambdaReturnType);
1507 if (!lambdaReturnType.hasTag(TypeTag.VOID)) {
1508 append(CoreOp.return_(exprVal));
1509 } else {
1510 appendTerminating(CoreOp::return_);
1511 }
1512 } else {
1513 Type prevBodyTarget = bodyTarget;
1514 try {
1515 bodyTarget = lambdaReturnType;
1516 toValue(((JCTree.JCStatement) tree.body));
1517 appendReturnOrUnreachable(tree.body);
1518 } finally {
1519 bodyTarget = prevBodyTarget;
1520 }
1521 }
1522
1523 // Get the functional interface type
1524 JavaType fiType = typeToTypeElement(tree.target);
1525 // build functional lambda
1526 Op lambdaOp = JavaOp.lambda(fiType, stack.body, true);
1527
1528 // Pop lambda body
1529 popBody();
1530
1531 Value lambdaResult;
1532 if (toQuote) {
1533 lambdaResult = append(lambdaOp, generateLocation(tree, true));
1534 } else {
1535 lambdaResult = append(lambdaOp);
1536 }
1537
1538 if (toQuote) {
1539 append(CoreOp.core_yield(lambdaResult));
1540 CoreOp.QuotedOp quotedOp = CoreOp.quoted(stack.body);
1541
1542 // Pop quoted body
1543 popBody();
1544
1545 lambdaResult = append(quotedOp);
1546 }
1547
1548 result = lambdaResult;
1549 }
1550
1551 @Override
1552 public void visitIf(JCTree.JCIf tree) {
1553 List<Body.Builder> bodies = new ArrayList<>();
1554
1555 while (tree != null) {
1556 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1557
1558 // Push if condition
1559 pushBody(cond,
1560 CoreType.functionType(JavaType.BOOLEAN));
1561 Value last = toValue(cond);
1562 last = convert(last, typeElementToType(JavaType.BOOLEAN));
1563 // Yield the boolean result of the condition
1564 append(CoreOp.core_yield(last));
1565 bodies.add(stack.body);
1566
1567 // Pop if condition
1568 popBody();
1569
1570 // Push if body
1571 pushBody(tree.thenpart, CoreType.FUNCTION_TYPE_VOID);
1572
1573 scan(tree.thenpart);
1574 appendTerminating(CoreOp::core_yield);
1575 bodies.add(stack.body);
1576
1577 // Pop if body
1578 popBody();
1579
1580 JCTree.JCStatement elsepart = tree.elsepart;
1581 if (elsepart == null) {
1582 tree = null;
1583 } else if (elsepart.getTag() == Tag.IF) {
1584 tree = (JCTree.JCIf) elsepart;
1585 } else {
1586 // Push else body
1587 pushBody(elsepart, CoreType.FUNCTION_TYPE_VOID);
1588
1589 scan(elsepart);
1590 appendTerminating(CoreOp::core_yield);
1591 bodies.add(stack.body);
1592
1593 // Pop else body
1594 popBody();
1595
1596 tree = null;
1597 }
1598 }
1599
1600 append(JavaOp.if_(bodies));
1601 result = null;
1602 }
1603
1604 @Override
1605 public void visitSwitchExpression(JCTree.JCSwitchExpression tree) {
1606 Value target = toValue(tree.selector);
1607
1608 Type switchType = adaptBottom(tree.type);
1609 FunctionType caseBodyType = CoreType.functionType(typeToTypeElement(switchType));
1610
1611 List<Body.Builder> bodies = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, caseBodyType,
1612 !tree.hasUnconditionalPattern);
1613
1614 result = append(JavaOp.switchExpression(caseBodyType.returnType(), target, bodies));
1615 }
1616
1617 @Override
1618 public void visitSwitch(JCTree.JCSwitch tree) {
1619 Value target = toValue(tree.selector);
1620
1621 FunctionType actionType = CoreType.FUNCTION_TYPE_VOID;
1622
1623 List<Body.Builder> bodies = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, actionType,
1624 tree.patternSwitch && !tree.hasUnconditionalPattern);
1625
1626 result = append(JavaOp.switchStatement(target, bodies));
1627 }
1628
1629 private List<Body.Builder> visitSwitchStatAndExpr(JCTree tree, JCExpression selector, Value target,
1630 List<JCTree.JCCase> cases, FunctionType caseBodyType,
1631 boolean isDefaultCaseNeeded) {
1632 List<Body.Builder> bodies = new ArrayList<>();
1633 boolean hasDefaultCase = false;
1634
1635 for (JCTree.JCCase c : cases) {
1636 Body.Builder caseLabel = visitCaseLabel(tree, selector, target, c);
1637 Body.Builder caseBody = visitCaseBody(tree, c, caseBodyType, cases.getLast() == c);
1638 bodies.add(caseLabel);
1639 bodies.add(caseBody);
1640 hasDefaultCase = c.labels.head instanceof JCTree.JCDefaultCaseLabel;
1641 }
1642
1643 if (!hasDefaultCase && isDefaultCaseNeeded) {
1644 // label
1645 pushBody(tree, CoreType.functionType(JavaType.BOOLEAN));
1646 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1647 bodies.add(stack.body);
1648 popBody();
1649
1650 // body
1651 pushBody(tree, caseBodyType);
1652 append(JavaOp.throw_(
1653 append(JavaOp.new_(MethodRef.constructor(MatchException.class)))
1654 ));
1655 bodies.add(stack.body);
1656 popBody();
1657 }
1658
1659 return bodies;
1660 }
1661
1662 private Body.Builder visitCaseLabel(JCTree tree, JCExpression selector, Value target, JCTree.JCCase c) {
1663 Body.Builder body;
1664 FunctionType caseLabelType = CoreType.functionType(JavaType.BOOLEAN, target.type());
1665
1666 JCTree.JCCaseLabel headCl = c.labels.head;
1667 if (headCl instanceof JCTree.JCPatternCaseLabel pcl) {
1668 if (c.labels.size() > 1) {
1669 throw unsupported(c);
1670 }
1671
1672 pushBody(pcl, caseLabelType);
1673
1674 Value localTarget = stack.block.parameters().get(0);
1675 final Value localResult;
1676 if (c.guard != null) {
1677 List<Body.Builder> clBodies = new ArrayList<>();
1678
1679 pushBody(pcl.pat, CoreType.functionType(JavaType.BOOLEAN));
1680 Value patVal = scanPattern(pcl.pat, localTarget);
1681 append(CoreOp.core_yield(patVal));
1682 clBodies.add(stack.body);
1683 popBody();
1684
1685 pushBody(c.guard, CoreType.functionType(JavaType.BOOLEAN));
1686 append(CoreOp.core_yield(toValue(c.guard)));
1687 clBodies.add(stack.body);
1688 popBody();
1689
1690 localResult = append(JavaOp.conditionalAnd(clBodies));
1691 } else {
1692 localResult = scanPattern(pcl.pat, localTarget);
1693 }
1694 // Yield the boolean result of the condition
1695 append(CoreOp.core_yield(localResult));
1696 body = stack.body;
1697
1698 // Pop label
1699 popBody();
1700 } else if (headCl instanceof JCTree.JCConstantCaseLabel ccl) {
1701 pushBody(headCl, caseLabelType);
1702
1703 Value localTarget = stack.block.parameters().get(0);
1704 final Value localResult;
1705 if (c.labels.size() == 1) {
1706 Value expr = toValue(ccl.expr);
1707 // per java spec, constant type is compatible with the type of the selector expression
1708 // so, we convert constant to the type of the selector expression
1709 expr = convert(expr, selector.type);
1710 if (selector.type.isPrimitive()) {
1711 localResult = append(JavaOp.eq(localTarget, expr));
1712 } else {
1713 localResult = append(JavaOp.invoke(
1714 MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
1715 localTarget, expr));
1716 }
1717 } else {
1718 List<Body.Builder> clBodies = new ArrayList<>();
1719 for (JCTree.JCCaseLabel cl : c.labels) {
1720 ccl = (JCTree.JCConstantCaseLabel) cl;
1721 pushBody(ccl, CoreType.functionType(JavaType.BOOLEAN));
1722
1723 Value expr = toValue(ccl.expr);
1724 expr = convert(expr, selector.type);
1725 final Value labelResult;
1726 if (selector.type.isPrimitive()) {
1727 labelResult = append(JavaOp.eq(localTarget, expr));
1728 } else {
1729 labelResult = append(JavaOp.invoke(
1730 MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
1731 localTarget, expr));
1732 }
1733
1734 append(CoreOp.core_yield(labelResult));
1735 clBodies.add(stack.body);
1736
1737 // Pop label
1738 popBody();
1739 }
1740
1741 localResult = append(JavaOp.conditionalOr(clBodies));
1742 }
1743
1744 append(CoreOp.core_yield(localResult));
1745 body = stack.body;
1746
1747 // Pop labels
1748 popBody();
1749 } else if (headCl instanceof JCTree.JCDefaultCaseLabel) {
1750 // @@@ Do we need to model the default label body?
1751 pushBody(headCl, CoreType.functionType(JavaType.BOOLEAN));
1752
1753 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1754 body = stack.body;
1755
1756 // Pop label
1757 popBody();
1758 } else {
1759 throw unsupported(tree);
1760 }
1761
1762 return body;
1763 }
1764
1765 private Body.Builder visitCaseBody(JCTree tree, JCTree.JCCase c, FunctionType caseBodyType, boolean isLastCase) {
1766 Body.Builder body = null;
1767 Type yieldType = tree.type != null ? adaptBottom(tree.type) : Type.noType;
1768
1769 JCTree.JCCaseLabel headCl = c.labels.head;
1770 switch (c.caseKind) {
1771 case RULE -> {
1772 pushBody(c.body, caseBodyType);
1773
1774 if (c.body instanceof JCTree.JCExpression e) {
1775 Value bodyVal = toValue(e, yieldType);
1776 append(CoreOp.core_yield(bodyVal));
1777 } else if (c.body instanceof JCTree.JCStatement s){ // this includes Block
1778 // Otherwise there is a yield statement
1779 Type prevBodyTarget = bodyTarget;
1780 try {
1781 bodyTarget = yieldType;
1782 toValue(s);
1783 } finally {
1784 bodyTarget = prevBodyTarget;
1785 }
1786 appendTerminating(c.completesNormally ? CoreOp::core_yield : CoreOp::unreachable);
1787 }
1788 body = stack.body;
1789
1790 // Pop block
1791 popBody();
1792 }
1793 case STATEMENT -> {
1794 // @@@ Avoid nesting for a single block? Goes against "say what you see"
1795 // boolean oneBlock = c.stats.size() == 1 && c.stats.head instanceof JCBlock;
1796 pushBody(c, caseBodyType);
1797
1798 scan(c.stats);
1799
1800 appendTerminating(c.completesNormally ?
1801 isLastCase ? CoreOp::core_yield : JavaOp::switchFallthroughOp
1802 : CoreOp::unreachable);
1803
1804 body = stack.body;
1805
1806 // Pop block
1807 popBody();
1808 }
1809 }
1810 return body;
1811 }
1812
1813 @Override
1814 public void visitYield(JCTree.JCYield tree) {
1815 Value retVal = toValue(tree.value, bodyTarget);
1816 if (retVal == null) {
1817 result = append(JavaOp.java_yield());
1818 } else {
1819 result = append(JavaOp.java_yield(retVal));
1820 }
1821 }
1822
1823 @Override
1824 public void visitWhileLoop(JCTree.JCWhileLoop tree) {
1825 // @@@ Patterns
1826 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1827
1828 // Push while condition
1829 pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1830 Value last = toValue(cond);
1831 // Yield the boolean result of the condition
1832 last = convert(last, typeElementToType(JavaType.BOOLEAN));
1833 append(CoreOp.core_yield(last));
1834 Body.Builder condition = stack.body;
1835
1836 // Pop while condition
1837 popBody();
1838
1839 // Push while body
1840 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1841 scan(tree.body);
1842 appendTerminating(JavaOp::continue_);
1843 Body.Builder body = stack.body;
1844
1845 // Pop while body
1846 popBody();
1847
1848 append(JavaOp.while_(condition, body));
1849 result = null;
1850 }
1851
1852 @Override
1853 public void visitDoLoop(JCTree.JCDoWhileLoop tree) {
1854 // @@@ Patterns
1855 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1856
1857 // Push while body
1858 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1859 scan(tree.body);
1860 appendTerminating(JavaOp::continue_);
1861 Body.Builder body = stack.body;
1862
1863 // Pop while body
1864 popBody();
1865
1866 // Push while condition
1867 pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1868 Value last = toValue(cond);
1869 last = convert(last, typeElementToType(JavaType.BOOLEAN));
1870 // Yield the boolean result of the condition
1871 append(CoreOp.core_yield(last));
1872 Body.Builder condition = stack.body;
1873
1874 // Pop while condition
1875 popBody();
1876
1877 append(JavaOp.doWhile(body, condition));
1878 result = null;
1879 }
1880
1881 @Override
1882 public void visitForeachLoop(JCTree.JCEnhancedForLoop tree) {
1883 // Push expression
1884 pushBody(tree.expr, CoreType.functionType(typeToTypeElement(tree.expr.type)));
1885 Value last = toValue(tree.expr);
1886 // Yield the Iterable result of the expression
1887 append(CoreOp.core_yield(last));
1888 Body.Builder expression = stack.body;
1889
1890 // Pop expression
1891 popBody();
1892
1893 JCVariableDecl var = tree.getVariable();
1894 VarType varEType = CoreType.varType(typeToTypeElement(var.type));
1895
1896 // Push init
1897 // @@@ When lhs assignment is a pattern we embed the pattern match into the init body and
1898 // return the bound variables
1899 Type exprType = types.cvarUpperBound(tree.expr.type);
1900 Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
1901 if (elemtype == null) {
1902 Type iterableType = types.asSuper(tree.expr.type, syms.iterableType.tsym);
1903 com.sun.tools.javac.util.List<Type> iterableParams = iterableType.allparams();
1904 elemtype = iterableParams.isEmpty()
1905 ? syms.objectType
1906 : types.wildUpperBound(iterableParams.head);
1907 }
1908 pushBody(var, CoreType.functionType(varEType, typeToTypeElement(elemtype)));
1909 var initVarExpr = convert(stack.block.parameters().get(0), var.type);
1910 Op.Result varEResult = append(CoreOp.var(var.name.toString(), initVarExpr));
1911 append(CoreOp.core_yield(varEResult));
1912 Body.Builder init = stack.body;
1913 // Pop init
1914 popBody();
1915
1916 // Push body
1917 pushBody(tree.body, CoreType.functionType(JavaType.VOID, varEType));
1918 stack.localToOp.put(var.sym, stack.block.parameters().get(0));
1919
1920 scan(tree.body);
1921 appendTerminating(JavaOp::continue_);
1922 Body.Builder body = stack.body;
1923 // Pop body
1924 popBody();
1925
1926 append(JavaOp.enhancedFor(expression, init, body));
1927 result = null;
1928 }
1929
1930 @Override
1931 public void visitForLoop(JCTree.JCForLoop tree) {
1932 class VarDefScanner extends FilterScanner {
1933 final List<JCVariableDecl> decls;
1934
1935 public VarDefScanner() {
1936 super(Set.of(Tag.VARDEF));
1937 this.decls = new ArrayList<>();
1938 }
1939
1940 @Override
1941 public void visitVarDef(JCVariableDecl tree) {
1942 decls.add(tree);
1943 }
1944
1945 void mapVarsToBlockArguments() {
1946 for (int i = 0; i < decls.size(); i++) {
1947 stack.localToOp.put(decls.get(i).sym, stack.block.parameters().get(i));
1948 }
1949 }
1950
1951 List<VarType> varTypes() {
1952 return decls.stream()
1953 .map(t -> CoreType.varType(typeToTypeElement(t.type)))
1954 .toList();
1955 }
1956
1957 List<Value> varValues() {
1958 return decls.stream()
1959 .map(t -> stack.localToOp.get(t.sym))
1960 .toList();
1961 }
1962 }
1963
1964 // Scan local variable declarations
1965 VarDefScanner vds = new VarDefScanner();
1966 vds.scan(tree.init);
1967 List<VarType> varTypes = vds.varTypes();
1968
1969 // Push init
1970 if (varTypes.size() > 1) {
1971 pushBody(null, CoreType.functionType(CoreType.tupleType(varTypes)));
1972 scan(tree.init);
1973
1974 // Capture all local variable declarations in tuple
1975 append(CoreOp.core_yield(append(CoreOp.tuple(vds.varValues()))));
1976 } else if (varTypes.size() == 1) {
1977 pushBody(null, CoreType.functionType(varTypes.get(0)));
1978 scan(tree.init);
1979
1980 append(CoreOp.core_yield(vds.varValues().get(0)));
1981 } else {
1982 pushBody(null, CoreType.FUNCTION_TYPE_VOID);
1983 scan(tree.init);
1984
1985 append(CoreOp.core_yield());
1986 }
1987 Body.Builder init = stack.body;
1988
1989 // Pop init
1990 popBody();
1991
1992 // Push cond
1993 pushBody(tree.cond, CoreType.functionType(JavaType.BOOLEAN, varTypes));
1994 if (tree.cond != null) {
1995 vds.mapVarsToBlockArguments();
1996
1997 Value last = toValue(tree.cond);
1998 // Yield the boolean result of the condition
1999 append(CoreOp.core_yield(last));
2000 } else {
2001 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
2002 }
2003 Body.Builder cond = stack.body;
2004
2005 // Pop cond
2006 popBody();
2007
2008 // Push update
2009 // @@@ tree.step is a List<JCStatement>
2010 pushBody(null, CoreType.functionType(JavaType.VOID, varTypes));
2011 if (!tree.step.isEmpty()) {
2012 vds.mapVarsToBlockArguments();
2013
2014 scan(tree.step);
2015 }
2016 append(CoreOp.core_yield());
2017 Body.Builder update = stack.body;
2018
2019 // Pop update
2020 popBody();
2021
2022 // Push body
2023 pushBody(tree.body, CoreType.functionType(JavaType.VOID, varTypes));
2024 if (tree.body != null) {
2025 vds.mapVarsToBlockArguments();
2026
2027 scan(tree.body);
2028 }
2029 appendTerminating(JavaOp::continue_);
2030 Body.Builder body = stack.body;
2031
2032 // Pop update
2033 popBody();
2034
2035 append(JavaOp.for_(init, cond, update, body));
2036 result = null;
2037 }
2038
2039 @Override
2040 public void visitConditional(JCTree.JCConditional tree) {
2041 List<Body.Builder> bodies = new ArrayList<>();
2042
2043 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2044
2045 // Push condition
2046 pushBody(cond,
2047 CoreType.functionType(JavaType.BOOLEAN));
2048 Value condVal = toValue(cond);
2049 // Yield the boolean result of the condition
2050 append(CoreOp.core_yield(condVal));
2051 bodies.add(stack.body);
2052
2053 // Pop condition
2054 popBody();
2055
2056 JCTree.JCExpression truepart = TreeInfo.skipParens(tree.truepart);
2057
2058 Type condType = adaptBottom(tree.type);
2059
2060 // Push true body
2061 pushBody(truepart,
2062 CoreType.functionType(typeToTypeElement(condType)));
2063
2064 Value trueVal = toValue(truepart, condType);
2065 // Yield the result
2066 append(CoreOp.core_yield(trueVal));
2067 bodies.add(stack.body);
2068
2069 // Pop true body
2070 popBody();
2071
2072 JCTree.JCExpression falsepart = TreeInfo.skipParens(tree.falsepart);
2073
2074 // Push false body
2075 pushBody(falsepart,
2076 CoreType.functionType(typeToTypeElement(condType)));
2077
2078 Value falseVal = toValue(falsepart, condType);
2079 // Yield the result
2080 append(CoreOp.core_yield(falseVal));
2081 bodies.add(stack.body);
2082
2083 // Pop false body
2084 popBody();
2085
2086 result = append(JavaOp.conditionalExpression(typeToTypeElement(condType), bodies));
2087 }
2088
2089 private Type condType(JCExpression tree, Type type) {
2090 if (type.hasTag(BOT)) {
2091 return adaptBottom(tree.type);
2092 } else {
2093 return type;
2094 }
2095 }
2096
2097 private Type adaptBottom(Type type) {
2098 return type.hasTag(BOT) ?
2099 (pt.hasTag(NONE) ? syms.objectType : pt) :
2100 type;
2101 }
2102
2103 @Override
2104 public void visitAssert(JCAssert tree) {
2105 // assert <cond:body1> [detail:body2]
2106
2107 List<Body.Builder> bodies = new ArrayList<>();
2108 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2109
2110 // Push condition
2111 pushBody(cond,
2112 CoreType.functionType(JavaType.BOOLEAN));
2113 Value condVal = toValue(cond);
2114
2115 // Yield the boolean result of the condition
2116 append(CoreOp.core_yield(condVal));
2117 bodies.add(stack.body);
2118
2119 // Pop condition
2120 popBody();
2121
2122 if (tree.detail != null) {
2123 JCTree.JCExpression detail = TreeInfo.skipParens(tree.detail);
2124
2125 pushBody(detail,
2126 CoreType.functionType(typeToTypeElement(tree.detail.type)));
2127 Value detailVal = toValue(detail);
2128
2129 append(CoreOp.core_yield(detailVal));
2130 bodies.add(stack.body);
2131
2132 //Pop detail
2133 popBody();
2134 }
2135
2136 result = append(JavaOp.assert_(bodies));
2137
2138 }
2139
2140 @Override
2141 public void visitBlock(JCTree.JCBlock tree) {
2142 if (stack.tree == tree) {
2143 // Block is associated with the visit of a parent structure
2144 scan(tree.stats);
2145 } else {
2146 // Otherwise, independent block structure
2147 // Push block
2148 pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2149 scan(tree.stats);
2150 appendTerminating(CoreOp::core_yield);
2151 Body.Builder body = stack.body;
2152
2153 // Pop block
2154 popBody();
2155
2156 append(JavaOp.block(body));
2157 }
2158 result = null;
2159 }
2160
2161 @Override
2162 public void visitSynchronized(JCTree.JCSynchronized tree) {
2163 // Push expr
2164 pushBody(tree.lock, CoreType.functionType(typeToTypeElement(tree.lock.type)));
2165 Value last = toValue(tree.lock);
2166 append(CoreOp.core_yield(last));
2167 Body.Builder expr = stack.body;
2168
2169 // Pop expr
2170 popBody();
2171
2172 // Push body block
2173 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
2174 // Scan body block statements
2175 scan(tree.body.stats);
2176 appendTerminating(CoreOp::core_yield);
2177 Body.Builder blockBody = stack.body;
2178
2179 // Pop body block
2180 popBody();
2181
2182 append(JavaOp.synchronized_(expr, blockBody));
2183 }
2184
2185 @Override
2186 public void visitLabelled(JCTree.JCLabeledStatement tree) {
2187 // Push block
2188 pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2189 // Create constant for label
2190 String labelName = tree.label.toString();
2191 Op.Result label = append(CoreOp.constant(JavaType.J_L_STRING, labelName));
2192 // Set label on body stack
2193 stack.setLabel(labelName, label);
2194 scan(tree.body);
2195 appendTerminating(CoreOp::core_yield);
2196 Body.Builder body = stack.body;
2197
2198 // Pop block
2199 popBody();
2200
2201 result = append(JavaOp.labeled(body));
2202 }
2203
2204 @Override
2205 public void visitTry(JCTree.JCTry tree) {
2206 List<JCVariableDecl> rVariableDecls = new ArrayList<>();
2207 List<TypeElement> rTypes = new ArrayList<>();
2208 Body.Builder resources;
2209 if (!tree.resources.isEmpty()) {
2210 // Resources body returns a tuple that contains the resource variables/values
2211 // in order of declaration
2212 for (JCTree resource : tree.resources) {
2213 if (resource instanceof JCVariableDecl vdecl) {
2214 rVariableDecls.add(vdecl);
2215 rTypes.add(CoreType.varType(typeToTypeElement(vdecl.type)));
2216 } else {
2217 rTypes.add(typeToTypeElement(resource.type));
2218 }
2219 }
2220
2221 // Push resources body
2222 pushBody(null, CoreType.functionType(CoreType.tupleType(rTypes)));
2223
2224 List<Value> rValues = new ArrayList<>();
2225 for (JCTree resource : tree.resources) {
2226 if (resource instanceof JCTree.JCExpression e) {
2227 rValues.add(toValue(e));
2228 } else if (resource instanceof JCTree.JCStatement s) {
2229 rValues.add(toValue(s));
2230 }
2231 }
2232
2233 append(CoreOp.core_yield(append(CoreOp.tuple(rValues))));
2234 resources = stack.body;
2235
2236 // Pop resources body
2237 popBody();
2238 } else {
2239 resources = null;
2240 }
2241
2242 // Push body
2243 // Try body accepts the resource variables (in order of declaration).
2244 List<VarType> rVarTypes = rTypes.stream().<VarType>mapMulti((t, c) -> {
2245 if (t instanceof VarType vt) {
2246 c.accept(vt);
2247 }
2248 }).toList();
2249 pushBody(tree.body, CoreType.functionType(JavaType.VOID, rVarTypes));
2250 for (int i = 0; i < rVariableDecls.size(); i++) {
2251 stack.localToOp.put(rVariableDecls.get(i).sym, stack.block.parameters().get(i));
2252 }
2253 scan(tree.body);
2254 appendTerminating(CoreOp::core_yield);
2255 Body.Builder body = stack.body;
2256
2257 // Pop block
2258 popBody();
2259
2260 List<Body.Builder> catchers = new ArrayList<>();
2261 for (JCTree.JCCatch catcher : tree.catchers) {
2262 // Push body
2263 pushBody(catcher.body, CoreType.functionType(JavaType.VOID, typeToTypeElement(catcher.param.type)));
2264 Op.Result exVariable = append(CoreOp.var(
2265 catcher.param.name.toString(),
2266 stack.block.parameters().get(0)));
2267 stack.localToOp.put(catcher.param.sym, exVariable);
2268 scan(catcher.body);
2269 appendTerminating(CoreOp::core_yield);
2270 catchers.add(stack.body);
2271
2272 // Pop block
2273 popBody();
2274 }
2275
2276 Body.Builder finalizer;
2277 if (tree.finalizer != null) {
2278 // Push body
2279 pushBody(tree.finalizer, CoreType.FUNCTION_TYPE_VOID);
2280 scan(tree.finalizer);
2281 appendTerminating(CoreOp::core_yield);
2282 finalizer = stack.body;
2283
2284 // Pop block
2285 popBody();
2286 }
2287 else {
2288 finalizer = null;
2289 }
2290
2291 result = append(JavaOp.try_(resources, body, catchers, finalizer));
2292 }
2293
2294 @Override
2295 public void visitUnary(JCTree.JCUnary tree) {
2296 Tag tag = tree.getTag();
2297 switch (tag) {
2298 case POSTINC, POSTDEC, PREINC, PREDEC -> {
2299 // Capture applying rhs and operation
2300 Function<Value, Value> scanRhs = (lhs) -> {
2301 Type unboxedType = types.unboxedTypeOrType(tree.type);
2302 Value one = convert(append(numericOneValue(unboxedType)), unboxedType);
2303 Value unboxedLhs = unboxIfNeeded(lhs);
2304
2305 Value unboxedLhsPlusOne = switch (tree.getTag()) {
2306 // Arithmetic operations
2307 case POSTINC, PREINC -> append(JavaOp.add(unboxedLhs, one));
2308 case POSTDEC, PREDEC -> append(JavaOp.sub(unboxedLhs, one));
2309
2310 default -> throw unsupported(tree);
2311 };
2312 Value lhsPlusOne = convert(unboxedLhsPlusOne, tree.type);
2313
2314 // Assign expression result
2315 result = switch (tree.getTag()) {
2316 case POSTINC, POSTDEC -> lhs;
2317 case PREINC, PREDEC -> lhsPlusOne;
2318
2319 default -> throw unsupported(tree);
2320 };
2321 return lhsPlusOne;
2322 };
2323
2324 applyCompoundAssign(tree.arg, scanRhs);
2325 }
2326 case NEG -> {
2327 Value rhs = toValue(tree.arg, tree.type);
2328 result = append(JavaOp.neg(rhs));
2329 }
2330 case NOT -> {
2331 Value rhs = toValue(tree.arg, tree.type);
2332 result = append(JavaOp.not(rhs));
2333 }
2334 case COMPL -> {
2335 Value rhs = toValue(tree.arg, tree.type);
2336 result = append(JavaOp.compl(rhs));
2337 }
2338 case POS -> {
2339 // Result is value of the operand
2340 result = toValue(tree.arg, tree.type);
2341 }
2342 default -> throw unsupported(tree);
2343 }
2344 }
2345
2346 @Override
2347 public void visitBinary(JCBinary tree) {
2348 Tag tag = tree.getTag();
2349 if (tag == Tag.AND || tag == Tag.OR) {
2350 // Logical operations
2351 // @@@ Flatten nested sequences
2352
2353 // Push lhs
2354 pushBody(tree.lhs, CoreType.functionType(JavaType.BOOLEAN));
2355 Value lhs = toValue(tree.lhs);
2356 // Yield the boolean result of the condition
2357 append(CoreOp.core_yield(lhs));
2358 Body.Builder bodyLhs = stack.body;
2359
2360 // Pop lhs
2361 popBody();
2362
2363 // Push rhs
2364 pushBody(tree.rhs, CoreType.functionType(JavaType.BOOLEAN));
2365 Value rhs = toValue(tree.rhs);
2366 // Yield the boolean result of the condition
2367 append(CoreOp.core_yield(rhs));
2368 Body.Builder bodyRhs = stack.body;
2369
2370 // Pop lhs
2371 popBody();
2372
2373 List<Body.Builder> bodies = List.of(bodyLhs, bodyRhs);
2374 result = append(tag == Tag.AND
2375 ? JavaOp.conditionalAnd(bodies)
2376 : JavaOp.conditionalOr(bodies));
2377 } else if (tag == Tag.PLUS && tree.operator.opcode == ByteCodes.string_add) {
2378 //Ignore the operator and query both subexpressions for their type with concats
2379 Type lhsType = tree.lhs.type;
2380 Type rhsType = tree.rhs.type;
2381
2382 Value lhs = toValue(tree.lhs, lhsType);
2383 Value rhs = toValue(tree.rhs, rhsType);
2384
2385 result = append(JavaOp.concat(lhs, rhs));
2386 }
2387 else {
2388 Type opType = tree.operator.type.getParameterTypes().getFirst();
2389 // @@@ potentially handle shift input conversion like other binary ops
2390 boolean isShift = tag == Tag.SL || tag == Tag.SR || tag == Tag.USR;
2391 Value lhs = toValue(tree.lhs, opType);
2392 Value rhs = toValue(tree.rhs, isShift ? tree.operator.type.getParameterTypes().getLast() : opType);
2393
2394 result = switch (tag) {
2395 // Arithmetic operations
2396 case PLUS -> append(JavaOp.add(lhs, rhs));
2397 case MINUS -> append(JavaOp.sub(lhs, rhs));
2398 case MUL -> append(JavaOp.mul(lhs, rhs));
2399 case DIV -> append(JavaOp.div(lhs, rhs));
2400 case MOD -> append(JavaOp.mod(lhs, rhs));
2401
2402 // Test operations
2403 case EQ -> append(JavaOp.eq(lhs, rhs));
2404 case NE -> append(JavaOp.neq(lhs, rhs));
2405 //
2406 case LT -> append(JavaOp.lt(lhs, rhs));
2407 case LE -> append(JavaOp.le(lhs, rhs));
2408 case GT -> append(JavaOp.gt(lhs, rhs));
2409 case GE -> append(JavaOp.ge(lhs, rhs));
2410
2411 // Bitwise operations (including their boolean variants)
2412 case BITOR -> append(JavaOp.or(lhs, rhs));
2413 case BITAND -> append(JavaOp.and(lhs, rhs));
2414 case BITXOR -> append(JavaOp.xor(lhs, rhs));
2415
2416 // Shift operations
2417 case SL -> append(JavaOp.lshl(lhs, rhs));
2418 case SR -> append(JavaOp.ashr(lhs, rhs));
2419 case USR -> append(JavaOp.lshr(lhs, rhs));
2420
2421 default -> throw unsupported(tree);
2422 };
2423 }
2424 }
2425
2426 @Override
2427 public void visitLiteral(JCLiteral tree) {
2428 Object value = switch (tree.type.getTag()) {
2429 case BOOLEAN -> tree.value instanceof Integer i && i == 1;
2430 case CHAR -> (char) (int) tree.value;
2431 default -> tree.value;
2432 };
2433 Type constantType = adaptBottom(tree.type);
2434 result = append(CoreOp.constant(typeToTypeElement(constantType), value));
2435 }
2436
2437 @Override
2438 public void visitReturn(JCReturn tree) {
2439 Value retVal = toValue(tree.expr, bodyTarget);
2440 if (retVal == null) {
2441 result = append(CoreOp.return_());
2442 } else {
2443 result = append(CoreOp.return_(retVal));
2444 }
2445 }
2446
2447 @Override
2448 public void visitThrow(JCTree.JCThrow tree) {
2449 Value throwVal = toValue(tree.expr);
2450 result = append(JavaOp.throw_(throwVal));
2451 }
2452
2453 @Override
2454 public void visitBreak(JCTree.JCBreak tree) {
2455 Value label = tree.label != null
2456 ? getLabel(tree.label.toString())
2457 : null;
2458 result = append(JavaOp.break_(label));
2459 }
2460
2461 @Override
2462 public void visitContinue(JCTree.JCContinue tree) {
2463 Value label = tree.label != null
2464 ? getLabel(tree.label.toString())
2465 : null;
2466 result = append(JavaOp.continue_(label));
2467 }
2468
2469 @Override
2470 public void visitClassDef(JCClassDecl tree) {
2471 computeCapturesIfNeeded(tree);
2472 }
2473
2474 UnsupportedASTException unsupported(JCTree tree) {
2475 return new UnsupportedASTException(tree);
2476 }
2477
2478 CoreOp.FuncOp scanMethod(JCBlock body) {
2479 scan(body, ReflectMethods.this.currentNode());
2480 appendReturnOrUnreachable(body);
2481 CoreOp.FuncOp func = CoreOp.func(name.toString(), stack.body);
2482 func.setLocation(generateLocation(tree, true));
2483 return func;
2484 }
2485
2486 CoreOp.FuncOp scanMethod() {
2487 return scanMethod(((JCMethodDecl)tree).body);
2488 }
2489
2490 CoreOp.FuncOp scanLambda() {
2491 scan(tree, ReflectMethods.this.prevNode());
2492 // Return the quoted result
2493 append(CoreOp.return_(result));
2494 return CoreOp.func(name.toString(), stack.body);
2495 }
2496
2497 Op defaultValue(Type t) {
2498 return switch (t.getTag()) {
2499 case BYTE, SHORT, INT -> CoreOp.constant(JavaType.INT, 0);
2500 case CHAR -> CoreOp.constant(typeToTypeElement(t), (char)0);
2501 case BOOLEAN -> CoreOp.constant(typeToTypeElement(t), false);
2502 case FLOAT -> CoreOp.constant(typeToTypeElement(t), 0f);
2503 case LONG -> CoreOp.constant(typeToTypeElement(t), 0L);
2504 case DOUBLE -> CoreOp.constant(typeToTypeElement(t), 0d);
2505 default -> CoreOp.constant(typeToTypeElement(t), null);
2506 };
2507 }
2508
2509 Op numericOneValue(Type t) {
2510 return switch (t.getTag()) {
2511 case BYTE, SHORT, INT -> CoreOp.constant(JavaType.INT, 1);
2512 case CHAR -> CoreOp.constant(typeToTypeElement(t), (char)1);
2513 case FLOAT -> CoreOp.constant(typeToTypeElement(t), 1f);
2514 case LONG -> CoreOp.constant(typeToTypeElement(t), 1L);
2515 case DOUBLE -> CoreOp.constant(typeToTypeElement(t), 1d);
2516 default -> throw new UnsupportedOperationException(t.toString());
2517 };
2518 }
2519 }
2520
2521 /**
2522 * An exception thrown when an unsupported AST node is found when building a method IR.
2523 */
2524 static class UnsupportedASTException extends RuntimeException {
2525
2526 private static final long serialVersionUID = 0;
2527 transient final JCTree tree;
2528
2529 public UnsupportedASTException(JCTree tree) {
2530 this.tree = tree;
2531 }
2532
2533 @Override
2534 public String toString() {
2535 return super.toString() + ":" + tree;
2536 }
2537 }
2538
2539 boolean isReflectable(JCMethodDecl tree) {
2540 return reflectAll || codeReflectionEnabled ||
2541 (tree.body != null &&
2542 tree.sym.attribute(crSyms.codeReflectionType.tsym) != null);
2543 }
2544
2545 boolean isReflectable(JCFunctionalExpression expr) {
2546 return reflectAll || codeReflectionEnabled ||
2547 (prevNode() instanceof JCTypeCast castTree && isReflectable(castTree.clazz.type));
2548 }
2549
2550 boolean isReflectable(Type target) {
2551 if (target.isCompound()) {
2552 return ((IntersectionClassType)target).getComponents().stream()
2553 .anyMatch(this::isReflectable);
2554 } else {
2555 return target.getAnnotationMirrors().stream()
2556 .anyMatch(tc -> tc.type.tsym == crSyms.codeReflectionType.tsym);
2557 }
2558 }
2559
2560 /*
2561 * Converts a method reference which cannot be used directly into a lambda.
2562 * This code has been derived from LambdaToMethod::MemberReferenceToLambda. The main
2563 * difference is that, while that code concerns with translation strategy, boxing
2564 * conversion and type erasure, this version does not and, as such, can remain
2565 * at a higher level. Note that this code needs to create a synthetic variable
2566 * declaration in case of a bounded method reference whose receiver expression
2567 * is other than 'this'/'super' (this is done to prevent the receiver expression
2568 * from being computed twice).
2569 */
2570 private class MemberReferenceToLambda {
2571
2572 private final JCMemberReference tree;
2573 private final Symbol owner;
2574 private final ListBuffer<JCExpression> args = new ListBuffer<>();
2575 private final ListBuffer<JCVariableDecl> params = new ListBuffer<>();
2576 private JCVariableDecl receiverVar = null;
2577
2578 MemberReferenceToLambda(JCMemberReference tree, Symbol currentClass) {
2579 this.tree = tree;
2580 this.owner = new MethodSymbol(0, names.lambda, tree.target, currentClass);
2581 if (tree.kind == ReferenceKind.BOUND && !TreeInfo.isThisQualifier(tree.getQualifierExpression())) {
2582 // true bound method reference, hoist receiver expression out
2583 Type recvType = types.asSuper(tree.getQualifierExpression().type, tree.sym.owner);
2584 VarSymbol vsym = makeSyntheticVar("rec$", recvType);
2585 receiverVar = make.VarDef(vsym, tree.getQualifierExpression());
2586 }
2587 }
2588
2589 JCVariableDecl receiverVar() {
2590 return receiverVar;
2591 }
2592
2593 JCLambda lambda() {
2594 int prevPos = make.pos;
2595 try {
2596 make.at(tree);
2597
2598 //body generation - this can be either a method call or a
2599 //new instance creation expression, depending on the member reference kind
2600 VarSymbol rcvr = addParametersReturnReceiver();
2601 JCExpression expr = (tree.getMode() == ReferenceMode.INVOKE)
2602 ? expressionInvoke(rcvr)
2603 : expressionNew();
2604
2605 JCLambda slam = make.Lambda(params.toList(), expr);
2606 slam.target = tree.target;
2607 slam.type = tree.type;
2608 slam.pos = tree.pos;
2609 return slam;
2610 } finally {
2611 make.at(prevPos);
2612 }
2613 }
2614
2615 /**
2616 * Generate the parameter list for the converted member reference.
2617 *
2618 * @return The receiver variable symbol, if any
2619 */
2620 VarSymbol addParametersReturnReceiver() {
2621 com.sun.tools.javac.util.List<Type> descPTypes = tree.getDescriptorType(types).getParameterTypes();
2622 VarSymbol receiverParam = null;
2623 switch (tree.kind) {
2624 case BOUND:
2625 if (receiverVar != null) {
2626 receiverParam = receiverVar.sym;
2627 }
2628 break;
2629 case UNBOUND:
2630 // The receiver is the first parameter, extract it and
2631 // adjust the SAM and unerased type lists accordingly
2632 receiverParam = addParameter("rec$", descPTypes.head, false);
2633 descPTypes = descPTypes.tail;
2634 break;
2635 }
2636 for (int i = 0; descPTypes.nonEmpty(); ++i) {
2637 // By default use the implementation method parameter type
2638 Type parmType = descPTypes.head;
2639 addParameter("x$" + i, parmType, true);
2640
2641 // Advance to the next parameter
2642 descPTypes = descPTypes.tail;
2643 }
2644
2645 return receiverParam;
2646 }
2647
2648 /**
2649 * determine the receiver of the method call - the receiver can
2650 * be a type qualifier, the synthetic receiver parameter or 'super'.
2651 */
2652 private JCExpression expressionInvoke(VarSymbol receiverParam) {
2653 JCExpression qualifier = receiverParam != null ?
2654 make.at(tree.pos).Ident(receiverParam) :
2655 tree.getQualifierExpression();
2656
2657 //create the qualifier expression
2658 JCFieldAccess select = make.Select(qualifier, tree.sym.name);
2659 select.sym = tree.sym;
2660 select.type = tree.referentType;
2661
2662 //create the method call expression
2663 JCMethodInvocation apply = make.Apply(com.sun.tools.javac.util.List.nil(), select, args.toList()).
2664 setType(tree.referentType.getReturnType());
2665
2666 apply.varargsElement = tree.varargsElement;
2667 return apply;
2668 }
2669
2670 /**
2671 * Lambda body to use for a 'new'.
2672 */
2673 private JCExpression expressionNew() {
2674 Type expectedType = tree.referentType.getReturnType().hasTag(TypeTag.VOID) ?
2675 tree.expr.type : tree.referentType.getReturnType();
2676 if (tree.kind == ReferenceKind.ARRAY_CTOR) {
2677 //create the array creation expression
2678 JCNewArray newArr = make.NewArray(
2679 make.Type(types.elemtype(expectedType)),
2680 com.sun.tools.javac.util.List.of(make.Ident(params.first())),
2681 null);
2682 newArr.type = tree.getQualifierExpression().type;
2683 return newArr;
2684 } else {
2685 //create the instance creation expression
2686 //note that method reference syntax does not allow an explicit
2687 //enclosing class (so the enclosing class is null)
2688 // but this may need to be patched up later with the proxy for the outer this
2689 JCExpression newType = make.Type(types.erasure(expectedType));
2690 if (expectedType.tsym.type.getTypeArguments().nonEmpty()) {
2691 newType = make.TypeApply(newType, com.sun.tools.javac.util.List.nil());
2692 }
2693 JCNewClass newClass = make.NewClass(null,
2694 com.sun.tools.javac.util.List.nil(),
2695 newType,
2696 args.toList(),
2697 null);
2698 newClass.constructor = tree.sym;
2699 newClass.constructorType = tree.referentType;
2700 newClass.type = expectedType;
2701 newClass.varargsElement = tree.varargsElement;
2702 return newClass;
2703 }
2704 }
2705
2706 private VarSymbol makeSyntheticVar(String name, Type type) {
2707 VarSymbol vsym = new VarSymbol(PARAMETER | SYNTHETIC, names.fromString(name), type, owner);
2708 vsym.pos = tree.pos;
2709 return vsym;
2710 }
2711
2712 private VarSymbol addParameter(String name, Type type, boolean genArg) {
2713 VarSymbol vsym = makeSyntheticVar(name, type);
2714 params.append(make.VarDef(vsym, null));
2715 if (genArg) {
2716 args.append(make.Ident(vsym));
2717 }
2718 return vsym;
2719 }
2720 }
2721
2722 static class JCReflectMethodsClassDecl extends JCClassDecl {
2723
2724 SequencedMap<String, Op> ops;
2725
2726 JCReflectMethodsClassDecl(JCClassDecl cls, SequencedMap<String, Op> ops) {
2727 super(cls.mods, cls.name, cls.typarams, cls.extending, cls.implementing, cls.permitting, cls.defs, cls.sym);
2728 this.pos = cls.pos;
2729 this.type = cls.type;
2730 this.ops = ops;
2731 }
2732 }
2733
2734 public static class Provider implements CodeReflectionTransformer {
2735 @Override
2736 public JCTree translateTopLevelClass(Context context, JCTree tree, TreeMaker make) {
2737 return ReflectMethods.instance(context).translateTopLevelClass(tree, make);
2738 }
2739
2740 @Override
2741 public void genCode(Context context, JCClassDecl cdef) throws IOException {
2742 if (cdef instanceof JCReflectMethodsClassDecl rmcdef) {
2743 JavaFileManager fileManager = context.get(JavaFileManager.class);
2744 JavaFileManager.Location outLocn;
2745 if (fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
2746 outLocn = fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT, cdef.sym.packge().modle.name.toString());
2747 } else {
2748 outLocn = StandardLocation.CLASS_OUTPUT;
2749 }
2750 String className = cdef.sym.flatName().toString() + "$$CM";
2751 ClassDesc classDesc = ClassDesc.of(className);
2752 JavaFileObject outFile = fileManager.getJavaFileForOutput(outLocn, className, JavaFileObject.Kind.CLASS, cdef.sym.sourcefile);
2753 ClassDesc hostClass = ClassDesc.of(cdef.sym.flatName().toString());
2754
2755 CoreOp.ModuleOp module = OpBuilder.createBuilderFunctions(
2756 rmcdef.ops,
2757 b -> b.op(JavaOp.fieldLoad(
2758 FieldRef.field(JavaOp.class, "JAVA_DIALECT_FACTORY", DialectFactory.class))));
2759 byte[] data = BytecodeGenerator.generateClassData(MethodHandles.lookup(), classDesc, module);
2760 // inject InnerClassesAttribute and NestHostAttribute
2761 var clm = ClassFile.of().parse(data);
2762 data = ClassFile.of().transformClass(clm, ClassTransform.endHandler(clb ->
2763 clb.with(InnerClassesAttribute.of(InnerClassInfo.of(classDesc, Optional.of(hostClass), Optional.of("$CM"), ClassFile.ACC_STATIC)))
2764 .with(NestHostAttribute.of(hostClass))));
2765 try (OutputStream out = outFile.openOutputStream()) {
2766 out.write(data);
2767 }
2768 }
2769 }
2770 }
2771
2772 // type and ref conversion utils
2773
2774 JavaType symbolToErasedDesc(Symbol s) {
2775 return typeToTypeElement(s.erasure(types));
2776 }
2777
2778 JavaType typeToTypeElement(Type t) {
2779 Assert.check(!t.hasTag(METHOD));
2780 t = asDenotable(t);
2781 return switch (t.getTag()) {
2782 case VOID -> JavaType.VOID;
2783 case CHAR -> JavaType.CHAR;
2784 case BOOLEAN -> JavaType.BOOLEAN;
2785 case BYTE -> JavaType.BYTE;
2786 case SHORT -> JavaType.SHORT;
2787 case INT -> JavaType.INT;
2788 case FLOAT -> JavaType.FLOAT;
2789 case LONG -> JavaType.LONG;
2790 case DOUBLE -> JavaType.DOUBLE;
2791 case ARRAY -> {
2792 Type et = ((ArrayType)t).elemtype;
2793 yield JavaType.array(typeToTypeElement(et));
2794 }
2795 case WILDCARD -> {
2796 Type.WildcardType wt = (Type.WildcardType)t;
2797 yield wt.isUnbound() ?
2798 JavaType.wildcard() :
2799 JavaType.wildcard(wt.isExtendsBound() ? BoundKind.EXTENDS : BoundKind.SUPER, typeToTypeElement(wt.type));
2800 }
2801 case TYPEVAR -> {
2802 Type ub = t.getUpperBound();
2803 if (ub.contains(t)) {
2804 // @@@ stop infinite recursion, ex: <E extends Enum<E>>
2805 ub = types.erasure(ub);
2806 }
2807 yield t.tsym.owner.kind == Kind.MTH ?
2808 JavaType.typeVarRef(t.tsym.name.toString(), symbolToMethodRef(t.tsym.owner),
2809 typeToTypeElement(ub)) :
2810 JavaType.typeVarRef(t.tsym.name.toString(),
2811 (jdk.incubator.code.dialect.java.ClassType)symbolToErasedDesc(t.tsym.owner),
2812 typeToTypeElement(ub));
2813 }
2814 case CLASS -> {
2815 Assert.check(!t.isIntersection() && !t.isUnion());
2816 JavaType typ;
2817 if (t.getEnclosingType() != Type.noType) {
2818 Name innerName = t.tsym.flatName().subName(t.getEnclosingType().tsym.flatName().length() + 1);
2819 typ = JavaType.qualified(typeToTypeElement(t.getEnclosingType()), innerName.toString());
2820 } else {
2821 typ = JavaType.type(ClassDesc.of(t.tsym.flatName().toString()));
2822 }
2823
2824 List<JavaType> typeArguments;
2825 if (t.getTypeArguments().nonEmpty()) {
2826 typeArguments = new ArrayList<>();
2827 for (Type ta : t.getTypeArguments()) {
2828 typeArguments.add(typeToTypeElement(ta));
2829 }
2830 } else {
2831 typeArguments = List.of();
2832 }
2833
2834 // Use flat name to ensure demarcation of nested classes
2835 yield JavaType.parameterized(typ, typeArguments);
2836 }
2837 default -> throw new UnsupportedOperationException("Unsupported type: kind=" + t.getKind() + " type=" + t);
2838 };
2839 }
2840
2841 Type typeElementToType(TypeElement jt) {
2842 return switch (jt) {
2843 case PrimitiveType pt when pt == JavaType.BOOLEAN -> syms.booleanType;
2844 case PrimitiveType pt when pt == JavaType.CHAR -> syms.charType;
2845 case PrimitiveType pt when pt == JavaType.BYTE -> syms.byteType;
2846 case PrimitiveType pt when pt == JavaType.SHORT -> syms.shortType;
2847 case PrimitiveType pt when pt == JavaType.INT -> syms.intType;
2848 case PrimitiveType pt when pt == JavaType.LONG -> syms.longType;
2849 case PrimitiveType pt when pt == JavaType.FLOAT -> syms.floatType;
2850 case PrimitiveType pt when pt == JavaType.DOUBLE -> syms.doubleType;
2851 case ClassType ct when ct.hasTypeArguments() -> {
2852 Type enclosing = ct.enclosingType().map(this::typeElementToType).orElse(Type.noType);
2853 com.sun.tools.javac.util.List<Type> typeArgs = com.sun.tools.javac.util.List.from(ct.typeArguments()).map(this::typeElementToType);
2854 yield new Type.ClassType(enclosing, typeArgs, typeElementToType(ct.rawType()).tsym);
2855 }
2856 case ClassType ct -> types.erasure(syms.enterClass(attrEnv().toplevel.modle, names.fromString(ct.toClassName())).type);
2857 case jdk.incubator.code.dialect.java.ArrayType at -> new Type.ArrayType(typeElementToType(at.componentType()), syms.arrayClass);
2858 default -> Type.noType;
2859 };
2860 }
2861
2862 Type symbolSiteType(Symbol s) {
2863 boolean isMember = s.owner == syms.predefClass ||
2864 s.isMemberOf(currentClassSym, types);
2865 return isMember ? currentClassSym.type : s.owner.type;
2866 }
2867
2868 FieldRef symbolToFieldRef(Symbol s, Type site) {
2869 // @@@ Made Gen::binaryQualifier public, duplicate logic?
2870 // Ensure correct qualifying class is used in the reference, see JLS 13.1
2871 // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
2872 return symbolFieldRef(gen.binaryQualifier(s, types.erasure(site)));
2873 }
2874
2875 FieldRef symbolFieldRef(Symbol s) {
2876 Type erasedType = s.erasure(types);
2877 return FieldRef.field(
2878 typeToTypeElement(s.owner.erasure(types)),
2879 s.name.toString(),
2880 typeToTypeElement(erasedType));
2881 }
2882
2883 MethodRef symbolToMethodRef(Symbol s, Type site) {
2884 // @@@ Made Gen::binaryQualifier public, duplicate logic?
2885 // Ensure correct qualifying class is used in the reference, see JLS 13.1
2886 // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
2887 return symbolToMethodRef(gen.binaryQualifier(s, types.erasure(site)));
2888 }
2889
2890 MethodRef symbolToMethodRef(Symbol s) {
2891 Type erasedType = s.erasure(types);
2892 return MethodRef.method(
2893 typeToTypeElement(s.owner.erasure(types)),
2894 s.name.toString(),
2895 typeToTypeElement(erasedType.getReturnType()),
2896 erasedType.getParameterTypes().stream().map(this::typeToTypeElement).toArray(TypeElement[]::new));
2897 }
2898
2899 FunctionType typeToFunctionType(Type t) {
2900 return CoreType.functionType(
2901 typeToTypeElement(t.getReturnType()),
2902 t.getParameterTypes().stream().map(this::typeToTypeElement).toArray(TypeElement[]::new));
2903 }
2904
2905 RecordTypeRef symbolToRecordTypeRef(Symbol.ClassSymbol s) {
2906 TypeElement recordType = typeToTypeElement(s.type);
2907 List<RecordTypeRef.ComponentRef> components = s.getRecordComponents().stream()
2908 .map(rc -> new RecordTypeRef.ComponentRef(typeToTypeElement(rc.type), rc.name.toString()))
2909 .toList();
2910 return RecordTypeRef.recordType(recordType, components);
2911 }
2912
2913 Env<AttrContext> attrEnv() {
2914 return typeEnvs.get(currentClassSym);
2915 }
2916
2917 Type asDenotable(Type t) {
2918 // The goal of this type mapping is to replace occurrences of intersection and union types
2919 // with fresh type variables with appropriate upper bounds. For instance, consider the generic type
2920 // Foo<A & B & C>. We need to:
2921 // 1. replace A & B & C with a fresh type variable, so this becomes Foo<#1>, #1 <: A
2922 // 2. add #1 to the set of type-variables to be projected
2923 // 3. run upward projection on Foo<#1>, which gives Foo<? extends A>
2924 // In other words, by replacing intersection types with fresh type variables we make sure that the output
2925 // of this method is a type that is fully denotable -- e.g. can be fully represented in terms of the
2926 // TypeElement API.
2927 class DenotableProjection extends StructuralTypeMapping<Void> {
2928 final ListBuffer<Type> tvars = new ListBuffer<>();
2929 final Type t;
2930
2931 DenotableProjection(Type t) {
2932 tvars.appendList(types.captures(t));
2933 this.t = t;
2934 }
2935
2936 Type asDenotable() {
2937 return types.upward(apply(t), tvars.toList());
2938 }
2939
2940 @Override
2941 public Type visitClassType(Type.ClassType t, Void unused) {
2942 if (t.isIntersection()) {
2943 Type bound = visit(((IntersectionClassType) t).getExplicitComponents().head, null);
2944 return addTypeVar(bound, t.tsym);
2945 } else if (t.isUnion()) {
2946 Type bound = visit(((UnionClassType)t).getLub(), null);
2947 return addTypeVar(bound, t.tsym);
2948 } else {
2949 return super.visitClassType(t, null);
2950 }
2951 }
2952
2953 Type addTypeVar(Type bound, Symbol owner) {
2954 var tvsym = new TypeVariableSymbol(0, names.empty, null, owner);
2955 tvsym.type = new TypeVar(tvsym, bound, syms.botType);
2956 tvars.append(tvsym.type);
2957 return tvsym.type;
2958 }
2959 }
2960
2961 return new DenotableProjection(t).asDenotable();
2962 }
2963 }