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