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