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