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