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