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