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