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