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