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.TypeVariableSymbol;
  35 import com.sun.tools.javac.code.Symbol.VarSymbol;
  36 import com.sun.tools.javac.code.Symtab;
  37 import com.sun.tools.javac.code.Type;
  38 import com.sun.tools.javac.code.Type.ArrayType;
  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.TypeVar;
  43 import com.sun.tools.javac.code.Type.UnionClassType;
  44 import com.sun.tools.javac.code.TypeTag;
  45 import com.sun.tools.javac.code.Types;
  46 import com.sun.tools.javac.comp.AttrContext;
  47 import com.sun.tools.javac.comp.CaptureScanner;
  48 import com.sun.tools.javac.comp.DeferredAttr.FilterScanner;
  49 import com.sun.tools.javac.comp.Env;
  50 import com.sun.tools.javac.comp.Flow;
  51 import com.sun.tools.javac.comp.Lower;
  52 import com.sun.tools.javac.comp.CodeReflectionTransformer;
  53 import com.sun.tools.javac.comp.TypeEnvs;
  54 import com.sun.tools.javac.file.PathFileObject;
  55 import com.sun.tools.javac.jvm.ByteCodes;
  56 import com.sun.tools.javac.jvm.Gen;
  57 import com.sun.tools.javac.resources.CompilerProperties.*;
  58 import com.sun.tools.javac.tree.JCTree;
  59 import com.sun.tools.javac.tree.JCTree.JCAnnotation;
  60 import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
  61 import com.sun.tools.javac.tree.JCTree.JCAssign;
  62 import com.sun.tools.javac.tree.JCTree.JCBinary;
  63 import com.sun.tools.javac.tree.JCTree.JCBlock;
  64 import com.sun.tools.javac.tree.JCTree.JCCaseLabel;
  65 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
  66 import com.sun.tools.javac.tree.JCTree.JCConstantCaseLabel;
  67 import com.sun.tools.javac.tree.JCTree.JCDefaultCaseLabel;
  68 import com.sun.tools.javac.tree.JCTree.JCExpression;
  69 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
  70 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression;
  71 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression.CodeReflectionInfo;
  72 import com.sun.tools.javac.tree.JCTree.JCIdent;
  73 import com.sun.tools.javac.tree.JCTree.JCLambda;
  74 import com.sun.tools.javac.tree.JCTree.JCLiteral;
  75 import com.sun.tools.javac.tree.JCTree.JCMemberReference;
  76 import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
  77 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
  78 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
  79 import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
  80 import com.sun.tools.javac.tree.JCTree.JCNewArray;
  81 import com.sun.tools.javac.tree.JCTree.JCNewClass;
  82 import com.sun.tools.javac.tree.JCTree.JCReturn;
  83 import com.sun.tools.javac.tree.JCTree.JCTypeCast;
  84 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
  85 import com.sun.tools.javac.tree.JCTree.JCAssert;
  86 import com.sun.tools.javac.tree.JCTree.Tag;
  87 import com.sun.tools.javac.tree.TreeInfo;
  88 import com.sun.tools.javac.tree.TreeMaker;
  89 import com.sun.tools.javac.util.Assert;
  90 import com.sun.tools.javac.util.Context;
  91 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
  92 import com.sun.tools.javac.util.ListBuffer;
  93 import com.sun.tools.javac.util.Log;
  94 import com.sun.tools.javac.util.Name;
  95 import com.sun.tools.javac.util.Names;
  96 import com.sun.tools.javac.util.Options;
  97 import jdk.incubator.code.*;
  98 import jdk.incubator.code.extern.DialectFactory;
  99 import jdk.incubator.code.dialect.core.*;
 100 import jdk.incubator.code.dialect.java.*;
 101 import jdk.incubator.code.dialect.java.WildcardType.BoundKind;
 102 
 103 import javax.lang.model.element.Modifier;
 104 import javax.tools.JavaFileObject;
 105 import java.lang.constant.ClassDesc;
 106 import java.util.*;
 107 import java.util.List;
 108 import java.util.function.Function;
 109 import java.util.function.Supplier;
 110 
 111 import static com.sun.tools.javac.code.Flags.*;
 112 import static com.sun.tools.javac.code.Kinds.Kind.MTH;
 113 import static com.sun.tools.javac.code.Kinds.Kind.TYP;
 114 import static com.sun.tools.javac.code.Kinds.Kind.VAR;
 115 import static com.sun.tools.javac.code.TypeTag.BOT;
 116 import static com.sun.tools.javac.code.TypeTag.CLASS;
 117 import static com.sun.tools.javac.code.TypeTag.INT;
 118 import static com.sun.tools.javac.code.TypeTag.METHOD;
 119 import static com.sun.tools.javac.code.TypeTag.NONE;
 120 import static com.sun.tools.javac.main.Option.G_CUSTOM;
 121 
 122 import java.io.IOException;
 123 import java.io.OutputStream;
 124 import java.lang.classfile.ClassFile;
 125 import java.lang.classfile.ClassTransform;
 126 import java.lang.classfile.attribute.InnerClassInfo;
 127 import java.lang.classfile.attribute.InnerClassesAttribute;
 128 import java.lang.classfile.attribute.NestHostAttribute;
 129 import java.lang.invoke.MethodHandles;
 130 import javax.tools.JavaFileManager;
 131 import javax.tools.StandardLocation;
 132 import jdk.incubator.code.bytecode.BytecodeGenerator;
 133 
 134 /**
 135  * This a tree translator that adds the code model to all method declaration marked
 136  * with the {@code Reflect} annotation. The model is expressed using the code
 137  * reflection API (see {@code jdk.incubator.code}).
 138  */
 139 public class ReflectMethods extends TreeTranslatorPrev {
 140     protected static final Context.Key<ReflectMethods> reflectMethodsKey = new Context.Key<>();
 141 
 142     public static ReflectMethods instance(Context context) {
 143         ReflectMethods instance = context.get(reflectMethodsKey);
 144         if (instance == null)
 145             instance = new ReflectMethods(context);
 146         return instance;
 147     }
 148 
 149     private final Types types;
 150     private final Names names;
 151     private final Symtab syms;
 152     private final Gen gen;
 153     private final Log log;
 154     private final Lower lower;
 155     private final TypeEnvs typeEnvs;
 156     private final Flow flow;
 157     private final CodeReflectionSymbols crSyms;
 158     private final boolean dumpIR;
 159     private final boolean lineDebugInfo;
 160     private final boolean reflectAll;
 161 
 162     private TreeMaker make;
 163     private ListBuffer<JCTree> opMethodDecls;
 164     private SequencedMap<String, Op> ops;
 165     private Symbol.ClassSymbol currentClassSym;
 166     private Symbol.ClassSymbol codeModelsClassSym;
 167     private int lambdaCount;
 168     private boolean codeReflectionEnabled = false;
 169     private final Map<Symbol, List<Symbol>> localCaptures = new HashMap<>();
 170 
 171     @SuppressWarnings("this-escape")
 172     protected ReflectMethods(Context context) {
 173         context.put(reflectMethodsKey, this);
 174         Options options = Options.instance(context);
 175         dumpIR = options.isSet("dumpIR");
 176         lineDebugInfo =
 177                 options.isUnset(G_CUSTOM) ||
 178                         options.isSet(G_CUSTOM, "lines");
 179         reflectAll = options.isSet("reflectAll");
 180         names = Names.instance(context);
 181         syms = Symtab.instance(context);
 182         types = Types.instance(context);
 183         gen = Gen.instance(context);
 184         log = Log.instance(context);
 185         lower = Lower.instance(context);
 186         typeEnvs = TypeEnvs.instance(context);
 187         flow = Flow.instance(context);
 188         crSyms = new CodeReflectionSymbols(context);
 189     }
 190 
 191     @Override
 192     public void visitVarDef(JCVariableDecl tree) {
 193         boolean prevCodeReflectionEnabled = codeReflectionEnabled;
 194         try {
 195             codeReflectionEnabled = codeReflectionEnabled ||
 196                     tree.sym.attribute(crSyms.codeReflectionType.tsym) != null;
 197             super.visitVarDef(tree);
 198         } finally {
 199             codeReflectionEnabled = prevCodeReflectionEnabled;
 200         }
 201     }
 202 
 203     boolean isInsideInnerOrLocalClass() {
 204         return currentClassSym.type.getEnclosingType().hasTag(CLASS) ||
 205                 currentClassSym.isDirectlyOrIndirectlyLocal();
 206     }
 207 
 208     @Override
 209     public void visitMethodDef(JCMethodDecl tree) {
 210         boolean isReflectable = !tree.sym.isConstructor() && isReflectable(tree);
 211         if (isReflectable) {
 212             if (isInsideInnerOrLocalClass()) {
 213                 // Reflectable methods in local classes are not supported
 214                 log.warning(tree, Warnings.ReflectableMethodInnerClass(currentClassSym.enclClass()));
 215                 super.visitMethodDef(tree);
 216                 return;
 217             } else {
 218                 // if the method is annotated, scan it
 219                 BodyScanner bodyScanner = new BodyScanner(tree);
 220                 CoreOp.FuncOp funcOp = bodyScanner.scanMethod();
 221                 if (dumpIR) {
 222                     // dump the method IR if requested
 223                     log.note(Notes.ReflectableMethodIrDump(tree.sym.enclClass(), tree.sym, funcOp.toText()));
 224                 }
 225                 // create a static method that returns the op
 226                 Name methodName = methodName(symbolToMethodRef(tree.sym));
 227                 opMethodDecls.add(opMethodDecl(methodName));
 228                 ops.put(methodName.toString(), funcOp);
 229             }
 230         }
 231         boolean prevCodeReflectionEnabled = codeReflectionEnabled;
 232         try {
 233             codeReflectionEnabled = isReflectable;
 234             super.visitMethodDef(tree);
 235         } finally {
 236             codeReflectionEnabled = prevCodeReflectionEnabled;
 237         }
 238     }
 239 
 240     @Override
 241     public void visitModuleDef(JCModuleDecl that) {
 242         // do nothing
 243     }
 244 
 245     @Override
 246     public void visitClassDef(JCClassDecl tree) {
 247         ListBuffer<JCTree> prevOpMethodDecls = opMethodDecls;
 248         SequencedMap<String, Op> prevOps = ops;
 249         Symbol.ClassSymbol prevClassSym = currentClassSym;
 250         Symbol.ClassSymbol prevCodeModelsClassSym = codeModelsClassSym;
 251         int prevLambdaCount = lambdaCount;
 252         JavaFileObject prev = log.useSource(tree.sym.sourcefile);
 253         computeCapturesIfNeeded(tree);
 254         try {
 255             lambdaCount = 0;
 256             currentClassSym = tree.sym;
 257             opMethodDecls = new ListBuffer<>();
 258             codeModelsClassSym = new ClassSymbol(0, 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, PARAMETER, EXCEPTION_PARAMETER -> {
 845                             Value varOp = varOpValue(sym);
 846                             append(CoreOp.varStore(varOp, result));
 847                         }
 848                         case FIELD -> {
 849                             FieldRef fd = symbolToFieldRef(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 = symbolToFieldRef(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, 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 = symbolToFieldRef(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 = symbolToFieldRef(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 = symbolToFieldRef(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 = symbolToFieldRef(sym, qualifierTarget.hasTag(NONE) ?
1100                                     tree.selected.type : qualifierTarget);
1101                             CodeType resultType = typeToCodeType(types.memberType(tree.selected.type, sym));
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 = symbolToMethodRef(sym, symbolSiteType(sym));
1152                     Value res = append(JavaOp.invoke(ik, tree.varargsElement != null,
1153                             typeToCodeType(meth.type.getReturnType()), mr, args));
1154                     if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1155                         result = res;
1156                     }
1157                     break;
1158                 }
1159                 case SELECT: {
1160                     JCFieldAccess access = (JCFieldAccess) meth;
1161 
1162                     Type qualifierTarget = qualifierTarget(access);
1163                     Value receiver = toValue(access.selected, qualifierTarget);
1164 
1165                     Symbol sym = access.sym;
1166                     List<Value> args = new ArrayList<>();
1167                     JavaOp.InvokeOp.InvokeKind ik;
1168                     if (!sym.isStatic()) {
1169                         args.add(receiver);
1170                         // @@@ expr.super(...) for inner class super constructor calls
1171                         ik = switch (access.selected) {
1172                             case JCIdent i when i.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1173                             case JCFieldAccess fa when fa.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1174                             default -> JavaOp.InvokeOp.InvokeKind.INSTANCE;
1175                         };
1176                     } else {
1177                         ik = JavaOp.InvokeOp.InvokeKind.STATIC;
1178                     }
1179 
1180                     args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
1181 
1182                     MethodRef mr = symbolToMethodRef(sym, qualifierTarget.hasTag(NONE) ?
1183                             access.selected.type : qualifierTarget);
1184                     JavaType returnType = typeToCodeType(meth.type.getReturnType());
1185                     JavaOp.InvokeOp iop = JavaOp.invoke(ik, tree.varargsElement != null,
1186                             returnType, mr, args);
1187                     Value res = append(iop);
1188                     if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1189                         result = res;
1190                     }
1191                     break;
1192                 }
1193                 default:
1194                     throw unreachable();
1195             }
1196         }
1197 
1198         List<Value> scanMethodArguments(List<JCExpression> args, Type methodType, Type varargsElement) {
1199             ListBuffer<Value> argValues = new ListBuffer<>();
1200             com.sun.tools.javac.util.List<Type> targetTypes = methodType.getParameterTypes();
1201             if (varargsElement != null) {
1202                 targetTypes = targetTypes.reverse().tail;
1203                 for (int i = 0 ; i < args.size() - (methodType.getParameterTypes().size() - 1) ; i++) {
1204                     targetTypes = targetTypes.prepend(varargsElement);
1205                 }
1206                 targetTypes = targetTypes.reverse();
1207             }
1208 
1209             for (JCTree.JCExpression arg : args) {
1210                 argValues.add(toValue(arg, targetTypes.head));
1211                 targetTypes = targetTypes.tail;
1212             }
1213             return argValues.toList();
1214         }
1215 
1216         @Override
1217         public void visitReference(JCTree.JCMemberReference tree) {
1218             MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
1219             JCVariableDecl recv = memberReferenceToLambda.receiverVar();
1220             if (recv != null) {
1221                 scan(recv);
1222             }
1223             scan(memberReferenceToLambda.lambda());
1224         }
1225 
1226         Type qualifierTarget(JCFieldAccess tree) {
1227             Type selectedType = types.skipTypeVars(tree.selected.type, true);
1228             return selectedType.isCompound() ?
1229                     tree.sym.owner.type :
1230                     Type.noType;
1231         }
1232 
1233         @Override
1234         public void visitTypeCast(JCTree.JCTypeCast tree) {
1235             Value v = toValue(tree.expr);
1236 
1237             Type expressionType = tree.expr.type;
1238             Type type = tree.type;
1239             if (expressionType.isPrimitive() && type.isPrimitive()) {
1240                 if (expressionType.equals(type)) {
1241                     // Redundant cast
1242                     result = v;
1243                 } else {
1244                     result = append(JavaOp.conv(typeToCodeType(type), v));
1245                 }
1246             } else if (expressionType.isPrimitive() || type.isPrimitive()) {
1247                 result = convert(v, tree.type);
1248             } else if (!expressionType.hasTag(BOT) &&
1249                     types.isAssignable(expressionType, type)) {
1250                 // Redundant cast
1251                 result = v;
1252             } else {
1253                 // Reference cast
1254                 JavaType jt = typeToCodeType(types.erasure(type));
1255                 result = append(JavaOp.cast(typeToCodeType(type), jt, v));
1256             }
1257         }
1258 
1259         @Override
1260         public void visitTypeTest(JCTree.JCInstanceOf tree) {
1261             Value target = toValue(tree.expr);
1262 
1263             if (tree.pattern.getTag() != Tag.IDENT) {
1264                 result = scanPattern(tree.getPattern(), target);
1265             } else {
1266                 result = append(JavaOp.instanceOf(typeToCodeType(tree.pattern.type), target));
1267             }
1268         }
1269 
1270         Body.Builder scanPatternAsBody(JCTree.JCPattern pattern, Value target) {
1271             pushBody(pattern, CoreType.functionType(JavaType.BOOLEAN));
1272             Value localTarget = boxIfNeeded(target);
1273             Value patVal = scanPattern(pattern, localTarget);
1274             append(CoreOp.core_yield(patVal));
1275             Body.Builder patternBody = stack.body;
1276             popBody();
1277             return patternBody;
1278         }
1279 
1280         Value scanPattern(JCTree.JCPattern pattern, Value target) {
1281             // Type of pattern
1282             JavaType patternType;
1283             if (pattern instanceof JCTree.JCBindingPattern p) {
1284                 patternType = JavaOp.Pattern.bindingType(typeToCodeType(p.type));
1285             } else if (pattern instanceof JCTree.JCRecordPattern p) {
1286                 patternType = JavaOp.Pattern.recordType(typeToCodeType(p.record.type));
1287             } else {
1288                 throw unreachable(); // toplevel patterns are type test/record
1289             }
1290 
1291             // Push pattern body
1292             pushBody(pattern, CoreType.functionType(patternType));
1293 
1294             // @@@ Assumes just pattern nodes, likely will change when method patterns are supported
1295             //     that have expressions for any arguments (which perhaps in turn may have pattern expressions)
1296             List<JCVariableDecl> variables = new ArrayList<>();
1297             class PatternScanner extends FilterScanner {
1298 
1299                 private Value result;
1300 
1301                 public PatternScanner() {
1302                     super(Set.of(Tag.BINDINGPATTERN, Tag.RECORDPATTERN, Tag.ANYPATTERN));
1303                 }
1304 
1305                 @Override
1306                 public void visitBindingPattern(JCTree.JCBindingPattern binding) {
1307                     JCVariableDecl var = binding.var;
1308                     variables.add(var);
1309                     boolean unnamedPatternVariable = var.name.isEmpty();
1310                     String bindingName = unnamedPatternVariable ? null : var.name.toString();
1311                     result = append(JavaOp.typePattern(typeToCodeType(var.type), bindingName));
1312                 }
1313 
1314                 @Override
1315                 public void visitRecordPattern(JCTree.JCRecordPattern record) {
1316                     // @@@ Is always Identifier to record?
1317                     // scan(record.deconstructor);
1318 
1319                     List<Value> nestedValues = new ArrayList<>();
1320                     for (JCTree.JCPattern jcPattern : record.nested) {
1321                         // @@@ when we support ANYPATTERN, we must add result of toValue only if it's non-null
1322                         // because passing null to recordPattern methods will cause an error
1323                         nestedValues.add(toValue(jcPattern));
1324                     }
1325 
1326                     result = append(JavaOp.recordPattern(symbolToRecordTypeRef(record.record), nestedValues));
1327                 }
1328 
1329                 @Override
1330                 public void visitAnyPattern(JCTree.JCAnyPattern anyPattern) {
1331                     result = append(JavaOp.matchAllPattern());
1332                 }
1333 
1334                 Value toValue(JCTree tree) {
1335                     result = null;
1336                     scan(tree);
1337                     return result;
1338                 }
1339             }
1340             // Scan pattern
1341             Value patternValue = new PatternScanner().toValue(pattern);
1342             append(CoreOp.core_yield(patternValue));
1343             Body.Builder patternBody = stack.body;
1344 
1345             // Pop body
1346             popBody();
1347 
1348             // Find nearest ancestor body stack element associated with a statement tree
1349             // @@@ Strengthen check of tree?
1350             BodyStack _variablesStack = stack;
1351             while (!(_variablesStack.tree instanceof JCTree.JCStatement)) {
1352                 _variablesStack = _variablesStack.parent;
1353             }
1354             BodyStack variablesStack = _variablesStack;
1355 
1356             // Create pattern var ops for pattern variables using the
1357             // builder associated with the nearest statement tree
1358             BodyStack previous = stack;
1359             // Temporarily position the stack to where the pattern variables are to be declared
1360             stack = variablesStack;
1361             try {
1362                 for (JCVariableDecl jcVar : variables) {
1363                     // @@@ use uninitialized variable
1364                     Value defaultValue = append(defaultValue(jcVar.type));
1365                     Value init = convert(defaultValue, jcVar.type);
1366                     Op.Result op = append(CoreOp.var(jcVar.name.toString(), typeToCodeType(jcVar.type), init));
1367                     stack.localToOp.put(jcVar.sym, op);
1368                 }
1369             } finally {
1370                 stack = previous;
1371             }
1372 
1373             // Create pattern descriptor
1374             List<JavaType> patternDescParams = variables.stream().map(var -> typeToCodeType(var.type)).toList();
1375             FunctionType matchFuncType = CoreType.functionType(JavaType.VOID, patternDescParams);
1376 
1377             // Create the match body, assigning pattern values to pattern variables
1378             Body.Builder matchBody = Body.Builder.of(patternBody.connectedAncestorBody(), matchFuncType);
1379             Block.Builder matchBuilder = matchBody.entryBlock();
1380             for (int i = 0; i < variables.size(); i++) {
1381                 Value v = matchBuilder.parameters().get(i);
1382                 Value var = variablesStack.localToOp.get(variables.get(i).sym);
1383                 matchBuilder.add(CoreOp.varStore(var, v));
1384             }
1385             matchBuilder.add(CoreOp.core_yield());
1386 
1387             // Create the match operation
1388             return append(JavaOp.match(target, patternBody, matchBody));
1389         }
1390 
1391         @Override
1392         public void visitNewClass(JCTree.JCNewClass tree) {
1393             if (tree.def != null) {
1394                 scan(tree.def);
1395             }
1396 
1397             List<CodeType> argtypes = new ArrayList<>();
1398             Type type = tree.type;
1399             List<Value> args = new ArrayList<>();
1400             if (type.tsym.hasOuterInstance()) {
1401                 // Obtain outer value for inner class, and add as first argument
1402                 JCTree.JCExpression encl = tree.encl;
1403                 Value outerInstance;
1404                 if (encl == null) {
1405                     outerInstance = thisValue();
1406                 } else {
1407                     outerInstance = toValue(tree.encl);
1408                 }
1409                 args.add(outerInstance);
1410                 JavaType outerType = typeToCodeType(tree.constructor.innermostAccessibleEnclosingClass().erasure(types));
1411                 argtypes.add(outerType);
1412             }
1413 
1414             MethodRef methodRef = symbolToMethodRef(tree.constructor);
1415             argtypes.addAll(methodRef.signature().parameterTypes());
1416             args.addAll(scanMethodArguments(tree.args, tree.constructorType, tree.varargsElement));
1417 
1418             if (tree.type.tsym.isDirectlyOrIndirectlyLocal()) {
1419                 for (Symbol c : localCaptures.get(tree.type.tsym)) {
1420                     args.add(loadVar(c));
1421                     argtypes.add(symbolToErasedDesc(c));
1422                 }
1423             }
1424 
1425             // Create erased method type reference for constructor, where
1426             // the return type declares the class to instantiate
1427             // We need to manually construct the constructor reference,
1428             // as the signature of the constructor symbol is not augmented
1429             // with enclosing this and captured params.
1430             FunctionType constructorSignature = CoreType.functionType(
1431                     symbolToErasedDesc(tree.constructor.owner),
1432                     argtypes);
1433             MethodRef constructorRef = MethodRef.constructor(constructorSignature);
1434 
1435             result = append(JavaOp.new_(tree.varargsElement != null, typeToCodeType(type), constructorRef, args));
1436         }
1437 
1438         @Override
1439         public void visitNewArray(JCTree.JCNewArray tree) {
1440             if (tree.elems != null) {
1441                 int length = tree.elems.size();
1442                 Op.Result a = append(JavaOp.newArray(
1443                         typeToCodeType(tree.type),
1444                         append(CoreOp.constant(JavaType.INT, length))));
1445                 int i = 0;
1446                 for (JCExpression elem : tree.elems) {
1447                     Value element = toValue(elem, types.elemtype(tree.type));
1448                     append(JavaOp.arrayStoreOp(
1449                             a,
1450                             append(CoreOp.constant(JavaType.INT, i)),
1451                             element));
1452                     i++;
1453                 }
1454 
1455                 result = a;
1456             } else {
1457                 List<Value> indexes = new ArrayList<>();
1458                 for (JCTree.JCExpression dim : tree.dims) {
1459                     indexes.add(toValue(dim));
1460                 }
1461 
1462                 JavaType arrayType = typeToCodeType(tree.type);
1463                 MethodRef constructorRef = MethodRef.constructor(arrayType,
1464                         indexes.stream().map(Value::type).toList());
1465                 result = append(JavaOp.new_(constructorRef, indexes));
1466             }
1467         }
1468 
1469         @Override
1470         public void visitLambda(JCTree.JCLambda tree) {
1471             final FunctionType lambdaType = typeToFunctionType(types.findDescriptorType(tree.target));
1472 
1473             // Push quoted body
1474             // We can either be explicitly quoted or a structural quoted expression
1475             // within some larger reflected code
1476 
1477             // a reflectable lambda is going to have its model wrapped in QuotedOp
1478             // only when we are producing the model of the lambda, thus the condition (isReflectable ...)
1479             // also, a lambda contained in a reflectable lambda, will not have its model wrapped in QuotedOp,
1480             // thus the condition (... body == tree)
1481             boolean toQuote = (isLambdaReflectable && this.tree == tree);
1482             if (toQuote) {
1483                 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1484             }
1485 
1486             // Push lambda body
1487             pushBody(tree.body, lambdaType);
1488 
1489             // Map lambda parameters to varOp values
1490             for (int i = 0; i < tree.params.size(); i++) {
1491                 JCVariableDecl p = tree.params.get(i);
1492                 Op.Result paramOp = append(CoreOp.var(
1493                         p.name.toString(),
1494                         stack.block.parameters().get(i)));
1495                 stack.localToOp.put(p.sym, paramOp);
1496             }
1497 
1498             // Scan the lambda body
1499             Type lambdaReturnType = tree.getDescriptorType(types).getReturnType();
1500             if (tree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) {
1501                 Value exprVal = toValue(((JCExpression) tree.body), lambdaReturnType);
1502                 if (!lambdaReturnType.hasTag(TypeTag.VOID)) {
1503                     append(CoreOp.return_(exprVal));
1504                 } else {
1505                     appendTerminating(CoreOp::return_);
1506                 }
1507             } else {
1508                 Type prevBodyTarget = bodyTarget;
1509                 try {
1510                     bodyTarget = lambdaReturnType;
1511                     toValue(((JCTree.JCStatement) tree.body));
1512                     appendReturnOrUnreachable(tree.body);
1513                 } finally {
1514                     bodyTarget = prevBodyTarget;
1515                 }
1516             }
1517 
1518             // Get the functional interface type
1519             JavaType fiType = typeToCodeType(tree.target);
1520             // build functional lambda
1521             Op lambdaOp = JavaOp.lambda(fiType, stack.body, true);
1522 
1523             // Pop lambda body
1524             popBody();
1525 
1526             Value lambdaResult;
1527             if (toQuote) {
1528                 lambdaResult = append(lambdaOp, generateLocation(tree, true));
1529             } else {
1530                 lambdaResult = append(lambdaOp);
1531             }
1532 
1533             if (toQuote) {
1534                 append(CoreOp.core_yield(lambdaResult));
1535                 CoreOp.QuotedOp quotedOp = CoreOp.quoted(stack.body);
1536 
1537                 // Pop quoted body
1538                 popBody();
1539 
1540                 lambdaResult = append(quotedOp);
1541             }
1542 
1543             result = lambdaResult;
1544         }
1545 
1546         @Override
1547         public void visitIf(JCTree.JCIf tree) {
1548             List<Body.Builder> bodies = new ArrayList<>();
1549 
1550             while (tree != null) {
1551                 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1552 
1553                 // Push if condition
1554                 pushBody(cond,
1555                         CoreType.functionType(JavaType.BOOLEAN));
1556                 Value last = toValue(cond, syms.booleanType);
1557                 // Yield the boolean result of the condition
1558                 append(CoreOp.core_yield(last));
1559                 bodies.add(stack.body);
1560 
1561                 // Pop if condition
1562                 popBody();
1563 
1564                 // Push if body
1565                 pushBody(tree.thenpart, CoreType.FUNCTION_TYPE_VOID);
1566 
1567                 scan(tree.thenpart);
1568                 appendTerminating(CoreOp::core_yield);
1569                 bodies.add(stack.body);
1570 
1571                 // Pop if body
1572                 popBody();
1573 
1574                 JCTree.JCStatement elsepart = tree.elsepart;
1575                 if (elsepart == null) {
1576                     tree = null;
1577                 } else if (elsepart.getTag() == Tag.IF) {
1578                     tree = (JCTree.JCIf) elsepart;
1579                 } else {
1580                     // Push else body
1581                     pushBody(elsepart, CoreType.FUNCTION_TYPE_VOID);
1582 
1583                     scan(elsepart);
1584                     appendTerminating(CoreOp::core_yield);
1585                     bodies.add(stack.body);
1586 
1587                     // Pop else body
1588                     popBody();
1589 
1590                     tree = null;
1591                 }
1592             }
1593 
1594             append(JavaOp.if_(bodies));
1595             result = null;
1596         }
1597 
1598         @Override
1599         public void visitSwitchExpression(JCTree.JCSwitchExpression tree) {
1600             Value target = toValue(tree.selector);
1601 
1602             Type switchType = adaptBottom(tree.type);
1603             FunctionType caseBodyType = CoreType.functionType(typeToCodeType(switchType));
1604 
1605             SwitchBodyInfo bodyInfo = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, caseBodyType,
1606                     !tree.hasUnconditionalPattern);
1607 
1608             result = append(JavaOp.switchExpression(caseBodyType.returnType(), target, bodyInfo.handlesNull, bodyInfo.bodies));
1609         }
1610 
1611         @Override
1612         public void visitSwitch(JCTree.JCSwitch tree) {
1613             Value target = toValue(tree.selector);
1614 
1615             FunctionType actionType = CoreType.FUNCTION_TYPE_VOID;
1616 
1617             SwitchBodyInfo bodyInfo = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, actionType,
1618                     tree.patternSwitch && !tree.hasUnconditionalPattern);
1619 
1620             result = append(JavaOp.switchStatement(target, bodyInfo.handlesNull, bodyInfo.bodies));
1621         }
1622 
1623         record SwitchBodyInfo(boolean handlesNull, List<Body.Builder> bodies) { }
1624 
1625         private SwitchBodyInfo visitSwitchStatAndExpr(JCTree tree, JCExpression selector, Value target,
1626                                                           List<JCTree.JCCase> cases, FunctionType caseBodyType,
1627                                                           boolean isDefaultCaseNeeded) {
1628             List<Body.Builder> bodies = new ArrayList<>();
1629             boolean hasDefaultCase = false;
1630             boolean handlesNull = false;
1631 
1632             for (JCTree.JCCase c : cases) {
1633                 if (handlesNull(c)) {
1634                     handlesNull = true;
1635                 }
1636                 if (isDefault(c)) {
1637                     hasDefaultCase = true;
1638                 }
1639                 Body.Builder caseLabel = visitCaseLabel(tree, target, c);
1640                 Body.Builder caseBody = visitCaseBody(tree, c, caseBodyType, cases.getLast() == c);
1641                 bodies.add(caseLabel);
1642                 bodies.add(caseBody);
1643             }
1644 
1645             if (!hasDefaultCase && isDefaultCaseNeeded) {
1646                 // label
1647                 pushBody(tree, CoreType.functionType(JavaType.BOOLEAN));
1648                 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1649                 bodies.add(stack.body);
1650                 popBody();
1651 
1652                 // body
1653                 pushBody(tree, caseBodyType);
1654                 append(JavaOp.throw_(
1655                         append(JavaOp.new_(MethodRef.constructor(MatchException.class)))
1656                 ));
1657                 bodies.add(stack.body);
1658                 popBody();
1659             }
1660 
1661             return new SwitchBodyInfo(handlesNull, bodies);
1662         }
1663 
1664         boolean handlesNull(JCTree.JCCase caseTree) {
1665             return caseTree.labels.stream().anyMatch(l -> l instanceof JCConstantCaseLabel constLabel &&
1666                     TreeInfo.isNull(constLabel.expr));
1667         }
1668 
1669         boolean isDefault(JCTree.JCCase caseTree) {
1670             return caseTree.labels.stream().anyMatch(l -> l instanceof JCDefaultCaseLabel);
1671         }
1672 
1673         private Value processConstantLabel(Value target, JCTree.JCConstantCaseLabel label) {
1674             if (target.type().equals(JavaType.J_L_STRING)) {
1675                 return append(JavaOp.invoke(
1676                         MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
1677                         target, toValue(label.expr)));
1678             } else {
1679                 // target is primitive wrapper, primitive or enum
1680                 // if target of type Character, Byte, Short or Integer, unbox it
1681                 if (target.type().equals(JavaType.J_L_CHARACTER) || target.type().equals(JavaType.J_L_BYTE) ||
1682                         target.type().equals(JavaType.J_L_SHORT) || target.type().equals(JavaType.J_L_INTEGER)) {
1683                     PrimitiveType pt = ((ClassType) target.type()).unbox().get();
1684                     target = convert(target, codeTypeToType(pt));
1685                 }
1686                 Value expr = toValue(label.expr);
1687                 // conversion may be needed for primitive, e.g. label (byte) 1 and selector of type int
1688                 expr = convert(expr, codeTypeToType(target.type()));
1689                 return append(JavaOp.eq(target, expr));
1690             }
1691         }
1692 
1693         private Body.Builder visitCaseLabel(JCTree tree, Value target, JCTree.JCCase c) {
1694             Body.Builder body;
1695             FunctionType caseLabelType = CoreType.functionType(JavaType.BOOLEAN, target.type());
1696 
1697             JCTree.JCCaseLabel headCl = c.labels.head;
1698             if (isDefault(c)) {
1699                 // @@@ Do we need to model the default label body?
1700                 pushBody(headCl, CoreType.functionType(JavaType.BOOLEAN));
1701 
1702                 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1703                 body = stack.body;
1704 
1705                 // Pop label
1706                 popBody();
1707             } else if (headCl instanceof JCTree.JCPatternCaseLabel pcl) {
1708                 boolean isMultiLabel = c.labels.size() > 1;
1709 
1710                 pushBody(pcl, caseLabelType);
1711 
1712                 Value localTarget = stack.block.parameters().get(0);
1713                 final Value localResult;
1714                 if (c.guard != null) {
1715                     List<Body.Builder> clBodies = new ArrayList<>();
1716 
1717                     if (isMultiLabel) {
1718                         // push a body for or-ing the patterns
1719                         pushBody(pcl, CoreType.functionType(JavaType.BOOLEAN));
1720                     }
1721 
1722                     for (JCCaseLabel l : c.labels) {
1723                         JCTree.JCPatternCaseLabel pat = (JCTree.JCPatternCaseLabel)l;
1724                         clBodies.add(scanPatternAsBody(pat.pat, localTarget));
1725                     }
1726 
1727                     if (isMultiLabel) {
1728                         // or the pattern bodies and replace clBodies with a single body
1729                         Value patternOrResult = append(JavaOp.conditionalOr(clBodies));
1730                         append(CoreOp.core_yield(patternOrResult));
1731                         clBodies.clear();
1732                         clBodies.add(stack.body);
1733                         popBody();
1734                     }
1735 
1736                     pushBody(c.guard, CoreType.functionType(JavaType.BOOLEAN));
1737                     append(CoreOp.core_yield(toValue(c.guard, syms.booleanType)));
1738                     clBodies.add(stack.body);
1739                     popBody();
1740 
1741                     localResult = append(JavaOp.conditionalAnd(clBodies));
1742                 } else if (isMultiLabel) {
1743                     List<Body.Builder> clBodies = new ArrayList<>();
1744                     for (JCCaseLabel l : c.labels) {
1745                         JCTree.JCPatternCaseLabel pat = (JCTree.JCPatternCaseLabel)l;
1746                         clBodies.add(scanPatternAsBody(pat.pat, localTarget));
1747                     }
1748                     localResult = append(JavaOp.conditionalOr(clBodies));
1749                 } else {
1750                     localTarget = boxIfNeeded(localTarget);
1751                     localResult = scanPattern(pcl.pat, localTarget);
1752                 }
1753                 // Yield the boolean result of the condition
1754                 append(CoreOp.core_yield(localResult));
1755                 body = stack.body;
1756 
1757                 // Pop label
1758                 popBody();
1759             } else if (headCl instanceof JCTree.JCConstantCaseLabel ccl) {
1760                 pushBody(headCl, caseLabelType);
1761 
1762                 Value localTarget = stack.block.parameters().get(0);
1763                 final Value localResult;
1764                 if (c.labels.size() == 1) {
1765                     localResult = processConstantLabel(localTarget, ccl);
1766                 } else {
1767                     List<Body.Builder> clBodies = new ArrayList<>();
1768                     for (JCTree.JCCaseLabel cl : c.labels) {
1769                         ccl = (JCTree.JCConstantCaseLabel) cl;
1770                         pushBody(ccl, CoreType.functionType(JavaType.BOOLEAN));
1771 
1772                         final Value labelResult = processConstantLabel(localTarget, ccl);
1773 
1774                         append(CoreOp.core_yield(labelResult));
1775                         clBodies.add(stack.body);
1776 
1777                         // Pop label
1778                         popBody();
1779                     }
1780 
1781                     localResult = append(JavaOp.conditionalOr(clBodies));
1782                 }
1783 
1784                 append(CoreOp.core_yield(localResult));
1785                 body = stack.body;
1786 
1787                 // Pop labels
1788                 popBody();
1789             } else {
1790                 throw unreachable();
1791             }
1792 
1793             return body;
1794         }
1795 
1796         private Body.Builder visitCaseBody(JCTree tree, JCTree.JCCase c, FunctionType caseBodyType, boolean isLastCase) {
1797             Body.Builder body = null;
1798             Type yieldType = tree.type != null ? adaptBottom(tree.type) : Type.noType;
1799 
1800             JCTree.JCCaseLabel headCl = c.labels.head;
1801             switch (c.caseKind) {
1802                 case RULE -> {
1803                     pushBody(c.body, caseBodyType);
1804 
1805                     if (c.body instanceof JCTree.JCExpression e) {
1806                         Value bodyVal = toValue(e, yieldType);
1807                         append(CoreOp.core_yield(bodyVal));
1808                     } else if (c.body instanceof JCTree.JCStatement s){ // this includes Block
1809                         // Otherwise there is a yield statement
1810                         Type prevBodyTarget = bodyTarget;
1811                         try {
1812                             bodyTarget = yieldType;
1813                             toValue(s);
1814                         } finally {
1815                             bodyTarget = prevBodyTarget;
1816                         }
1817                         appendTerminating(c.completesNormally ? CoreOp::core_yield : CoreOp::unreachable);
1818                     }
1819                     body = stack.body;
1820 
1821                     // Pop block
1822                     popBody();
1823                 }
1824                 case STATEMENT -> {
1825                     // @@@ Avoid nesting for a single block? Goes against "say what you see"
1826                     // boolean oneBlock = c.stats.size() == 1 && c.stats.head instanceof JCBlock;
1827                     pushBody(c, caseBodyType);
1828 
1829                     scan(c.stats);
1830 
1831                     appendTerminating(c.completesNormally ?
1832                             isLastCase ? CoreOp::core_yield : JavaOp::switchFallthroughOp
1833                             : CoreOp::unreachable);
1834 
1835                     body = stack.body;
1836 
1837                     // Pop block
1838                     popBody();
1839                 }
1840             }
1841             return body;
1842         }
1843 
1844         @Override
1845         public void visitYield(JCTree.JCYield tree) {
1846             Value retVal = toValue(tree.value, bodyTarget);
1847             result = append(JavaOp.java_yield(retVal));
1848         }
1849 
1850         @Override
1851         public void visitWhileLoop(JCTree.JCWhileLoop tree) {
1852             // @@@ Patterns
1853             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1854 
1855             // Push while condition
1856             pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1857             Value last = toValue(cond, syms.booleanType);
1858             // Yield the boolean result of the condition
1859             append(CoreOp.core_yield(last));
1860             Body.Builder condition = stack.body;
1861 
1862             // Pop while condition
1863             popBody();
1864 
1865             // Push while body
1866             pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1867             scan(tree.body);
1868             appendTerminating(JavaOp::continue_);
1869             Body.Builder body = stack.body;
1870 
1871             // Pop while body
1872             popBody();
1873 
1874             append(JavaOp.while_(condition, body));
1875             result = null;
1876         }
1877 
1878         @Override
1879         public void visitDoLoop(JCTree.JCDoWhileLoop tree) {
1880             // @@@ Patterns
1881             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1882 
1883             // Push while body
1884             pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1885             scan(tree.body);
1886             appendTerminating(JavaOp::continue_);
1887             Body.Builder body = stack.body;
1888 
1889             // Pop while body
1890             popBody();
1891 
1892             // Push while condition
1893             pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1894             Value last = toValue(cond, syms.booleanType);
1895             // Yield the boolean result of the condition
1896             append(CoreOp.core_yield(last));
1897             Body.Builder condition = stack.body;
1898 
1899             // Pop while condition
1900             popBody();
1901 
1902             append(JavaOp.doWhile(body, condition));
1903             result = null;
1904         }
1905 
1906         @Override
1907         public void visitForeachLoop(JCTree.JCEnhancedForLoop tree) {
1908             // Push expression
1909             pushBody(tree.expr, CoreType.functionType(typeToCodeType(tree.expr.type)));
1910             Value last = toValue(tree.expr);
1911             // Yield the Iterable result of the expression
1912             append(CoreOp.core_yield(last));
1913             Body.Builder expression = stack.body;
1914 
1915             // Pop expression
1916             popBody();
1917 
1918             JCVariableDecl var = tree.getVariable();
1919             VarType varEType = CoreType.varType(typeToCodeType(var.type));
1920 
1921             // Push init
1922             // @@@ When lhs assignment is a pattern we embed the pattern match into the init body and
1923             // return the bound variables
1924             Type exprType = types.cvarUpperBound(tree.expr.type);
1925             Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
1926             if (elemtype == null) {
1927                 Type iterableType = types.asSuper(tree.expr.type, syms.iterableType.tsym);
1928                 com.sun.tools.javac.util.List<Type> iterableParams = iterableType.allparams();
1929                 elemtype = iterableParams.isEmpty()
1930                         ? syms.objectType
1931                         : types.wildUpperBound(iterableParams.head);
1932             }
1933             pushBody(var, CoreType.functionType(varEType, typeToCodeType(elemtype)));
1934             var initVarExpr = convert(stack.block.parameters().get(0), var.type);
1935             Op.Result varEResult = append(CoreOp.var(var.name.toString(), initVarExpr));
1936             append(CoreOp.core_yield(varEResult));
1937             Body.Builder init = stack.body;
1938             // Pop init
1939             popBody();
1940 
1941             // Push body
1942             pushBody(tree.body, CoreType.functionType(JavaType.VOID, varEType));
1943             stack.localToOp.put(var.sym, stack.block.parameters().get(0));
1944 
1945             scan(tree.body);
1946             appendTerminating(JavaOp::continue_);
1947             Body.Builder body = stack.body;
1948             // Pop body
1949             popBody();
1950 
1951             append(JavaOp.enhancedFor(expression, init, body));
1952             result = null;
1953         }
1954 
1955         @Override
1956         public void visitForLoop(JCTree.JCForLoop tree) {
1957             class VarDefScanner extends FilterScanner {
1958                 final List<JCVariableDecl> decls;
1959 
1960                 public VarDefScanner() {
1961                     super(Set.of(Tag.VARDEF));
1962                     this.decls = new ArrayList<>();
1963                 }
1964 
1965                 @Override
1966                 public void visitVarDef(JCVariableDecl tree) {
1967                     decls.add(tree);
1968                 }
1969 
1970                 void mapVarsToBlockArguments() {
1971                     for (int i = 0; i < decls.size(); i++) {
1972                         stack.localToOp.put(decls.get(i).sym, stack.block.parameters().get(i));
1973                     }
1974                 }
1975 
1976                 List<VarType> varTypes() {
1977                     return decls.stream()
1978                             .map(t -> CoreType.varType(typeToCodeType(t.type)))
1979                             .toList();
1980                 }
1981 
1982                 List<Value> varValues() {
1983                     return decls.stream()
1984                             .map(t -> stack.localToOp.get(t.sym))
1985                             .toList();
1986                 }
1987             }
1988 
1989             // Scan local variable declarations
1990             VarDefScanner vds = new VarDefScanner();
1991             vds.scan(tree.init);
1992             List<VarType> varTypes = vds.varTypes();
1993 
1994             // Push init
1995             if (varTypes.size() > 1) {
1996                 pushBody(null, CoreType.functionType(CoreType.tupleType(varTypes)));
1997                 scan(tree.init);
1998 
1999                 // Capture all local variable declarations in tuple
2000                 append(CoreOp.core_yield(append(CoreOp.tuple(vds.varValues()))));
2001             } else if (varTypes.size() == 1) {
2002                 pushBody(null, CoreType.functionType(varTypes.get(0)));
2003                 scan(tree.init);
2004 
2005                 append(CoreOp.core_yield(vds.varValues().get(0)));
2006             } else {
2007                 pushBody(null, CoreType.FUNCTION_TYPE_VOID);
2008                 scan(tree.init);
2009 
2010                 append(CoreOp.core_yield());
2011             }
2012             Body.Builder init = stack.body;
2013 
2014             // Pop init
2015             popBody();
2016 
2017             // Push cond
2018             pushBody(tree.cond, CoreType.functionType(JavaType.BOOLEAN, varTypes));
2019             if (tree.cond != null) {
2020                 vds.mapVarsToBlockArguments();
2021 
2022                 Value last = toValue(tree.cond, syms.booleanType);
2023                 // Yield the boolean result of the condition
2024                 append(CoreOp.core_yield(last));
2025             } else {
2026                 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
2027             }
2028             Body.Builder cond = stack.body;
2029 
2030             // Pop cond
2031             popBody();
2032 
2033             // Push update
2034             // @@@ tree.step is a List<JCStatement>
2035             pushBody(null, CoreType.functionType(JavaType.VOID, varTypes));
2036             if (!tree.step.isEmpty()) {
2037                 vds.mapVarsToBlockArguments();
2038 
2039                 scan(tree.step);
2040             }
2041             append(CoreOp.core_yield());
2042             Body.Builder update = stack.body;
2043 
2044             // Pop update
2045             popBody();
2046 
2047             // Push body
2048             pushBody(tree.body, CoreType.functionType(JavaType.VOID, varTypes));
2049             if (tree.body != null) {
2050                 vds.mapVarsToBlockArguments();
2051 
2052                 scan(tree.body);
2053             }
2054             appendTerminating(JavaOp::continue_);
2055             Body.Builder body = stack.body;
2056 
2057             // Pop update
2058             popBody();
2059 
2060             append(JavaOp.for_(init, cond, update, body));
2061             result = null;
2062         }
2063 
2064         @Override
2065         public void visitConditional(JCTree.JCConditional tree) {
2066             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2067 
2068             // Push condition
2069             pushBody(cond,
2070                     CoreType.functionType(JavaType.BOOLEAN));
2071             Value condVal = toValue(cond, syms.booleanType);
2072             // Yield the boolean result of the condition
2073             append(CoreOp.core_yield(condVal));
2074             Body.Builder predicateBody = stack.body;
2075 
2076             // Pop condition
2077             popBody();
2078 
2079             JCTree.JCExpression truepart = TreeInfo.skipParens(tree.truepart);
2080 
2081             Type condType = adaptBottom(tree.type);
2082 
2083             // Push true body
2084             pushBody(truepart,
2085                     CoreType.functionType(typeToCodeType(condType)));
2086 
2087             Value trueVal = toValue(truepart, condType);
2088             // Yield the result
2089             append(CoreOp.core_yield(trueVal));
2090             Body.Builder trueBody = stack.body;
2091 
2092             // Pop true body
2093             popBody();
2094 
2095             JCTree.JCExpression falsepart = TreeInfo.skipParens(tree.falsepart);
2096 
2097             // Push false body
2098             pushBody(falsepart,
2099                     CoreType.functionType(typeToCodeType(condType)));
2100 
2101             Value falseVal = toValue(falsepart, condType);
2102             // Yield the result
2103             append(CoreOp.core_yield(falseVal));
2104             Body.Builder falseBody = stack.body;
2105 
2106             // Pop false body
2107             popBody();
2108 
2109             result = append(JavaOp.conditionalExpression(typeToCodeType(condType), predicateBody, trueBody, falseBody));
2110         }
2111 
2112         private Type condType(JCExpression tree, Type type) {
2113             if (type.hasTag(BOT)) {
2114                 return adaptBottom(tree.type);
2115             } else {
2116                 return type;
2117             }
2118         }
2119 
2120         private Type adaptBottom(Type type) {
2121             return type.hasTag(BOT) ?
2122                     (pt.hasTag(NONE) ? syms.objectType : pt) :
2123                     type;
2124         }
2125 
2126         @Override
2127         public void visitAssert(JCAssert tree) {
2128             // assert <cond:body1> [detail:body2]
2129 
2130             List<Body.Builder> bodies = new ArrayList<>();
2131             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2132 
2133             // Push condition
2134             pushBody(cond,
2135                     CoreType.functionType(JavaType.BOOLEAN));
2136             Value condVal = toValue(cond, syms.booleanType);
2137 
2138             // Yield the boolean result of the condition
2139             append(CoreOp.core_yield(condVal));
2140             bodies.add(stack.body);
2141 
2142             // Pop condition
2143             popBody();
2144 
2145             if (tree.detail != null) {
2146                 JCTree.JCExpression detail = TreeInfo.skipParens(tree.detail);
2147 
2148                 pushBody(detail,
2149                         CoreType.functionType(typeToCodeType(tree.detail.type)));
2150                 Value detailVal = toValue(detail);
2151 
2152                 append(CoreOp.core_yield(detailVal));
2153                 bodies.add(stack.body);
2154 
2155                 //Pop detail
2156                 popBody();
2157             }
2158 
2159             result = append(JavaOp.assert_(bodies));
2160 
2161         }
2162 
2163         @Override
2164         public void visitBlock(JCTree.JCBlock tree) {
2165             if (stack.tree == tree) {
2166                 // Block is associated with the visit of a parent structure
2167                 scan(tree.stats);
2168             } else {
2169                 // Otherwise, independent block structure
2170                 // Push block
2171                 pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2172                 scan(tree.stats);
2173                 appendTerminating(CoreOp::core_yield);
2174                 Body.Builder body = stack.body;
2175 
2176                 // Pop block
2177                 popBody();
2178 
2179                 append(JavaOp.block(body));
2180             }
2181             result = null;
2182         }
2183 
2184         @Override
2185         public void visitSynchronized(JCTree.JCSynchronized tree) {
2186             // Push expr
2187             pushBody(tree.lock, CoreType.functionType(typeToCodeType(tree.lock.type)));
2188             Value last = toValue(tree.lock);
2189             append(CoreOp.core_yield(last));
2190             Body.Builder expr = stack.body;
2191 
2192             // Pop expr
2193             popBody();
2194 
2195             // Push body block
2196             pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
2197             // Scan body block statements
2198             scan(tree.body.stats);
2199             appendTerminating(CoreOp::core_yield);
2200             Body.Builder blockBody = stack.body;
2201 
2202             // Pop body block
2203             popBody();
2204 
2205             append(JavaOp.synchronized_(expr, blockBody));
2206         }
2207 
2208         @Override
2209         public void visitLabelled(JCTree.JCLabeledStatement tree) {
2210             // Push block
2211             pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2212             // Create constant for label
2213             String labelName = tree.label.toString();
2214             Op.Result label = append(CoreOp.constant(JavaType.J_L_STRING, labelName));
2215             // Set label on body stack
2216             stack.setLabel(labelName, label);
2217             scan(tree.body);
2218             appendTerminating(CoreOp::core_yield);
2219             Body.Builder body = stack.body;
2220 
2221             // Pop block
2222             popBody();
2223 
2224             result = append(JavaOp.labeled(body));
2225         }
2226 
2227         @Override
2228         public void visitTry(JCTree.JCTry tree) {
2229             List<Symbol> rVariableDecls = new ArrayList<>();
2230             List<CodeType> rTypes = new ArrayList<>();
2231             List<Body.Builder> resources = new ArrayList<>();
2232             if (!tree.resources.isEmpty()) {
2233                 // Resources bodies return the resource variables/values in order of declaration
2234                 for (JCTree resource : tree.resources) {
2235                     CodeType rType;
2236                     if (resource instanceof JCVariableDecl vdecl) {
2237                         rType = CoreType.varType(typeToCodeType(vdecl.type));
2238                     } else {
2239                         rType = typeToCodeType(resource.type);
2240                     }
2241 
2242                     // Push resources body
2243                     pushBody(null, CoreType.functionType(rType, rTypes));
2244                     for (int i = 0; i < rVariableDecls.size(); i++) {
2245                         Symbol rVariableDecl = rVariableDecls.get(i);
2246                         if (rVariableDecl != null) {
2247                             stack.localToOp.put(rVariableDecl, stack.block.parameters().get(i));
2248                         }
2249                     }
2250 
2251                     if (resource instanceof JCTree.JCExpression e) {
2252                         append(CoreOp.core_yield(toValue(e)));
2253                     } else if (resource instanceof JCTree.JCStatement s) {
2254                         append(CoreOp.core_yield(toValue(s)));
2255                     }
2256 
2257                     resources.add(stack.body);
2258 
2259                     // Pop resources body
2260                     popBody();
2261 
2262                     // Null entries preserve positions for resource expressions, which have no variable declaration.
2263                     rVariableDecls.add(resource instanceof JCVariableDecl vdecl ? vdecl.sym : null);
2264                     rTypes.add(rType);
2265                 }
2266             }
2267 
2268             // Push body
2269             // Try body accepts the resource variables (in order of declaration).
2270             pushBody(tree.body, CoreType.functionType(JavaType.VOID, rTypes));
2271             for (int i = 0; i < rVariableDecls.size(); i++) {
2272                 stack.localToOp.put(rVariableDecls.get(i), stack.block.parameters().get(i));
2273             }
2274             scan(tree.body);
2275             appendTerminating(CoreOp::core_yield);
2276             Body.Builder body = stack.body;
2277 
2278             // Pop block
2279             popBody();
2280 
2281             List<CodeType> catchTypes = new ArrayList<>();
2282             List<Body.Builder> catchers = new ArrayList<>();
2283             for (JCTree.JCCatch catcher : tree.catchers) {
2284 
2285                 catchTypes.add(TreeInfo.isMultiCatch(catcher)
2286                         ? CoreType.tupleType(((JCTree.JCTypeUnion) catcher.param.vartype).alternatives.stream()
2287                                 .map(a -> typeToCodeType(a.type)).toList())
2288                         : typeToCodeType(catcher.param.vartype.type));
2289 
2290                 // Push body
2291                 pushBody(catcher.body, CoreType.functionType(JavaType.VOID, typeToCodeType(catcher.param.type)));
2292                 Op.Result exVariable = append(CoreOp.var(
2293                         catcher.param.name.toString(),
2294                         stack.block.parameters().get(0)));
2295                 stack.localToOp.put(catcher.param.sym, exVariable);
2296                 scan(catcher.body);
2297                 appendTerminating(CoreOp::core_yield);
2298                 catchers.add(stack.body);
2299 
2300                 // Pop block
2301                 popBody();
2302             }
2303 
2304             Body.Builder finalizer;
2305             if (tree.finalizer != null) {
2306                 // Push body
2307                 pushBody(tree.finalizer, CoreType.FUNCTION_TYPE_VOID);
2308                 scan(tree.finalizer);
2309                 appendTerminating(CoreOp::core_yield);
2310                 finalizer = stack.body;
2311 
2312                 // Pop block
2313                 popBody();
2314             }
2315             else {
2316                 finalizer = null;
2317             }
2318 
2319             result = append(JavaOp.try_(resources, body, catchTypes, catchers, finalizer));
2320         }
2321 
2322         @Override
2323         public void visitUnary(JCTree.JCUnary tree) {
2324             Tag tag = tree.getTag();
2325             switch (tag) {
2326                 case POSTINC, POSTDEC, PREINC, PREDEC -> {
2327                     // Capture applying rhs and operation
2328                     Function<Value, Value> scanRhs = (lhs) -> {
2329                         // arithmetic operators are all of kind (T, T)T
2330                         Type opType = tree.operator.type.getReturnType();
2331                         if (!opType.hasTag(INT) &&
2332                                 opType.getTag().isSubRangeOf(INT)) {
2333                             // unary ++/-- can use sub-int operator types,
2334                             // which doesn't make sense for the model
2335                             opType = syms.intType;
2336                         }
2337 
2338                         // We first convert LHS, then process RHS
2339                         // While JLS doesn't require this, javac generates bytecode this way
2340                         Value lhsConv = convert(lhs, opType);
2341                         Value one = append(numericOneValue(opType));
2342 
2343                         Value lhsPlusOne = (tag == Tag.PREINC || tag ==  Tag.POSTINC) ?
2344                             append(JavaOp.add(lhsConv, one)) :
2345                             append(JavaOp.sub(lhsConv, one));
2346                         lhsPlusOne = convert(lhsPlusOne, tree.type);
2347 
2348                         // Assign expression result
2349                         result = (tag == Tag.POSTINC || tag == Tag.POSTDEC) ?
2350                                 lhs : lhsPlusOne;
2351                         return lhsPlusOne;
2352                     };
2353 
2354                     applyCompoundAssign(tree.arg, scanRhs);
2355                 }
2356                 case NEG -> {
2357                     Value rhs = toValue(tree.arg, tree.type);
2358                     result = append(JavaOp.neg(rhs));
2359                 }
2360                 case NOT -> {
2361                     Value rhs = toValue(tree.arg, tree.type);
2362                     result = append(JavaOp.not(rhs));
2363                 }
2364                 case COMPL -> {
2365                     Value rhs = toValue(tree.arg, tree.type);
2366                     result = append(JavaOp.compl(rhs));
2367                 }
2368                 case POS -> {
2369                     // Result is value of the operand
2370                     result = toValue(tree.arg, tree.type);
2371                 }
2372                 default -> throw unreachable(); // NULLCHK not possible
2373             }
2374         }
2375 
2376         @Override
2377         public void visitBinary(JCBinary tree) {
2378             Tag tag = tree.getTag();
2379             if (tag == Tag.AND || tag == Tag.OR) {
2380                 // Logical operations
2381                 // @@@ Flatten nested sequences
2382 
2383                 // Push lhs
2384                 pushBody(tree.lhs, CoreType.functionType(JavaType.BOOLEAN));
2385                 Value lhs = toValue(tree.lhs, syms.booleanType);
2386                 // Yield the boolean result of the condition
2387                 append(CoreOp.core_yield(lhs));
2388                 Body.Builder bodyLhs = stack.body;
2389 
2390                 // Pop lhs
2391                 popBody();
2392 
2393                 // Push rhs
2394                 pushBody(tree.rhs, CoreType.functionType(JavaType.BOOLEAN));
2395                 Value rhs = toValue(tree.rhs, syms.booleanType);
2396                 // Yield the boolean result of the condition
2397                 append(CoreOp.core_yield(rhs));
2398                 Body.Builder bodyRhs = stack.body;
2399 
2400                 // Pop lhs
2401                 popBody();
2402 
2403                 List<Body.Builder> bodies = List.of(bodyLhs, bodyRhs);
2404                 result = append(tag == Tag.AND
2405                         ? JavaOp.conditionalAnd(bodies)
2406                         : JavaOp.conditionalOr(bodies));
2407             } else if (tag == Tag.PLUS && tree.operator.opcode == ByteCodes.string_add) {
2408                 //Ignore the operator and query both subexpressions for their type with concats
2409                 Type lhsType = tree.lhs.type;
2410                 Type rhsType = tree.rhs.type;
2411 
2412                 Value lhs = toValue(tree.lhs, lhsType.hasTag(BOT) ? syms.stringType : lhsType);
2413                 Value rhs = toValue(tree.rhs, rhsType.hasTag(BOT) ? syms.stringType : rhsType);
2414 
2415                 result = append(JavaOp.concat(lhs, rhs));
2416             }
2417             else {
2418                 Type lhsType = tree.operator.type.getParameterTypes().head;
2419                 Type rhsType = tree.operator.type.getParameterTypes().tail.head;
2420                 Value lhs = toValue(tree.lhs, lhsType);
2421                 Value rhs = toValue(tree.rhs, rhsType);
2422 
2423                 result = switch (tag) {
2424                     // Arithmetic operations
2425                     case PLUS -> append(JavaOp.add(lhs, rhs));
2426                     case MINUS -> append(JavaOp.sub(lhs, rhs));
2427                     case MUL -> append(JavaOp.mul(lhs, rhs));
2428                     case DIV -> append(JavaOp.div(lhs, rhs));
2429                     case MOD -> append(JavaOp.mod(lhs, rhs));
2430 
2431                     // Test operations
2432                     case EQ -> append(JavaOp.eq(lhs, rhs));
2433                     case NE -> append(JavaOp.neq(lhs, rhs));
2434                     //
2435                     case LT -> append(JavaOp.lt(lhs, rhs));
2436                     case LE -> append(JavaOp.le(lhs, rhs));
2437                     case GT -> append(JavaOp.gt(lhs, rhs));
2438                     case GE -> append(JavaOp.ge(lhs, rhs));
2439 
2440                     // Bitwise operations (including their boolean variants)
2441                     case BITOR -> append(JavaOp.or(lhs, rhs));
2442                     case BITAND -> append(JavaOp.and(lhs, rhs));
2443                     case BITXOR -> append(JavaOp.xor(lhs, rhs));
2444 
2445                     // Shift operations
2446                     case SL -> append(JavaOp.lshl(lhs, rhs));
2447                     case SR -> append(JavaOp.ashr(lhs, rhs));
2448                     case USR -> append(JavaOp.lshr(lhs, rhs));
2449 
2450                     default -> throw unreachable();
2451                 };
2452             }
2453         }
2454 
2455         @Override
2456         public void visitLiteral(JCLiteral tree) {
2457             Object value = switch (tree.type.getTag()) {
2458                 case BOOLEAN -> tree.value instanceof Integer i && i == 1;
2459                 case CHAR -> (char) (int) tree.value;
2460                 default -> tree.value;
2461             };
2462             Type constantType = adaptBottom(tree.type);
2463             result = append(CoreOp.constant(typeToCodeType(constantType), value));
2464         }
2465 
2466         @Override
2467         public void visitReturn(JCReturn tree) {
2468             Value retVal = toValue(tree.expr, bodyTarget);
2469             if (retVal == null) {
2470                 result = append(CoreOp.return_());
2471             } else {
2472                 result = append(CoreOp.return_(retVal));
2473             }
2474         }
2475 
2476         @Override
2477         public void visitThrow(JCTree.JCThrow tree) {
2478             Value throwVal = toValue(tree.expr);
2479             result = append(JavaOp.throw_(throwVal));
2480         }
2481 
2482         @Override
2483         public void visitBreak(JCTree.JCBreak tree) {
2484             Value label = tree.label != null
2485                     ? getLabel(tree.label.toString())
2486                     : null;
2487             result = append(JavaOp.break_(label));
2488         }
2489 
2490         @Override
2491         public void visitContinue(JCTree.JCContinue tree) {
2492             Value label = tree.label != null
2493                     ? getLabel(tree.label.toString())
2494                     : null;
2495             result = append(JavaOp.continue_(label));
2496         }
2497 
2498         @Override
2499         public void visitClassDef(JCClassDecl tree) {
2500             computeCapturesIfNeeded(tree);
2501         }
2502 
2503         AssertionError unreachable() {
2504             return new AssertionError("Should not reach here!");
2505         }
2506 
2507         CoreOp.FuncOp scanMethod(JCBlock body) {
2508             scan(body, ReflectMethods.this.currentNode());
2509             appendReturnOrUnreachable(body);
2510             CoreOp.FuncOp func = CoreOp.func(name.toString(), stack.body);
2511             func.setLocation(generateLocation(tree, true));
2512             return func;
2513         }
2514 
2515         CoreOp.FuncOp scanMethod() {
2516             return scanMethod(((JCMethodDecl)tree).body);
2517         }
2518 
2519         CoreOp.FuncOp scanLambda() {
2520             scan(tree, ReflectMethods.this.prevNode());
2521             // Return the quoted result
2522             append(CoreOp.return_(result));
2523             return CoreOp.func(name.toString(), stack.body);
2524         }
2525 
2526         Op defaultValue(Type t) {
2527             return switch (t.getTag()) {
2528                 case BYTE, SHORT, INT -> CoreOp.constant(JavaType.INT, 0);
2529                 case CHAR -> CoreOp.constant(typeToCodeType(t), (char)0);
2530                 case BOOLEAN -> CoreOp.constant(typeToCodeType(t), false);
2531                 case FLOAT -> CoreOp.constant(typeToCodeType(t), 0f);
2532                 case LONG -> CoreOp.constant(typeToCodeType(t), 0L);
2533                 case DOUBLE -> CoreOp.constant(typeToCodeType(t), 0d);
2534                 default -> CoreOp.constant(typeToCodeType(t), null);
2535             };
2536         }
2537 
2538         Op numericOneValue(Type t) {
2539             return switch (t.getTag()) {
2540                 case BYTE, SHORT, INT -> CoreOp.constant(JavaType.INT, 1);
2541                 case CHAR -> CoreOp.constant(typeToCodeType(t), (char)1);
2542                 case FLOAT -> CoreOp.constant(typeToCodeType(t), 1f);
2543                 case LONG -> CoreOp.constant(typeToCodeType(t), 1L);
2544                 case DOUBLE -> CoreOp.constant(typeToCodeType(t), 1d);
2545                 default -> throw new UnsupportedOperationException(t.toString());
2546             };
2547         }
2548     }
2549 
2550     boolean isReflectable(JCMethodDecl tree) {
2551         return codeReflectionEnabled ||
2552                 (tree.body != null &&
2553                 (reflectAll || tree.sym.attribute(crSyms.codeReflectionType.tsym) != null));
2554     }
2555 
2556     boolean isReflectable(JCFunctionalExpression expr) {
2557         return reflectAll || codeReflectionEnabled ||
2558                 (prevNode() instanceof JCTypeCast castTree && isReflectable(castTree.clazz.type));
2559     }
2560 
2561     boolean isReflectable(Type target) {
2562         if (target.isCompound()) {
2563             return ((IntersectionClassType)target).getComponents().stream()
2564                     .anyMatch(this::isReflectable);
2565         } else {
2566             return target.getAnnotationMirrors().stream()
2567                     .anyMatch(tc -> tc.type.tsym == crSyms.codeReflectionType.tsym);
2568         }
2569     }
2570 
2571     /*
2572      * Converts a method reference which cannot be used directly into a lambda.
2573      * This code has been derived from LambdaToMethod::MemberReferenceToLambda. The main
2574      * difference is that, while that code concerns with translation strategy, boxing
2575      * conversion and type erasure, this version does not and, as such, can remain
2576      * at a higher level. Note that this code needs to create a synthetic variable
2577      * declaration in case of a bounded method reference whose receiver expression
2578      * is other than 'this'/'super' (this is done to prevent the receiver expression
2579      * from being computed twice).
2580      */
2581     private class MemberReferenceToLambda {
2582 
2583         private final JCMemberReference tree;
2584         private final Symbol owner;
2585         private final ListBuffer<JCExpression> args = new ListBuffer<>();
2586         private final ListBuffer<JCVariableDecl> params = new ListBuffer<>();
2587         private JCVariableDecl receiverVar = null;
2588 
2589         MemberReferenceToLambda(JCMemberReference tree, Symbol currentClass) {
2590             this.tree = tree;
2591             this.owner = new MethodSymbol(0, names.lambda, tree.target, currentClass);
2592             if (tree.kind == ReferenceKind.BOUND && !TreeInfo.isThisQualifier(tree.getQualifierExpression())) {
2593                 // true bound method reference, hoist receiver expression out
2594                 Type recvType = types.asSuper(tree.getQualifierExpression().type, tree.sym.owner);
2595                 VarSymbol vsym = makeSyntheticVar("rec$", recvType);
2596                 receiverVar = make.VarDef(vsym, tree.getQualifierExpression());
2597             }
2598         }
2599 
2600         JCVariableDecl receiverVar() {
2601             return receiverVar;
2602         }
2603 
2604         JCLambda lambda() {
2605             int prevPos = make.pos;
2606             try {
2607                 make.at(tree);
2608 
2609                 //body generation - this can be either a method call or a
2610                 //new instance creation expression, depending on the member reference kind
2611                 VarSymbol rcvr = addParametersReturnReceiver();
2612                 JCExpression expr = (tree.getMode() == ReferenceMode.INVOKE)
2613                         ? expressionInvoke(rcvr)
2614                         : expressionNew();
2615 
2616                 JCLambda slam = make.Lambda(params.toList(), expr);
2617                 slam.target = tree.target;
2618                 slam.type = tree.type;
2619                 slam.pos = tree.pos;
2620                 return slam;
2621             } finally {
2622                 make.at(prevPos);
2623             }
2624         }
2625 
2626         /**
2627          * Generate the parameter list for the converted member reference.
2628          *
2629          * @return The receiver variable symbol, if any
2630          */
2631         VarSymbol addParametersReturnReceiver() {
2632             com.sun.tools.javac.util.List<Type> descPTypes = tree.getDescriptorType(types).getParameterTypes();
2633             VarSymbol receiverParam = null;
2634             switch (tree.kind) {
2635                 case BOUND:
2636                     if (receiverVar != null) {
2637                         receiverParam = receiverVar.sym;
2638                     }
2639                     break;
2640                 case UNBOUND:
2641                     // The receiver is the first parameter, extract it and
2642                     // adjust the SAM and unerased type lists accordingly
2643                     receiverParam = addParameter("rec$", descPTypes.head, false);
2644                     descPTypes = descPTypes.tail;
2645                     break;
2646             }
2647             for (int i = 0; descPTypes.nonEmpty(); ++i) {
2648                 // By default use the implementation method parameter type
2649                 Type parmType = descPTypes.head;
2650                 addParameter("x$" + i, parmType, true);
2651 
2652                 // Advance to the next parameter
2653                 descPTypes = descPTypes.tail;
2654             }
2655 
2656             return receiverParam;
2657         }
2658 
2659         /**
2660          * determine the receiver of the method call - the receiver can
2661          * be a type qualifier, the synthetic receiver parameter or 'super'.
2662          */
2663         private JCExpression expressionInvoke(VarSymbol receiverParam) {
2664             JCExpression qualifier = receiverParam != null ?
2665                     make.at(tree.pos).Ident(receiverParam) :
2666                     tree.getQualifierExpression();
2667 
2668             //create the qualifier expression
2669             JCFieldAccess select = make.Select(qualifier, tree.sym.name);
2670             select.sym = tree.sym;
2671             select.type = tree.referentType;
2672 
2673             //create the method call expression
2674             JCMethodInvocation apply = make.Apply(com.sun.tools.javac.util.List.nil(), select, args.toList()).
2675                     setType(tree.referentType.getReturnType());
2676 
2677             apply.varargsElement = tree.varargsElement;
2678             return apply;
2679         }
2680 
2681         /**
2682          * Lambda body to use for a 'new'.
2683          */
2684         private JCExpression expressionNew() {
2685             Type expectedType = tree.referentType.getReturnType().hasTag(TypeTag.VOID) ?
2686                     tree.expr.type : tree.referentType.getReturnType();
2687             if (tree.kind == ReferenceKind.ARRAY_CTOR) {
2688                 //create the array creation expression
2689                 JCNewArray newArr = make.NewArray(
2690                         make.Type(types.elemtype(expectedType)),
2691                         com.sun.tools.javac.util.List.of(make.Ident(params.first())),
2692                         null);
2693                 newArr.type = tree.getQualifierExpression().type;
2694                 return newArr;
2695             } else {
2696                 //create the instance creation expression
2697                 //note that method reference syntax does not allow an explicit
2698                 //enclosing class (so the enclosing class is null)
2699                 // but this may need to be patched up later with the proxy for the outer this
2700                 JCExpression newType = make.Type(types.erasure(expectedType));
2701                 if (expectedType.tsym.type.getTypeArguments().nonEmpty()) {
2702                     newType = make.TypeApply(newType, com.sun.tools.javac.util.List.nil());
2703                 }
2704                 JCNewClass newClass = make.NewClass(null,
2705                         com.sun.tools.javac.util.List.nil(),
2706                         newType,
2707                         args.toList(),
2708                         null);
2709                 newClass.constructor = tree.sym;
2710                 newClass.constructorType = tree.referentType;
2711                 newClass.type = expectedType;
2712                 newClass.varargsElement = tree.varargsElement;
2713                 return newClass;
2714             }
2715         }
2716 
2717         private VarSymbol makeSyntheticVar(String name, Type type) {
2718             VarSymbol vsym = new VarSymbol(PARAMETER | SYNTHETIC, names.fromString(name), type, owner);
2719             vsym.pos = tree.pos;
2720             return vsym;
2721         }
2722 
2723         private VarSymbol addParameter(String name, Type type, boolean genArg) {
2724             VarSymbol vsym = makeSyntheticVar(name, type);
2725             params.append(make.VarDef(vsym, null));
2726             if (genArg) {
2727                 args.append(make.Ident(vsym));
2728             }
2729             return vsym;
2730         }
2731     }
2732 
2733     static class JCReflectMethodsClassDecl extends JCClassDecl {
2734 
2735         SequencedMap<String, Op> ops;
2736 
2737         JCReflectMethodsClassDecl(JCClassDecl cls, SequencedMap<String, Op> ops) {
2738             super(cls.mods, cls.name, cls.typarams, cls.extending, cls.implementing, cls.permitting, cls.defs, cls.sym);
2739             this.pos = cls.pos;
2740             this.type = cls.type;
2741             this.ops = ops;
2742         }
2743     }
2744 
2745     public static class Provider implements CodeReflectionTransformer {
2746         @Override
2747         public JCTree translateTopLevelClass(Context context, JCTree tree, TreeMaker make) {
2748             return ReflectMethods.instance(context).translateTopLevelClass(tree, make);
2749         }
2750 
2751         @Override
2752         public void genCode(Context context, JCClassDecl cdef) throws IOException {
2753             if (cdef instanceof JCReflectMethodsClassDecl rmcdef) {
2754                 JavaFileManager fileManager = context.get(JavaFileManager.class);
2755                 JavaFileManager.Location outLocn;
2756                 if (fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
2757                     outLocn = fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT, cdef.sym.packge().modle.name.toString());
2758                 } else {
2759                     outLocn = StandardLocation.CLASS_OUTPUT;
2760                 }
2761                 String className = cdef.sym.flatName().toString() + "$$CM";
2762                 ClassDesc classDesc = ClassDesc.of(className);
2763                 JavaFileObject outFile = fileManager.getJavaFileForOutput(outLocn, className, JavaFileObject.Kind.CLASS, cdef.sym.sourcefile);
2764                 ClassDesc hostClass = ClassDesc.of(cdef.sym.flatName().toString());
2765 
2766                 CoreOp.ModuleOp module = OpBuilder.createBuilderFunctions(
2767                         rmcdef.ops,
2768                         b -> b.add(JavaOp.fieldLoad(
2769                                 FieldRef.field(JavaOp.class, "JAVA_DIALECT_FACTORY", DialectFactory.class))));
2770                 byte[] data = BytecodeGenerator.generateClassData(MethodHandles.lookup(), classDesc, module);
2771                 // inject InnerClassesAttribute and NestHostAttribute
2772                 var clm = ClassFile.of().parse(data);
2773                 data = ClassFile.of().transformClass(clm, ClassTransform.endHandler(clb ->
2774                         clb.with(InnerClassesAttribute.of(InnerClassInfo.of(classDesc, Optional.of(hostClass), Optional.of("$CM"), ClassFile.ACC_STATIC)))
2775                            .with(NestHostAttribute.of(hostClass))));
2776                 try (OutputStream out = outFile.openOutputStream()) {
2777                     out.write(data);
2778                 }
2779             }
2780         }
2781     }
2782 
2783     // type and ref conversion utils
2784 
2785     JavaType symbolToErasedDesc(Symbol s) {
2786         return typeToCodeType(s.erasure(types));
2787     }
2788 
2789     JavaType typeToCodeType(Type t) {
2790         Assert.check(!t.hasTag(METHOD));
2791         t = asDenotable(t);
2792         return switch (t.getTag()) {
2793             case VOID -> JavaType.VOID;
2794             case CHAR -> JavaType.CHAR;
2795             case BOOLEAN -> JavaType.BOOLEAN;
2796             case BYTE -> JavaType.BYTE;
2797             case SHORT -> JavaType.SHORT;
2798             case INT -> JavaType.INT;
2799             case FLOAT -> JavaType.FLOAT;
2800             case LONG -> JavaType.LONG;
2801             case DOUBLE -> JavaType.DOUBLE;
2802             case ARRAY -> {
2803                 Type et = ((ArrayType)t).elemtype;
2804                 yield JavaType.array(typeToCodeType(et));
2805             }
2806             case WILDCARD -> {
2807                 Type.WildcardType wt = (Type.WildcardType)t;
2808                 yield wt.isUnbound() ?
2809                         JavaType.wildcard() :
2810                         JavaType.wildcard(wt.isExtendsBound() ? BoundKind.EXTENDS : BoundKind.SUPER, typeToCodeType(wt.type));
2811             }
2812             case TYPEVAR -> {
2813                 Type ub = t.getUpperBound();
2814                 if (ub.contains(t)) {
2815                     // @@@ stop infinite recursion, ex: <E extends Enum<E>>
2816                     ub = types.erasure(ub);
2817                 }
2818                 yield t.tsym.owner.kind == Kind.MTH ?
2819                     JavaType.typeVar(t.tsym.name.toString(), symbolToMethodRef(t.tsym.owner),
2820                             typeToCodeType(ub)) :
2821                     JavaType.typeVar(t.tsym.name.toString(),
2822                             (jdk.incubator.code.dialect.java.ClassType)symbolToErasedDesc(t.tsym.owner),
2823                             typeToCodeType(ub));
2824             }
2825             case CLASS -> {
2826                 Assert.check(!t.isIntersection() && !t.isUnion());
2827                 JavaType typ;
2828                 if (t.getEnclosingType() != Type.noType) {
2829                     Name innerName = t.tsym.flatName().subName(t.getEnclosingType().tsym.flatName().length() + 1);
2830                     typ = JavaType.qualified(typeToCodeType(t.getEnclosingType()), innerName.toString());
2831                 } else {
2832                     typ = JavaType.type(ClassDesc.of(t.tsym.flatName().toString()));
2833                 }
2834 
2835                 List<JavaType> typeArguments;
2836                 if (t.getTypeArguments().nonEmpty()) {
2837                     typeArguments = new ArrayList<>();
2838                     for (Type ta : t.getTypeArguments()) {
2839                         typeArguments.add(typeToCodeType(ta));
2840                     }
2841                 } else {
2842                     typeArguments = List.of();
2843                 }
2844 
2845                 // Use flat name to ensure demarcation of nested classes
2846                 yield JavaType.parameterized(typ, typeArguments);
2847             }
2848             default -> throw new UnsupportedOperationException("Unsupported type: kind=" + t.getKind() + " type=" + t);
2849         };
2850     }
2851 
2852     Type codeTypeToType(CodeType jt) {
2853         return switch (jt) {
2854             case PrimitiveType pt when pt == JavaType.BOOLEAN -> syms.booleanType;
2855             case PrimitiveType pt when pt == JavaType.CHAR -> syms.charType;
2856             case PrimitiveType pt when pt == JavaType.BYTE -> syms.byteType;
2857             case PrimitiveType pt when pt == JavaType.SHORT -> syms.shortType;
2858             case PrimitiveType pt when pt == JavaType.INT -> syms.intType;
2859             case PrimitiveType pt when pt == JavaType.LONG -> syms.longType;
2860             case PrimitiveType pt when pt == JavaType.FLOAT -> syms.floatType;
2861             case PrimitiveType pt when pt == JavaType.DOUBLE -> syms.doubleType;
2862             case ClassType ct when ct.hasTypeArguments() -> {
2863                 Type enclosing = ct.enclosingType().map(this::codeTypeToType).orElse(Type.noType);
2864                 com.sun.tools.javac.util.List<Type> typeArgs = com.sun.tools.javac.util.List.from(ct.typeArguments()).map(this::codeTypeToType);
2865                 yield new Type.ClassType(enclosing, typeArgs, codeTypeToType(ct.rawType()).tsym);
2866             }
2867             case ClassType ct -> types.erasure(syms.enterClass(attrEnv().toplevel.modle, names.fromString(ct.toClassName())).type);
2868             case jdk.incubator.code.dialect.java.ArrayType at -> new Type.ArrayType(codeTypeToType(at.componentType()), syms.arrayClass);
2869             default -> Type.noType;
2870         };
2871     }
2872 
2873     Type symbolSiteType(Symbol s) {
2874         boolean isMember = s.owner == syms.predefClass ||
2875                 s.isMemberOf(currentClassSym, types);
2876         return isMember ? currentClassSym.type : s.owner.type;
2877     }
2878 
2879     FieldRef symbolToFieldRef(Symbol s, Type site) {
2880         // @@@ Made Gen::binaryQualifier public, duplicate logic?
2881         // Ensure correct qualifying class is used in the reference, see JLS 13.1
2882         // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
2883         return symbolFieldRef(gen.binaryQualifier(s, types.erasure(site)));
2884     }
2885 
2886     FieldRef symbolFieldRef(Symbol s) {
2887         Type erasedType = s.erasure(types);
2888         return FieldRef.field(
2889                 typeToCodeType(s.owner.erasure(types)),
2890                 s.name.toString(),
2891                 typeToCodeType(erasedType));
2892     }
2893 
2894     MethodRef symbolToMethodRef(Symbol s, Type site) {
2895         // @@@ Made Gen::binaryQualifier public, duplicate logic?
2896         // Ensure correct qualifying class is used in the reference, see JLS 13.1
2897         // https://docs.oracle.com/javase/specs/jls/se20/html/jls-13.html#jls-13.1
2898         return symbolToMethodRef(gen.binaryQualifier(s, types.erasure(site)));
2899     }
2900 
2901     MethodRef symbolToMethodRef(Symbol s) {
2902         Type erasedType = s.erasure(types);
2903         return MethodRef.method(
2904                 typeToCodeType(s.owner.erasure(types)),
2905                 s.name.toString(),
2906                 typeToCodeType(erasedType.getReturnType()),
2907                 erasedType.getParameterTypes().stream().map(this::typeToCodeType).toArray(CodeType[]::new));
2908     }
2909 
2910     FunctionType typeToFunctionType(Type t) {
2911         return CoreType.functionType(
2912                 typeToCodeType(t.getReturnType()),
2913                 t.getParameterTypes().stream().map(this::typeToCodeType).toArray(CodeType[]::new));
2914     }
2915 
2916     RecordTypeRef symbolToRecordTypeRef(Symbol.ClassSymbol s) {
2917         CodeType recordType = typeToCodeType(s.type);
2918         List<RecordTypeRef.ComponentRef> components = s.getRecordComponents().stream()
2919                 .map(rc -> new RecordTypeRef.ComponentRef(typeToCodeType(rc.type), rc.name.toString()))
2920                 .toList();
2921         return RecordTypeRef.recordType(recordType, components);
2922     }
2923 
2924     Env<AttrContext> attrEnv() {
2925         return typeEnvs.get(currentClassSym);
2926     }
2927 
2928     Type asDenotable(Type t) {
2929         // The goal of this type mapping is to replace occurrences of intersection and union types
2930         // with fresh type variables with appropriate upper bounds. For instance, consider the generic type
2931         // Foo<A & B & C>. We need to:
2932         // 1. replace A & B & C with a fresh type variable, so this becomes Foo<#1>, #1 <: A
2933         // 2. add #1 to the set of type-variables to be projected
2934         // 3. run upward projection on Foo<#1>, which gives Foo<? extends A>
2935         // In other words, by replacing intersection types with fresh type variables we make sure that the output
2936         // of this method is a type that is fully denotable -- e.g. can be fully represented in terms of the
2937         // CodeType API.
2938         class DenotableProjection extends StructuralTypeMapping<Void> {
2939             final ListBuffer<Type> tvars = new ListBuffer<>();
2940             final Type t;
2941 
2942             DenotableProjection(Type t) {
2943                 tvars.appendList(types.captures(t));
2944                 this.t = t;
2945             }
2946 
2947             Type asDenotable() {
2948                 return types.upward(apply(t), tvars.toList());
2949             }
2950 
2951             @Override
2952             public Type visitClassType(Type.ClassType t, Void unused) {
2953                 if (t.isIntersection()) {
2954                     Type bound = visit(((IntersectionClassType) t).getExplicitComponents().head, null);
2955                     return addTypeVar(bound, t.tsym);
2956                 } else if (t.isUnion()) {
2957                     Type bound = visit(((UnionClassType)t).getLub(), null);
2958                     return addTypeVar(bound, t.tsym);
2959                 } else {
2960                     return super.visitClassType(t, null);
2961                 }
2962             }
2963 
2964             Type addTypeVar(Type bound, Symbol owner) {
2965                 var tvsym = new TypeVariableSymbol(0, names.empty, null, owner);
2966                 tvsym.type = new TypeVar(tvsym, bound, syms.botType);
2967                 tvars.append(tvsym.type);
2968                 return tvsym.type;
2969             }
2970         }
2971 
2972         return new DenotableProjection(t).asDenotable();
2973     }
2974 }