1 /*
   2  * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.incubator.code.internal;
  27 
  28 import com.sun.source.tree.LambdaExpressionTree;
  29 import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
  30 import com.sun.tools.javac.code.Kinds.Kind;
  31 import com.sun.tools.javac.code.Symbol;
  32 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  33 import com.sun.tools.javac.code.Symbol.MethodSymbol;
  34 import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
  35 import com.sun.tools.javac.code.Symbol.VarSymbol;
  36 import com.sun.tools.javac.code.Symtab;
  37 import com.sun.tools.javac.code.Type;
  38 import com.sun.tools.javac.code.Type.ArrayType;
  39 import com.sun.tools.javac.code.Type.IntersectionClassType;
  40 import com.sun.tools.javac.code.Type.MethodType;
  41 import com.sun.tools.javac.code.Type.StructuralTypeMapping;
  42 import com.sun.tools.javac.code.Type.TypeVar;
  43 import com.sun.tools.javac.code.Type.UnionClassType;
  44 import com.sun.tools.javac.code.TypeTag;
  45 import com.sun.tools.javac.code.Types;
  46 import com.sun.tools.javac.comp.AttrContext;
  47 import com.sun.tools.javac.comp.CaptureScanner;
  48 import com.sun.tools.javac.comp.DeferredAttr.FilterScanner;
  49 import com.sun.tools.javac.comp.Env;
  50 import com.sun.tools.javac.comp.Flow;
  51 import com.sun.tools.javac.comp.Lower;
  52 import com.sun.tools.javac.comp.CodeReflectionTransformer;
  53 import com.sun.tools.javac.comp.TypeEnvs;
  54 import com.sun.tools.javac.jvm.ByteCodes;
  55 import com.sun.tools.javac.jvm.Gen;
  56 import com.sun.tools.javac.resources.CompilerProperties.*;
  57 import com.sun.tools.javac.tree.JCTree;
  58 import com.sun.tools.javac.tree.JCTree.JCAnnotation;
  59 import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
  60 import com.sun.tools.javac.tree.JCTree.JCAssign;
  61 import com.sun.tools.javac.tree.JCTree.JCBinary;
  62 import com.sun.tools.javac.tree.JCTree.JCBlock;
  63 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
  64 import com.sun.tools.javac.tree.JCTree.JCExpression;
  65 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
  66 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression;
  67 import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression.CodeReflectionInfo;
  68 import com.sun.tools.javac.tree.JCTree.JCIdent;
  69 import com.sun.tools.javac.tree.JCTree.JCLambda;
  70 import com.sun.tools.javac.tree.JCTree.JCLiteral;
  71 import com.sun.tools.javac.tree.JCTree.JCMemberReference;
  72 import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
  73 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
  74 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
  75 import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
  76 import com.sun.tools.javac.tree.JCTree.JCNewArray;
  77 import com.sun.tools.javac.tree.JCTree.JCNewClass;
  78 import com.sun.tools.javac.tree.JCTree.JCReturn;
  79 import com.sun.tools.javac.tree.JCTree.JCTypeCast;
  80 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
  81 import com.sun.tools.javac.tree.JCTree.JCAssert;
  82 import com.sun.tools.javac.tree.JCTree.Tag;
  83 import com.sun.tools.javac.tree.TreeInfo;
  84 import com.sun.tools.javac.tree.TreeMaker;
  85 import com.sun.tools.javac.util.Assert;
  86 import com.sun.tools.javac.util.Context;
  87 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
  88 import com.sun.tools.javac.util.ListBuffer;
  89 import com.sun.tools.javac.util.Log;
  90 import com.sun.tools.javac.util.Name;
  91 import com.sun.tools.javac.util.Names;
  92 import com.sun.tools.javac.util.Options;
  93 import jdk.incubator.code.*;
  94 import jdk.incubator.code.extern.DialectFactory;
  95 import jdk.incubator.code.dialect.core.*;
  96 import jdk.incubator.code.dialect.java.*;
  97 import jdk.incubator.code.dialect.java.WildcardType.BoundKind;
  98 
  99 import javax.lang.model.element.Modifier;
 100 import javax.tools.JavaFileObject;
 101 import java.lang.constant.ClassDesc;
 102 import java.util.*;
 103 import java.util.List;
 104 import java.util.function.BiFunction;
 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 bodyType) {
 463             this.parent = parent;
 464 
 465             this.tree = tree;
 466 
 467             this.body = Body.Builder.of(parent != null ? parent.body : null, bodyType);
 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<TypeElement> 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(typeToTypeElement(tree.sym.owner.type));
 505                 blockArgOffset++;
 506             }
 507             tree.sym.type.getParameterTypes().stream().map(ReflectMethods.this::typeToTypeElement).forEach(parameters::add);
 508 
 509             FunctionType bodyType = CoreType.functionType(
 510                     typeToTypeElement(tree.sym.type.getReturnType()), parameters);
 511 
 512             this.stack = this.top = new BodyStack(null, tree.body, bodyType);
 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::typeToTypeElement));
 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(typeToTypeElement(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.kind == VAR && ((VarSymbol)tree.sym).getConstValue() != null) {
 599                     // record the constant value associated with this
 600                     constantCaptures.put(tree.sym, ((VarSymbol)tree.sym).getConstValue());
 601                 } else {
 602                     // might be a local capture
 603                     super.visitIdent(tree);
 604                 }
 605             }
 606 
 607             @Override
 608             public void visitSelect(JCFieldAccess tree) {
 609                 if (tree.sym.kind == VAR &&
 610                         (tree.sym.name == names._this ||
 611                                 tree.sym.name == names._super) &&
 612                         !seenClasses.contains(tree.sym.type.tsym)) {
 613                     capturesThis = true;
 614                 }
 615                 super.visitSelect(tree);
 616             }
 617 
 618             @Override
 619             public void visitNewClass(JCNewClass tree) {
 620                 if (tree.type.tsym.isDirectlyOrIndirectlyLocal()) {
 621                     for (Symbol c : localCaptures.get(tree.type.tsym)) {
 622                         addFreeVar((VarSymbol) c);
 623                     }
 624                 }
 625                 if (tree.encl == null && tree.type.tsym.hasOuterInstance()) {
 626                     capturesThis = true;
 627                 }
 628                 super.visitNewClass(tree);
 629             }
 630 
 631             @Override
 632             public void visitAnnotation(JCAnnotation tree) {
 633                 // do nothing (annotation values look like captured instance fields)
 634             }
 635         }
 636 
 637         void pushBody(JCTree tree, FunctionType bodyType) {
 638             stack = new BodyStack(stack, tree, bodyType);
 639             lastOp = null; // reset
 640         }
 641 
 642         void popBody() {
 643             stack = stack.parent;
 644         }
 645 
 646         Value varOpValue(Symbol sym) {
 647             BodyStack s = stack;
 648             while (s != null) {
 649                 Value v = s.localToOp.get(sym);
 650                 if (v != null) {
 651                     return v;
 652                 }
 653                 s = s.parent;
 654             }
 655             throw new NoSuchElementException(sym.toString());
 656         }
 657 
 658         Value thisValue() { // @@@: outer this?
 659             return top.block.parameters().get(0);
 660         }
 661 
 662         Value getLabel(String labelName) {
 663             BodyStack s = stack;
 664             while (s != null) {
 665                 if (s.label != null && s.label.getKey().equals(labelName)) {
 666                     return s.label.getValue();
 667                 }
 668                 s = s.parent;
 669             }
 670             throw new NoSuchElementException(labelName);
 671         }
 672 
 673         private DiagnosticPosition pos() {
 674             JCTree current = currentNode();
 675             return current != null ? current : tree;
 676         }
 677 
 678         private Op.Result append(Op op) {
 679             return append(op, generateLocation(pos(), false), stack);
 680         }
 681 
 682         private Op.Result append(Op op, Op.Location l) {
 683             return append(op, l, stack);
 684         }
 685 
 686         private Op.Result append(Op op, Op.Location l, BodyStack stack) {
 687             lastOp = op;
 688             op.setLocation(l);
 689             return stack.block.op(op);
 690         }
 691 
 692         Op.Location generateLocation(DiagnosticPosition pos, boolean includeSourceReference) {
 693             if (!lineDebugInfo) {
 694                 return Op.Location.NO_LOCATION;
 695             }
 696 
 697             int startPos = pos.getStartPosition();
 698             int line = log.currentSource().getLineNumber(startPos);
 699             int col = log.currentSource().getColumnNumber(startPos, false);
 700             String path;
 701             if (includeSourceReference) {
 702                 path = log.currentSource().getFile().toUri().toString();
 703             } else {
 704                 path = null;
 705             }
 706             return new Op.Location(path, line, col);
 707         }
 708 
 709         private void appendReturnOrUnreachable(JCTree body) {
 710             // Append only if an existing terminating operation is not present
 711             if (lastOp == null || !(lastOp instanceof Op.Terminating)) {
 712                 // If control can continue after the body append return.
 713                 // Otherwise, append unreachable.
 714                 if (isAliveAfter(body)) {
 715                     append(CoreOp.return_());
 716                 } else {
 717                     append(CoreOp.unreachable());
 718                 }
 719             }
 720         }
 721 
 722         private boolean isAliveAfter(JCTree node) {
 723             return flow.aliveAfter(typeEnvs.get(currentClassSym), node, make);
 724         }
 725 
 726         private <O extends Op & Op.Terminating> void appendTerminating(Supplier<O> sop) {
 727             // Append only if an existing terminating operation is not present
 728             if (lastOp == null || !(lastOp instanceof Op.Terminating)) {
 729                 append(sop.get());
 730             }
 731         }
 732 
 733         public Value toValue(JCExpression expression, Type targetType) {
 734             result = null; // reset
 735             Type prevPt = pt;
 736             try {
 737                 pt = targetType;
 738                 scan(expression);
 739                 return (result == null || targetType.hasTag(TypeTag.VOID) || targetType.hasTag(NONE)) ?
 740                         result : coerce(result, expression.type, targetType);
 741             } finally {
 742                 pt = prevPt;
 743             }
 744         }
 745 
 746         public Value toValue(JCExpression expression) {
 747             return toValue(expression, Type.noType);
 748         }
 749 
 750         public Value toValue(JCTree.JCStatement statement) {
 751             result = null; // reset
 752             scan(statement);
 753             return result;
 754         }
 755 
 756         Value coerce(Value sourceValue, Type sourceType, Type targetType) {
 757             if (sourceType.isReference() && targetType.isReference() &&
 758                     !types.isSubtype(types.erasure(sourceType), types.erasure(targetType))) {
 759                 return append(JavaOp.cast(typeToTypeElement(targetType), sourceValue));
 760             }
 761             return convert(sourceValue, targetType);
 762         }
 763 
 764         Value boxIfNeeded(Value exprVal) {
 765             Type source = typeElementToType(exprVal.type());
 766             return source.hasTag(NONE) ?
 767                     exprVal : convert(exprVal, types.boxedTypeOrType(source));
 768         }
 769 
 770         Value unboxIfNeeded(Value exprVal) {
 771             Type source = typeElementToType(exprVal.type());
 772             return source.hasTag(NONE) ?
 773                     exprVal : convert(exprVal, types.unboxedTypeOrType(source));
 774         }
 775 
 776         Value convert(Value exprVal, Type target) {
 777             Type source = typeElementToType(exprVal.type());
 778             boolean sourcePrimitive = source.isPrimitive();
 779             boolean targetPrimitive = target.isPrimitive();
 780             if (target.hasTag(NONE)) {
 781                 return exprVal;
 782             } else if (sourcePrimitive == targetPrimitive) {
 783                 if (!sourcePrimitive || types.isSameType(source, target)) {
 784                     return exprVal;
 785                 } else {
 786                     // implicit primitive conversion
 787                     return append(JavaOp.conv(typeToTypeElement(target), exprVal));
 788                 }
 789             } else if (sourcePrimitive) {
 790                 // we need to box
 791                 Type unboxedTarget = types.unboxedType(target);
 792                 if (!unboxedTarget.hasTag(NONE)) {
 793                     // non-Object target
 794                     if (!types.isConvertible(source, unboxedTarget)) {
 795                         exprVal = convert(exprVal, unboxedTarget);
 796                     }
 797                     return box(exprVal, target);
 798                 } else {
 799                     // Object target
 800                     return box(exprVal, types.boxedClass(source).type);
 801                 }
 802             } else {
 803                 // we need to unbox
 804                 return unbox(exprVal, source, target, types.unboxedType(source));
 805             }
 806         }
 807 
 808         Value box(Value valueExpr, Type box) {
 809             // Boxing is a static method e.g., java.lang.Integer::valueOf(int)java.lang.Integer
 810             MethodRef boxMethod = MethodRef.method(typeToTypeElement(box), names.valueOf.toString(),
 811                     CoreType.functionType(typeToTypeElement(box), typeToTypeElement(types.unboxedType(box))));
 812             return append(JavaOp.invoke(boxMethod, valueExpr));
 813         }
 814 
 815         Value unbox(Value valueExpr, Type box, Type primitive, Type unboxedType) {
 816             if (unboxedType.hasTag(NONE)) {
 817                 // Object target, first downcast to correct wrapper type
 818                 unboxedType = primitive;
 819                 box = types.boxedClass(unboxedType).type;
 820                 valueExpr = append(JavaOp.cast(typeToTypeElement(box), valueExpr));
 821             }
 822             // Unboxing is a virtual method e.g., java.lang.Integer::intValue()int
 823             MethodRef unboxMethod = MethodRef.method(typeToTypeElement(box),
 824                     unboxedType.tsym.name.append(names.Value).toString(),
 825                     CoreType.functionType(typeToTypeElement(unboxedType)));
 826             return append(JavaOp.invoke(unboxMethod, valueExpr));
 827         }
 828 
 829         @Override
 830         public void visitVarDef(JCVariableDecl tree) {
 831             JavaType javaType = typeToTypeElement(tree.type);
 832             if (tree.init != null) {
 833                 Value initOp = toValue(tree.init, tree.type);
 834                 result = append(CoreOp.var(tree.name.toString(), javaType, initOp));
 835             } else {
 836                 // Uninitialized
 837                 result = append(CoreOp.var(tree.name.toString(), javaType));
 838             }
 839             stack.localToOp.put(tree.sym, result);
 840         }
 841 
 842         @Override
 843         public void visitAssign(JCAssign tree) {
 844             // Consume top node that applies to write access
 845             JCTree lhs = TreeInfo.skipParens(tree.lhs);
 846             Type target = tree.lhs.type;
 847             switch (lhs.getTag()) {
 848                 case IDENT: {
 849                     JCIdent assign = (JCIdent) lhs;
 850 
 851                     // Scan the rhs, the assign expression result is its input
 852                     result = toValue(tree.rhs, target);
 853 
 854                     Symbol sym = assign.sym;
 855                     switch (sym.getKind()) {
 856                         case LOCAL_VARIABLE, PARAMETER -> {
 857                             Value varOp = varOpValue(sym);
 858                             append(CoreOp.varStore(varOp, result));
 859                         }
 860                         case FIELD -> {
 861                             FieldRef fd = symbolToFieldRef(sym, symbolSiteType(sym));
 862                             if (sym.isStatic()) {
 863                                 append(JavaOp.fieldStore(fd, result));
 864                             } else {
 865                                 append(JavaOp.fieldStore(fd, thisValue(), result));
 866                             }
 867                         }
 868                         default -> {
 869                             // @@@ Cannot reach here?
 870                             throw unsupported(tree);
 871                         }
 872                     }
 873                     break;
 874                 }
 875                 case SELECT: {
 876                     JCFieldAccess assign = (JCFieldAccess) lhs;
 877 
 878                     Value receiver = toValue(assign.selected);
 879 
 880                     // Scan the rhs, the assign expression result is its input
 881                     result = toValue(tree.rhs, target);
 882 
 883                     Symbol sym = assign.sym;
 884                     FieldRef fr = symbolToFieldRef(sym, assign.selected.type);
 885                     if (sym.isStatic()) {
 886                         append(JavaOp.fieldStore(fr, result));
 887                     } else {
 888                         append(JavaOp.fieldStore(fr, receiver, result));
 889                     }
 890                     break;
 891                 }
 892                 case INDEXED: {
 893                     JCArrayAccess assign = (JCArrayAccess) lhs;
 894 
 895                     Value array = toValue(assign.indexed);
 896                     Value index = toValue(assign.index);
 897 
 898                     // Scan the rhs, the assign expression result is its input
 899                     result = toValue(tree.rhs, target);
 900 
 901                     append(JavaOp.arrayStoreOp(array, index, result));
 902                     break;
 903                 }
 904                 default:
 905                     throw unsupported(tree);
 906             }
 907         }
 908 
 909         @Override
 910         public void visitAssignop(JCTree.JCAssignOp tree) {
 911             // Capture applying rhs and operation
 912             Function<Value, Value> scanRhs = (lhs) -> {
 913                 Type unboxedType = types.unboxedTypeOrType(tree.type);
 914                 Value rhs;
 915                 if (tree.operator.opcode == ByteCodes.string_add && tree.rhs.type.isPrimitive()) {
 916                     rhs = toValue(tree.rhs);
 917                 } else {
 918                     rhs = toValue(tree.rhs, unboxedType);
 919                 }
 920                 lhs = unboxIfNeeded(lhs);
 921 
 922                 Value assignOpResult = switch (tree.getTag()) {
 923 
 924                     // Arithmetic operations
 925                     case PLUS_ASG -> {
 926                         if (tree.operator.opcode == ByteCodes.string_add) {
 927                             yield append(JavaOp.concat(lhs, rhs));
 928                         } else {
 929                             yield append(JavaOp.add(lhs, rhs));
 930                         }
 931                     }
 932                     case MINUS_ASG -> append(JavaOp.sub(lhs, rhs));
 933                     case MUL_ASG -> append(JavaOp.mul(lhs, rhs));
 934                     case DIV_ASG -> append(JavaOp.div(lhs, rhs));
 935                     case MOD_ASG -> append(JavaOp.mod(lhs, rhs));
 936 
 937                     // Bitwise operations (including their boolean variants)
 938                     case BITOR_ASG -> append(JavaOp.or(lhs, rhs));
 939                     case BITAND_ASG -> append(JavaOp.and(lhs, rhs));
 940                     case BITXOR_ASG -> append(JavaOp.xor(lhs, rhs));
 941 
 942                     // Shift operations
 943                     case SL_ASG -> append(JavaOp.lshl(lhs, rhs));
 944                     case SR_ASG -> append(JavaOp.ashr(lhs, rhs));
 945                     case USR_ASG -> append(JavaOp.lshr(lhs, rhs));
 946 
 947 
 948                     default -> throw unsupported(tree);
 949                 };
 950                 return result = convert(assignOpResult, tree.type);
 951             };
 952 
 953             applyCompoundAssign(tree.lhs, scanRhs);
 954         }
 955 
 956         void applyCompoundAssign(JCTree.JCExpression lhs, Function<Value, Value> scanRhs) {
 957             // Consume top node that applies to access
 958             lhs = TreeInfo.skipParens(lhs);
 959             switch (lhs.getTag()) {
 960                 case IDENT -> {
 961                     JCIdent assign = (JCIdent) lhs;
 962 
 963                     Symbol sym = assign.sym;
 964                     switch (sym.getKind()) {
 965                         case LOCAL_VARIABLE, PARAMETER -> {
 966                             Value varOp = varOpValue(sym);
 967 
 968                             Op.Result lhsOpValue = append(CoreOp.varLoad(varOp));
 969                             // Scan the rhs
 970                             Value r = scanRhs.apply(lhsOpValue);
 971 
 972                             append(CoreOp.varStore(varOp, r));
 973                         }
 974                         case FIELD -> {
 975                             FieldRef fr = symbolToFieldRef(sym, symbolSiteType(sym));
 976 
 977                             Op.Result lhsOpValue;
 978                             TypeElement resultType = typeToTypeElement(sym.type);
 979                             if (sym.isStatic()) {
 980                                 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr));
 981                             } else {
 982                                 lhsOpValue = append(JavaOp.fieldLoad(resultType, fr, thisValue()));
 983                             }
 984                             // Scan the rhs
 985                             Value r = scanRhs.apply(lhsOpValue);
 986 
 987                             if (sym.isStatic()) {
 988                                 append(JavaOp.fieldStore(fr, r));
 989                             } else {
 990                                 append(JavaOp.fieldStore(fr, thisValue(), r));
 991                             }
 992                         }
 993                         default -> {
 994                             // @@@ Cannot reach here?
 995                             throw unsupported(lhs);
 996                         }
 997                     }
 998                 }
 999                 case SELECT -> {
1000                     JCFieldAccess assign = (JCFieldAccess) lhs;
1001 
1002                     Value receiver = toValue(assign.selected);
1003 
1004                     Symbol sym = assign.sym;
1005                     FieldRef fr = symbolToFieldRef(sym, assign.selected.type);
1006 
1007                     Op.Result lhsOpValue;
1008                     TypeElement resultType = typeToTypeElement(sym.type);
1009                     if (sym.isStatic()) {
1010                         lhsOpValue = append(JavaOp.fieldLoad(resultType, fr));
1011                     } else {
1012                         lhsOpValue = append(JavaOp.fieldLoad(resultType, fr, receiver));
1013                     }
1014                     // Scan the rhs
1015                     Value r = scanRhs.apply(lhsOpValue);
1016 
1017                     if (sym.isStatic()) {
1018                         append(JavaOp.fieldStore(fr, r));
1019                     } else {
1020                         append(JavaOp.fieldStore(fr, receiver, r));
1021                     }
1022                 }
1023                 case INDEXED -> {
1024                     JCArrayAccess assign = (JCArrayAccess) lhs;
1025 
1026                     Value array = toValue(assign.indexed);
1027                     Value index = toValue(assign.index);
1028 
1029                     Op.Result lhsOpValue = append(JavaOp.arrayLoadOp(array, index));
1030                     // Scan the rhs
1031                     Value r = scanRhs.apply(lhsOpValue);
1032 
1033                     append(JavaOp.arrayStoreOp(array, index, r));
1034                 }
1035                 default -> throw unsupported(lhs);
1036             }
1037         }
1038 
1039         @Override
1040         public void visitIdent(JCIdent tree) {
1041             // Visited only for read access
1042 
1043             Symbol sym = tree.sym;
1044             switch (sym.getKind()) {
1045                 case LOCAL_VARIABLE, RESOURCE_VARIABLE, BINDING_VARIABLE, PARAMETER, EXCEPTION_PARAMETER ->
1046                         result = loadVar(sym);
1047                 case FIELD, ENUM_CONSTANT -> {
1048                     if (sym.name.equals(names._this) || sym.name.equals(names._super)) {
1049                         result = thisValue();
1050                     } else if (top.localToOp.containsKey(sym)) {
1051                         // if field symbol is a key in top.localToOp
1052                         // we expect that we're producing the model of a lambda
1053                         // we also expect that the field is a constant capture and sym was mapped to VarOp result
1054                         Assert.check(isLambdaReflectable);
1055                         Assert.check(sym.isStatic());
1056                         Assert.check(sym.isFinal());
1057                         result = loadVar(sym);
1058                     } else {
1059                         FieldRef fr = symbolToFieldRef(sym, symbolSiteType(sym));
1060                         TypeElement resultType = typeToTypeElement(sym.type);
1061                         if (sym.isStatic()) {
1062                             result = append(JavaOp.fieldLoad(resultType, fr));
1063                         } else {
1064                             result = append(JavaOp.fieldLoad(resultType, fr, thisValue()));
1065                         }
1066                     }
1067                 }
1068                 case PACKAGE, INTERFACE, CLASS, RECORD, ENUM -> {
1069                     result = null;
1070                 }
1071                 default -> {
1072                     // @@@ Cannot reach here?
1073                     throw unsupported(tree);
1074                 }
1075             }
1076         }
1077 
1078         private Value loadVar(Symbol sym) {
1079             Value varOp = varOpValue(sym);
1080             Assert.check(varOp.type() instanceof VarType);
1081             return append(CoreOp.varLoad(varOp));
1082         }
1083 
1084         @Override
1085         public void visitTypeIdent(JCTree.JCPrimitiveTypeTree tree) {
1086             result = null;
1087         }
1088 
1089         @Override
1090         public void visitTypeArray(JCTree.JCArrayTypeTree tree) {
1091             result = null; // MyType[].class is handled in visitSelect just as MyType.class
1092         }
1093 
1094         @Override
1095         public void visitSelect(JCFieldAccess tree) {
1096             // Visited only for read access
1097 
1098             Type qualifierTarget = qualifierTarget(tree);
1099             // @@@: might cause redundant load if accessed symbol is static but the qualifier is not a type
1100             Value receiver = toValue(tree.selected);
1101 
1102             if (tree.name.equals(names._class)) {
1103                 result = append(CoreOp.constant(JavaType.J_L_CLASS, typeToTypeElement(tree.selected.type)));
1104             } else if (types.isArray(tree.selected.type)) {
1105                 if (tree.sym.equals(syms.lengthVar)) {
1106                     result = append(JavaOp.arrayLength(receiver));
1107                 } else {
1108                     // Should not reach here
1109                     throw unsupported(tree);
1110                 }
1111             } else {
1112                 Symbol sym = tree.sym;
1113                 switch (sym.getKind()) {
1114                     case FIELD, ENUM_CONSTANT -> {
1115                         if (sym.name.equals(names._this) || sym.name.equals(names._super)) {
1116                             result = thisValue();
1117                         } else {
1118                             FieldRef fr = symbolToFieldRef(sym, qualifierTarget.hasTag(NONE) ?
1119                                     tree.selected.type : qualifierTarget);
1120                             TypeElement resultType = typeToTypeElement(types.memberType(tree.selected.type, sym));
1121                             if (sym.isStatic()) {
1122                                 result = append(JavaOp.fieldLoad(resultType, fr));
1123                             } else {
1124                                 result = append(JavaOp.fieldLoad(resultType, fr, receiver));
1125                             }
1126                         }
1127                     }
1128                     case PACKAGE, INTERFACE, CLASS, RECORD, ENUM -> {
1129                         result = null;
1130                     }
1131                     default -> {
1132                         // @@@ Cannot reach here?
1133                         throw unsupported(tree);
1134                     }
1135                 }
1136             }
1137         }
1138 
1139         @Override
1140         public void visitIndexed(JCArrayAccess tree) {
1141             // Visited only for read access
1142 
1143             Value array = toValue(tree.indexed);
1144 
1145             Value index = toValue(tree.index, typeElementToType(JavaType.INT));
1146 
1147             result = append(JavaOp.arrayLoadOp(array, index));
1148         }
1149 
1150         @Override
1151         public void visitApply(JCTree.JCMethodInvocation tree) {
1152             // @@@ Symbol.externalType, for use with inner classes
1153 
1154             // @@@ this.xyz(...) calls in a constructor
1155 
1156             JCTree meth = TreeInfo.skipParens(tree.meth);
1157             switch (meth.getTag()) {
1158                 case IDENT: {
1159                     JCIdent access = (JCIdent) meth;
1160 
1161                     Symbol sym = access.sym;
1162                     List<Value> args = new ArrayList<>();
1163                     JavaOp.InvokeOp.InvokeKind ik;
1164                     if (!sym.isStatic()) {
1165                         ik = JavaOp.InvokeOp.InvokeKind.INSTANCE;
1166                         args.add(thisValue());
1167                     } else {
1168                         ik = JavaOp.InvokeOp.InvokeKind.STATIC;
1169                     }
1170 
1171                     args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
1172 
1173                     MethodRef mr = symbolToMethodRef(sym, symbolSiteType(sym));
1174                     Value res = append(JavaOp.invoke(ik, tree.varargsElement != null,
1175                             typeToTypeElement(meth.type.getReturnType()), mr, args));
1176                     if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1177                         result = res;
1178                     }
1179                     break;
1180                 }
1181                 case SELECT: {
1182                     JCFieldAccess access = (JCFieldAccess) meth;
1183 
1184                     Type qualifierTarget = qualifierTarget(access);
1185                     Value receiver = toValue(access.selected, qualifierTarget);
1186 
1187                     Symbol sym = access.sym;
1188                     List<Value> args = new ArrayList<>();
1189                     JavaOp.InvokeOp.InvokeKind ik;
1190                     if (!sym.isStatic()) {
1191                         args.add(receiver);
1192                         // @@@ expr.super(...) for inner class super constructor calls
1193                         ik = switch (access.selected) {
1194                             case JCIdent i when i.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1195                             case JCFieldAccess fa when fa.sym.name.equals(names._super) -> JavaOp.InvokeOp.InvokeKind.SUPER;
1196                             default -> JavaOp.InvokeOp.InvokeKind.INSTANCE;
1197                         };
1198                     } else {
1199                         ik = JavaOp.InvokeOp.InvokeKind.STATIC;
1200                     }
1201 
1202                     args.addAll(scanMethodArguments(tree.args, tree.meth.type, tree.varargsElement));
1203 
1204                     MethodRef mr = symbolToMethodRef(sym, qualifierTarget.hasTag(NONE) ?
1205                             access.selected.type : qualifierTarget);
1206                     JavaType returnType = typeToTypeElement(meth.type.getReturnType());
1207                     JavaOp.InvokeOp iop = JavaOp.invoke(ik, tree.varargsElement != null,
1208                             returnType, mr, args);
1209                     Value res = append(iop);
1210                     if (sym.type.getReturnType().getTag() != TypeTag.VOID) {
1211                         result = res;
1212                     }
1213                     break;
1214                 }
1215                 default:
1216                     unsupported(meth);
1217             }
1218         }
1219 
1220         List<Value> scanMethodArguments(List<JCExpression> args, Type methodType, Type varargsElement) {
1221             ListBuffer<Value> argValues = new ListBuffer<>();
1222             com.sun.tools.javac.util.List<Type> targetTypes = methodType.getParameterTypes();
1223             if (varargsElement != null) {
1224                 targetTypes = targetTypes.reverse().tail;
1225                 for (int i = 0 ; i < args.size() - (methodType.getParameterTypes().size() - 1) ; i++) {
1226                     targetTypes = targetTypes.prepend(varargsElement);
1227                 }
1228                 targetTypes = targetTypes.reverse();
1229             }
1230 
1231             for (JCTree.JCExpression arg : args) {
1232                 argValues.add(toValue(arg, targetTypes.head));
1233                 targetTypes = targetTypes.tail;
1234             }
1235             return argValues.toList();
1236         }
1237 
1238         @Override
1239         public void visitReference(JCTree.JCMemberReference tree) {
1240             MemberReferenceToLambda memberReferenceToLambda = new MemberReferenceToLambda(tree, currentClassSym);
1241             JCVariableDecl recv = memberReferenceToLambda.receiverVar();
1242             if (recv != null) {
1243                 scan(recv);
1244             }
1245             scan(memberReferenceToLambda.lambda());
1246         }
1247 
1248         Type qualifierTarget(JCFieldAccess tree) {
1249             Type selectedType = types.skipTypeVars(tree.selected.type, true);
1250             return selectedType.isCompound() ?
1251                     tree.sym.owner.type :
1252                     Type.noType;
1253         }
1254 
1255         @Override
1256         public void visitTypeCast(JCTree.JCTypeCast tree) {
1257             Value v = toValue(tree.expr);
1258 
1259             Type expressionType = tree.expr.type;
1260             Type type = tree.type;
1261             if (expressionType.isPrimitive() && type.isPrimitive()) {
1262                 if (expressionType.equals(type)) {
1263                     // Redundant cast
1264                     result = v;
1265                 } else {
1266                     result = append(JavaOp.conv(typeToTypeElement(type), v));
1267                 }
1268             } else if (expressionType.isPrimitive() || type.isPrimitive()) {
1269                 result = convert(v, tree.type);
1270             } else if (!expressionType.hasTag(BOT) &&
1271                     types.isAssignable(expressionType, type)) {
1272                 // Redundant cast
1273                 result = v;
1274             } else {
1275                 // Reference cast
1276                 JavaType jt = typeToTypeElement(types.erasure(type));
1277                 result = append(JavaOp.cast(typeToTypeElement(type), jt, v));
1278             }
1279         }
1280 
1281         @Override
1282         public void visitTypeTest(JCTree.JCInstanceOf tree) {
1283             Value target = toValue(tree.expr);
1284 
1285             if (tree.pattern.getTag() != Tag.IDENT) {
1286                 result = scanPattern(tree.getPattern(), target);
1287             } else {
1288                 result = append(JavaOp.instanceOf(typeToTypeElement(tree.pattern.type), target));
1289             }
1290         }
1291 
1292         Value scanPattern(JCTree.JCPattern pattern, Value target) {
1293             // Type of pattern
1294             JavaType patternType;
1295             if (pattern instanceof JCTree.JCBindingPattern p) {
1296                 patternType = JavaOp.Pattern.bindingType(typeToTypeElement(p.type));
1297             } else if (pattern instanceof JCTree.JCRecordPattern p) {
1298                 patternType = JavaOp.Pattern.recordType(typeToTypeElement(p.record.type));
1299             } else {
1300                 throw unsupported(pattern);
1301             }
1302 
1303             // Push pattern body
1304             pushBody(pattern, CoreType.functionType(patternType));
1305 
1306             // @@@ Assumes just pattern nodes, likely will change when method patterns are supported
1307             //     that have expressions for any arguments (which perhaps in turn may have pattern expressions)
1308             List<JCVariableDecl> variables = new ArrayList<>();
1309             class PatternScanner extends FilterScanner {
1310 
1311                 private Value result;
1312 
1313                 public PatternScanner() {
1314                     super(Set.of(Tag.BINDINGPATTERN, Tag.RECORDPATTERN, Tag.ANYPATTERN));
1315                 }
1316 
1317                 @Override
1318                 public void visitBindingPattern(JCTree.JCBindingPattern binding) {
1319                     JCVariableDecl var = binding.var;
1320                     variables.add(var);
1321                     boolean unnamedPatternVariable = var.name.isEmpty();
1322                     String bindingName = unnamedPatternVariable ? null : var.name.toString();
1323                     result = append(JavaOp.typePattern(typeToTypeElement(var.type), bindingName));
1324                 }
1325 
1326                 @Override
1327                 public void visitRecordPattern(JCTree.JCRecordPattern record) {
1328                     // @@@ Is always Identifier to record?
1329                     // scan(record.deconstructor);
1330 
1331                     List<Value> nestedValues = new ArrayList<>();
1332                     for (JCTree.JCPattern jcPattern : record.nested) {
1333                         // @@@ when we support ANYPATTERN, we must add result of toValue only if it's non-null
1334                         // because passing null to recordPattern methods will cause an error
1335                         nestedValues.add(toValue(jcPattern));
1336                     }
1337 
1338                     result = append(JavaOp.recordPattern(symbolToRecordTypeRef(record.record), nestedValues));
1339                 }
1340 
1341                 @Override
1342                 public void visitAnyPattern(JCTree.JCAnyPattern anyPattern) {
1343                     result = append(JavaOp.matchAllPattern());
1344                 }
1345 
1346                 Value toValue(JCTree tree) {
1347                     result = null;
1348                     scan(tree);
1349                     return result;
1350                 }
1351             }
1352             // Scan pattern
1353             Value patternValue = new PatternScanner().toValue(pattern);
1354             append(CoreOp.core_yield(patternValue));
1355             Body.Builder patternBody = stack.body;
1356 
1357             // Pop body
1358             popBody();
1359 
1360             // Find nearest ancestor body stack element associated with a statement tree
1361             // @@@ Strengthen check of tree?
1362             BodyStack _variablesStack = stack;
1363             while (!(_variablesStack.tree instanceof JCTree.JCStatement)) {
1364                 _variablesStack = _variablesStack.parent;
1365             }
1366             BodyStack variablesStack = _variablesStack;
1367 
1368             // Create pattern var ops for pattern variables using the
1369             // builder associated with the nearest statement tree
1370             BodyStack previous = stack;
1371             // Temporarily position the stack to where the pattern variables are to be declared
1372             stack = variablesStack;
1373             try {
1374                 for (JCVariableDecl jcVar : variables) {
1375                     // @@@ use uninitialized variable
1376                     Value defaultValue = append(defaultValue(jcVar.type));
1377                     Value init = convert(defaultValue, jcVar.type);
1378                     Op.Result op = append(CoreOp.var(jcVar.name.toString(), typeToTypeElement(jcVar.type), init));
1379                     stack.localToOp.put(jcVar.sym, op);
1380                 }
1381             } finally {
1382                 stack = previous;
1383             }
1384 
1385             // Create pattern descriptor
1386             List<JavaType> patternDescParams = variables.stream().map(var -> typeToTypeElement(var.type)).toList();
1387             FunctionType matchFuncType = CoreType.functionType(JavaType.VOID, patternDescParams);
1388 
1389             // Create the match body, assigning pattern values to pattern variables
1390             Body.Builder matchBody = Body.Builder.of(patternBody.ancestorBody(), matchFuncType);
1391             Block.Builder matchBuilder = matchBody.entryBlock();
1392             for (int i = 0; i < variables.size(); i++) {
1393                 Value v = matchBuilder.parameters().get(i);
1394                 Value var = variablesStack.localToOp.get(variables.get(i).sym);
1395                 matchBuilder.op(CoreOp.varStore(var, v));
1396             }
1397             matchBuilder.op(CoreOp.core_yield());
1398 
1399             // Create the match operation
1400             return append(JavaOp.match(target, patternBody, matchBody));
1401         }
1402 
1403         @Override
1404         public void visitNewClass(JCTree.JCNewClass tree) {
1405             if (tree.def != null) {
1406                 scan(tree.def);
1407             }
1408 
1409             // @@@ Support local classes in pre-construction contexts
1410             // this cannot happen, as constructors cannot be reflectable
1411             if (tree.type.tsym.isDirectlyOrIndirectlyLocal() && (tree.type.tsym.flags() & NOOUTERTHIS) != 0) {
1412                 throw unsupported(tree);
1413             }
1414 
1415             List<TypeElement> argtypes = new ArrayList<>();
1416             Type type = tree.type;
1417             List<Value> args = new ArrayList<>();
1418             if (type.tsym.hasOuterInstance()) {
1419                 // Obtain outer value for inner class, and add as first argument
1420                 JCTree.JCExpression encl = tree.encl;
1421                 Value outerInstance;
1422                 if (encl == null) {
1423                     outerInstance = thisValue();
1424                 } else {
1425                     outerInstance = toValue(tree.encl);
1426                 }
1427                 args.add(outerInstance);
1428                 argtypes.add(outerInstance.type());
1429             }
1430             if (tree.type.tsym.isDirectlyOrIndirectlyLocal()) {
1431                 for (Symbol c : localCaptures.get(tree.type.tsym)) {
1432                     args.add(loadVar(c));
1433                     argtypes.add(symbolToErasedDesc(c));
1434                 }
1435             }
1436 
1437             // Create erased method type reference for constructor, where
1438             // the return type declares the class to instantiate
1439             // We need to manually construct the constructor reference,
1440             // as the signature of the constructor symbol is not augmented
1441             // with enclosing this and captured params.
1442             MethodRef methodRef = symbolToMethodRef(tree.constructor);
1443             argtypes.addAll(methodRef.type().parameterTypes());
1444             FunctionType constructorType = CoreType.functionType(
1445                     symbolToErasedDesc(tree.constructor.owner),
1446                     argtypes);
1447             MethodRef constructorRef = MethodRef.constructor(constructorType);
1448 
1449             args.addAll(scanMethodArguments(tree.args, tree.constructorType, tree.varargsElement));
1450 
1451             result = append(JavaOp.new_(tree.varargsElement != null, typeToTypeElement(type), constructorRef, args));
1452         }
1453 
1454         @Override
1455         public void visitNewArray(JCTree.JCNewArray tree) {
1456             if (tree.elems != null) {
1457                 int length = tree.elems.size();
1458                 Op.Result a = append(JavaOp.newArray(
1459                         typeToTypeElement(tree.type),
1460                         append(CoreOp.constant(JavaType.INT, length))));
1461                 int i = 0;
1462                 for (JCExpression elem : tree.elems) {
1463                     Value element = toValue(elem, types.elemtype(tree.type));
1464                     append(JavaOp.arrayStoreOp(
1465                             a,
1466                             append(CoreOp.constant(JavaType.INT, i)),
1467                             element));
1468                     i++;
1469                 }
1470 
1471                 result = a;
1472             } else {
1473                 List<Value> indexes = new ArrayList<>();
1474                 for (JCTree.JCExpression dim : tree.dims) {
1475                     indexes.add(toValue(dim));
1476                 }
1477 
1478                 JavaType arrayType = typeToTypeElement(tree.type);
1479                 MethodRef constructorRef = MethodRef.constructor(arrayType,
1480                         indexes.stream().map(Value::type).toList());
1481                 result = append(JavaOp.new_(constructorRef, indexes));
1482             }
1483         }
1484 
1485         @Override
1486         public void visitLambda(JCTree.JCLambda tree) {
1487             final FunctionType lambdaType = typeToFunctionType(types.findDescriptorType(tree.target));
1488 
1489             // Push quoted body
1490             // We can either be explicitly quoted or a structural quoted expression
1491             // within some larger reflected code
1492 
1493             // a reflectable lambda is going to have its model wrapped in QuotedOp
1494             // only when we are producing the model of the lambda, thus the condition (isReflectable ...)
1495             // also, a lambda contained in a reflectable lambda, will not have its model wrapped in QuotedOp,
1496             // thus the condition (... body == tree)
1497             boolean toQuote = (isLambdaReflectable && this.tree == tree);
1498             if (toQuote) {
1499                 pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1500             }
1501 
1502             // Push lambda body
1503             pushBody(tree.body, lambdaType);
1504 
1505             // Map lambda parameters to varOp values
1506             for (int i = 0; i < tree.params.size(); i++) {
1507                 JCVariableDecl p = tree.params.get(i);
1508                 Op.Result paramOp = append(CoreOp.var(
1509                         p.name.toString(),
1510                         stack.block.parameters().get(i)));
1511                 stack.localToOp.put(p.sym, paramOp);
1512             }
1513 
1514             // Scan the lambda body
1515             Type lambdaReturnType = tree.getDescriptorType(types).getReturnType();
1516             if (tree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) {
1517                 Value exprVal = toValue(((JCExpression) tree.body), lambdaReturnType);
1518                 if (!lambdaReturnType.hasTag(TypeTag.VOID)) {
1519                     append(CoreOp.return_(exprVal));
1520                 } else {
1521                     appendTerminating(CoreOp::return_);
1522                 }
1523             } else {
1524                 Type prevBodyTarget = bodyTarget;
1525                 try {
1526                     bodyTarget = lambdaReturnType;
1527                     toValue(((JCTree.JCStatement) tree.body));
1528                     appendReturnOrUnreachable(tree.body);
1529                 } finally {
1530                     bodyTarget = prevBodyTarget;
1531                 }
1532             }
1533 
1534             // Get the functional interface type
1535             JavaType fiType = typeToTypeElement(tree.target);
1536             // build functional lambda
1537             Op lambdaOp = JavaOp.lambda(fiType, stack.body, true);
1538 
1539             // Pop lambda body
1540             popBody();
1541 
1542             Value lambdaResult;
1543             if (toQuote) {
1544                 lambdaResult = append(lambdaOp, generateLocation(tree, true));
1545             } else {
1546                 lambdaResult = append(lambdaOp);
1547             }
1548 
1549             if (toQuote) {
1550                 append(CoreOp.core_yield(lambdaResult));
1551                 CoreOp.QuotedOp quotedOp = CoreOp.quoted(stack.body);
1552 
1553                 // Pop quoted body
1554                 popBody();
1555 
1556                 lambdaResult = append(quotedOp);
1557             }
1558 
1559             result = lambdaResult;
1560         }
1561 
1562         @Override
1563         public void visitIf(JCTree.JCIf tree) {
1564             List<Body.Builder> bodies = new ArrayList<>();
1565 
1566             while (tree != null) {
1567                 JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1568 
1569                 // Push if condition
1570                 pushBody(cond,
1571                         CoreType.functionType(JavaType.BOOLEAN));
1572                 Value last = toValue(cond);
1573                 last = convert(last, typeElementToType(JavaType.BOOLEAN));
1574                 // Yield the boolean result of the condition
1575                 append(CoreOp.core_yield(last));
1576                 bodies.add(stack.body);
1577 
1578                 // Pop if condition
1579                 popBody();
1580 
1581                 // Push if body
1582                 pushBody(tree.thenpart, CoreType.FUNCTION_TYPE_VOID);
1583 
1584                 scan(tree.thenpart);
1585                 appendTerminating(CoreOp::core_yield);
1586                 bodies.add(stack.body);
1587 
1588                 // Pop if body
1589                 popBody();
1590 
1591                 JCTree.JCStatement elsepart = tree.elsepart;
1592                 if (elsepart == null) {
1593                     tree = null;
1594                 } else if (elsepart.getTag() == Tag.IF) {
1595                     tree = (JCTree.JCIf) elsepart;
1596                 } else {
1597                     // Push else body
1598                     pushBody(elsepart, CoreType.FUNCTION_TYPE_VOID);
1599 
1600                     scan(elsepart);
1601                     appendTerminating(CoreOp::core_yield);
1602                     bodies.add(stack.body);
1603 
1604                     // Pop else body
1605                     popBody();
1606 
1607                     tree = null;
1608                 }
1609             }
1610 
1611             append(JavaOp.if_(bodies));
1612             result = null;
1613         }
1614 
1615         @Override
1616         public void visitSwitchExpression(JCTree.JCSwitchExpression tree) {
1617             Value target = toValue(tree.selector);
1618 
1619             Type switchType = adaptBottom(tree.type);
1620             FunctionType caseBodyType = CoreType.functionType(typeToTypeElement(switchType));
1621 
1622             List<Body.Builder> bodies = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, caseBodyType,
1623                     !tree.hasUnconditionalPattern);
1624 
1625             result = append(JavaOp.switchExpression(caseBodyType.returnType(), target, bodies));
1626         }
1627 
1628         @Override
1629         public void visitSwitch(JCTree.JCSwitch tree) {
1630             Value target = toValue(tree.selector);
1631 
1632             FunctionType actionType = CoreType.FUNCTION_TYPE_VOID;
1633 
1634             List<Body.Builder> bodies = visitSwitchStatAndExpr(tree, tree.selector, target, tree.cases, actionType,
1635                     tree.patternSwitch && !tree.hasUnconditionalPattern);
1636 
1637             result = append(JavaOp.switchStatement(target, bodies));
1638         }
1639 
1640         private List<Body.Builder> visitSwitchStatAndExpr(JCTree tree, JCExpression selector, Value target,
1641                                                           List<JCTree.JCCase> cases, FunctionType caseBodyType,
1642                                                           boolean isDefaultCaseNeeded) {
1643             List<Body.Builder> bodies = new ArrayList<>();
1644             boolean hasDefaultCase = false;
1645 
1646             for (JCTree.JCCase c : cases) {
1647                 Body.Builder caseLabel = visitCaseLabel(tree, target, c);
1648                 Body.Builder caseBody = visitCaseBody(tree, c, caseBodyType, cases.getLast() == c);
1649                 bodies.add(caseLabel);
1650                 bodies.add(caseBody);
1651                 hasDefaultCase = c.labels.head instanceof JCTree.JCDefaultCaseLabel;
1652             }
1653 
1654             if (!hasDefaultCase && isDefaultCaseNeeded) {
1655                 // label
1656                 pushBody(tree, CoreType.functionType(JavaType.BOOLEAN));
1657                 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1658                 bodies.add(stack.body);
1659                 popBody();
1660 
1661                 // body
1662                 pushBody(tree, caseBodyType);
1663                 append(JavaOp.throw_(
1664                         append(JavaOp.new_(MethodRef.constructor(MatchException.class)))
1665                 ));
1666                 bodies.add(stack.body);
1667                 popBody();
1668             }
1669 
1670             return bodies;
1671         }
1672 
1673         private Value processConstantLabel(Value target, JCTree.JCConstantCaseLabel label) {
1674             if (target.type().equals(JavaType.J_L_STRING)) {
1675                 return append(JavaOp.invoke(
1676                         MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
1677                         target, toValue(label.expr)));
1678             } else {
1679                 // target is primitive wrapper, primitive or enum
1680                 // if target of type Character, Byte, Short or Integer, unbox it
1681                 if (target.type().equals(JavaType.J_L_CHARACTER) || target.type().equals(JavaType.J_L_BYTE) ||
1682                         target.type().equals(JavaType.J_L_SHORT) || target.type().equals(JavaType.J_L_INTEGER)) {
1683                     PrimitiveType pt = ((ClassType) target.type()).unbox().get();
1684                     target = convert(target, typeElementToType(pt));
1685                 }
1686                 Value expr = toValue(label.expr);
1687                 // conversion may be needed for primitive, e.g. label (byte) 1 and selector of type int
1688                 expr = convert(expr, typeElementToType(target.type()));
1689                 return append(JavaOp.eq(target, expr));
1690             }
1691         }
1692 
1693         private Body.Builder visitCaseLabel(JCTree tree, Value target, JCTree.JCCase c) {
1694             Body.Builder body;
1695             FunctionType caseLabelType = CoreType.functionType(JavaType.BOOLEAN, target.type());
1696 
1697             JCTree.JCCaseLabel headCl = c.labels.head;
1698             if (headCl instanceof JCTree.JCPatternCaseLabel pcl) {
1699                 if (c.labels.size() > 1) {
1700                     throw unsupported(c);
1701                 }
1702 
1703                 pushBody(pcl, caseLabelType);
1704 
1705                 Value localTarget = stack.block.parameters().get(0);
1706                 final Value localResult;
1707                 if (c.guard != null) {
1708                     List<Body.Builder> clBodies = new ArrayList<>();
1709 
1710                     pushBody(pcl.pat, CoreType.functionType(JavaType.BOOLEAN));
1711 
1712                     localTarget = boxIfNeeded(localTarget);
1713                     Value patVal = scanPattern(pcl.pat, localTarget);
1714                     append(CoreOp.core_yield(patVal));
1715                     clBodies.add(stack.body);
1716                     popBody();
1717 
1718                     pushBody(c.guard, CoreType.functionType(JavaType.BOOLEAN));
1719                     append(CoreOp.core_yield(toValue(c.guard)));
1720                     clBodies.add(stack.body);
1721                     popBody();
1722 
1723                     localResult = append(JavaOp.conditionalAnd(clBodies));
1724                 } else {
1725                     localTarget = boxIfNeeded(localTarget);
1726                     localResult = scanPattern(pcl.pat, localTarget);
1727                 }
1728                 // Yield the boolean result of the condition
1729                 append(CoreOp.core_yield(localResult));
1730                 body = stack.body;
1731 
1732                 // Pop label
1733                 popBody();
1734             } else if (headCl instanceof JCTree.JCConstantCaseLabel ccl) {
1735                 pushBody(headCl, caseLabelType);
1736 
1737                 Value localTarget = stack.block.parameters().get(0);
1738                 final Value localResult;
1739                 if (c.labels.size() == 1) {
1740                     localResult = processConstantLabel(localTarget, ccl);
1741                 } else {
1742                     List<Body.Builder> clBodies = new ArrayList<>();
1743                     for (JCTree.JCCaseLabel cl : c.labels) {
1744                         ccl = (JCTree.JCConstantCaseLabel) cl;
1745                         pushBody(ccl, CoreType.functionType(JavaType.BOOLEAN));
1746 
1747                         final Value labelResult = processConstantLabel(localTarget, ccl);
1748 
1749                         append(CoreOp.core_yield(labelResult));
1750                         clBodies.add(stack.body);
1751 
1752                         // Pop label
1753                         popBody();
1754                     }
1755 
1756                     localResult = append(JavaOp.conditionalOr(clBodies));
1757                 }
1758 
1759                 append(CoreOp.core_yield(localResult));
1760                 body = stack.body;
1761 
1762                 // Pop labels
1763                 popBody();
1764             } else if (headCl instanceof JCTree.JCDefaultCaseLabel) {
1765                 // @@@ Do we need to model the default label body?
1766                 pushBody(headCl, CoreType.functionType(JavaType.BOOLEAN));
1767 
1768                 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
1769                 body = stack.body;
1770 
1771                 // Pop label
1772                 popBody();
1773             } else {
1774                 throw unsupported(tree);
1775             }
1776 
1777             return body;
1778         }
1779 
1780         private Body.Builder visitCaseBody(JCTree tree, JCTree.JCCase c, FunctionType caseBodyType, boolean isLastCase) {
1781             Body.Builder body = null;
1782             Type yieldType = tree.type != null ? adaptBottom(tree.type) : Type.noType;
1783 
1784             JCTree.JCCaseLabel headCl = c.labels.head;
1785             switch (c.caseKind) {
1786                 case RULE -> {
1787                     pushBody(c.body, caseBodyType);
1788 
1789                     if (c.body instanceof JCTree.JCExpression e) {
1790                         Value bodyVal = toValue(e, yieldType);
1791                         append(CoreOp.core_yield(bodyVal));
1792                     } else if (c.body instanceof JCTree.JCStatement s){ // this includes Block
1793                         // Otherwise there is a yield statement
1794                         Type prevBodyTarget = bodyTarget;
1795                         try {
1796                             bodyTarget = yieldType;
1797                             toValue(s);
1798                         } finally {
1799                             bodyTarget = prevBodyTarget;
1800                         }
1801                         appendTerminating(c.completesNormally ? CoreOp::core_yield : CoreOp::unreachable);
1802                     }
1803                     body = stack.body;
1804 
1805                     // Pop block
1806                     popBody();
1807                 }
1808                 case STATEMENT -> {
1809                     // @@@ Avoid nesting for a single block? Goes against "say what you see"
1810                     // boolean oneBlock = c.stats.size() == 1 && c.stats.head instanceof JCBlock;
1811                     pushBody(c, caseBodyType);
1812 
1813                     scan(c.stats);
1814 
1815                     appendTerminating(c.completesNormally ?
1816                             isLastCase ? CoreOp::core_yield : JavaOp::switchFallthroughOp
1817                             : CoreOp::unreachable);
1818 
1819                     body = stack.body;
1820 
1821                     // Pop block
1822                     popBody();
1823                 }
1824             }
1825             return body;
1826         }
1827 
1828         @Override
1829         public void visitYield(JCTree.JCYield tree) {
1830             Value retVal = toValue(tree.value, bodyTarget);
1831             result = append(JavaOp.java_yield(retVal));
1832         }
1833 
1834         @Override
1835         public void visitWhileLoop(JCTree.JCWhileLoop tree) {
1836             // @@@ Patterns
1837             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1838 
1839             // Push while condition
1840             pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1841             Value last = toValue(cond);
1842             // Yield the boolean result of the condition
1843             last = convert(last, typeElementToType(JavaType.BOOLEAN));
1844             append(CoreOp.core_yield(last));
1845             Body.Builder condition = stack.body;
1846 
1847             // Pop while condition
1848             popBody();
1849 
1850             // Push while body
1851             pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1852             scan(tree.body);
1853             appendTerminating(JavaOp::continue_);
1854             Body.Builder body = stack.body;
1855 
1856             // Pop while body
1857             popBody();
1858 
1859             append(JavaOp.while_(condition, body));
1860             result = null;
1861         }
1862 
1863         @Override
1864         public void visitDoLoop(JCTree.JCDoWhileLoop tree) {
1865             // @@@ Patterns
1866             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
1867 
1868             // Push while body
1869             pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
1870             scan(tree.body);
1871             appendTerminating(JavaOp::continue_);
1872             Body.Builder body = stack.body;
1873 
1874             // Pop while body
1875             popBody();
1876 
1877             // Push while condition
1878             pushBody(cond, CoreType.functionType(JavaType.BOOLEAN));
1879             Value last = toValue(cond);
1880             last = convert(last, typeElementToType(JavaType.BOOLEAN));
1881             // Yield the boolean result of the condition
1882             append(CoreOp.core_yield(last));
1883             Body.Builder condition = stack.body;
1884 
1885             // Pop while condition
1886             popBody();
1887 
1888             append(JavaOp.doWhile(body, condition));
1889             result = null;
1890         }
1891 
1892         @Override
1893         public void visitForeachLoop(JCTree.JCEnhancedForLoop tree) {
1894             // Push expression
1895             pushBody(tree.expr, CoreType.functionType(typeToTypeElement(tree.expr.type)));
1896             Value last = toValue(tree.expr);
1897             // Yield the Iterable result of the expression
1898             append(CoreOp.core_yield(last));
1899             Body.Builder expression = stack.body;
1900 
1901             // Pop expression
1902             popBody();
1903 
1904             JCVariableDecl var = tree.getVariable();
1905             VarType varEType = CoreType.varType(typeToTypeElement(var.type));
1906 
1907             // Push init
1908             // @@@ When lhs assignment is a pattern we embed the pattern match into the init body and
1909             // return the bound variables
1910             Type exprType = types.cvarUpperBound(tree.expr.type);
1911             Type elemtype = types.elemtype(exprType); // perhaps expr is an array?
1912             if (elemtype == null) {
1913                 Type iterableType = types.asSuper(tree.expr.type, syms.iterableType.tsym);
1914                 com.sun.tools.javac.util.List<Type> iterableParams = iterableType.allparams();
1915                 elemtype = iterableParams.isEmpty()
1916                         ? syms.objectType
1917                         : types.wildUpperBound(iterableParams.head);
1918             }
1919             pushBody(var, CoreType.functionType(varEType, typeToTypeElement(elemtype)));
1920             var initVarExpr = convert(stack.block.parameters().get(0), var.type);
1921             Op.Result varEResult = append(CoreOp.var(var.name.toString(), initVarExpr));
1922             append(CoreOp.core_yield(varEResult));
1923             Body.Builder init = stack.body;
1924             // Pop init
1925             popBody();
1926 
1927             // Push body
1928             pushBody(tree.body, CoreType.functionType(JavaType.VOID, varEType));
1929             stack.localToOp.put(var.sym, stack.block.parameters().get(0));
1930 
1931             scan(tree.body);
1932             appendTerminating(JavaOp::continue_);
1933             Body.Builder body = stack.body;
1934             // Pop body
1935             popBody();
1936 
1937             append(JavaOp.enhancedFor(expression, init, body));
1938             result = null;
1939         }
1940 
1941         @Override
1942         public void visitForLoop(JCTree.JCForLoop tree) {
1943             class VarDefScanner extends FilterScanner {
1944                 final List<JCVariableDecl> decls;
1945 
1946                 public VarDefScanner() {
1947                     super(Set.of(Tag.VARDEF));
1948                     this.decls = new ArrayList<>();
1949                 }
1950 
1951                 @Override
1952                 public void visitVarDef(JCVariableDecl tree) {
1953                     decls.add(tree);
1954                 }
1955 
1956                 void mapVarsToBlockArguments() {
1957                     for (int i = 0; i < decls.size(); i++) {
1958                         stack.localToOp.put(decls.get(i).sym, stack.block.parameters().get(i));
1959                     }
1960                 }
1961 
1962                 List<VarType> varTypes() {
1963                     return decls.stream()
1964                             .map(t -> CoreType.varType(typeToTypeElement(t.type)))
1965                             .toList();
1966                 }
1967 
1968                 List<Value> varValues() {
1969                     return decls.stream()
1970                             .map(t -> stack.localToOp.get(t.sym))
1971                             .toList();
1972                 }
1973             }
1974 
1975             // Scan local variable declarations
1976             VarDefScanner vds = new VarDefScanner();
1977             vds.scan(tree.init);
1978             List<VarType> varTypes = vds.varTypes();
1979 
1980             // Push init
1981             if (varTypes.size() > 1) {
1982                 pushBody(null, CoreType.functionType(CoreType.tupleType(varTypes)));
1983                 scan(tree.init);
1984 
1985                 // Capture all local variable declarations in tuple
1986                 append(CoreOp.core_yield(append(CoreOp.tuple(vds.varValues()))));
1987             } else if (varTypes.size() == 1) {
1988                 pushBody(null, CoreType.functionType(varTypes.get(0)));
1989                 scan(tree.init);
1990 
1991                 append(CoreOp.core_yield(vds.varValues().get(0)));
1992             } else {
1993                 pushBody(null, CoreType.FUNCTION_TYPE_VOID);
1994                 scan(tree.init);
1995 
1996                 append(CoreOp.core_yield());
1997             }
1998             Body.Builder init = stack.body;
1999 
2000             // Pop init
2001             popBody();
2002 
2003             // Push cond
2004             pushBody(tree.cond, CoreType.functionType(JavaType.BOOLEAN, varTypes));
2005             if (tree.cond != null) {
2006                 vds.mapVarsToBlockArguments();
2007 
2008                 Value last = toValue(tree.cond);
2009                 // Yield the boolean result of the condition
2010                 append(CoreOp.core_yield(last));
2011             } else {
2012                 append(CoreOp.core_yield(append(CoreOp.constant(JavaType.BOOLEAN, true))));
2013             }
2014             Body.Builder cond = stack.body;
2015 
2016             // Pop cond
2017             popBody();
2018 
2019             // Push update
2020             // @@@ tree.step is a List<JCStatement>
2021             pushBody(null, CoreType.functionType(JavaType.VOID, varTypes));
2022             if (!tree.step.isEmpty()) {
2023                 vds.mapVarsToBlockArguments();
2024 
2025                 scan(tree.step);
2026             }
2027             append(CoreOp.core_yield());
2028             Body.Builder update = stack.body;
2029 
2030             // Pop update
2031             popBody();
2032 
2033             // Push body
2034             pushBody(tree.body, CoreType.functionType(JavaType.VOID, varTypes));
2035             if (tree.body != null) {
2036                 vds.mapVarsToBlockArguments();
2037 
2038                 scan(tree.body);
2039             }
2040             appendTerminating(JavaOp::continue_);
2041             Body.Builder body = stack.body;
2042 
2043             // Pop update
2044             popBody();
2045 
2046             append(JavaOp.for_(init, cond, update, body));
2047             result = null;
2048         }
2049 
2050         @Override
2051         public void visitConditional(JCTree.JCConditional tree) {
2052             List<Body.Builder> bodies = new ArrayList<>();
2053 
2054             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2055 
2056             // Push condition
2057             pushBody(cond,
2058                     CoreType.functionType(JavaType.BOOLEAN));
2059             Value condVal = toValue(cond);
2060             // Yield the boolean result of the condition
2061             append(CoreOp.core_yield(condVal));
2062             bodies.add(stack.body);
2063 
2064             // Pop condition
2065             popBody();
2066 
2067             JCTree.JCExpression truepart = TreeInfo.skipParens(tree.truepart);
2068 
2069             Type condType = adaptBottom(tree.type);
2070 
2071             // Push true body
2072             pushBody(truepart,
2073                     CoreType.functionType(typeToTypeElement(condType)));
2074 
2075             Value trueVal = toValue(truepart, condType);
2076             // Yield the result
2077             append(CoreOp.core_yield(trueVal));
2078             bodies.add(stack.body);
2079 
2080             // Pop true body
2081             popBody();
2082 
2083             JCTree.JCExpression falsepart = TreeInfo.skipParens(tree.falsepart);
2084 
2085             // Push false body
2086             pushBody(falsepart,
2087                     CoreType.functionType(typeToTypeElement(condType)));
2088 
2089             Value falseVal = toValue(falsepart, condType);
2090             // Yield the result
2091             append(CoreOp.core_yield(falseVal));
2092             bodies.add(stack.body);
2093 
2094             // Pop false body
2095             popBody();
2096 
2097             result = append(JavaOp.conditionalExpression(typeToTypeElement(condType), bodies));
2098         }
2099 
2100         private Type condType(JCExpression tree, Type type) {
2101             if (type.hasTag(BOT)) {
2102                 return adaptBottom(tree.type);
2103             } else {
2104                 return type;
2105             }
2106         }
2107 
2108         private Type adaptBottom(Type type) {
2109             return type.hasTag(BOT) ?
2110                     (pt.hasTag(NONE) ? syms.objectType : pt) :
2111                     type;
2112         }
2113 
2114         @Override
2115         public void visitAssert(JCAssert tree) {
2116             // assert <cond:body1> [detail:body2]
2117 
2118             List<Body.Builder> bodies = new ArrayList<>();
2119             JCTree.JCExpression cond = TreeInfo.skipParens(tree.cond);
2120 
2121             // Push condition
2122             pushBody(cond,
2123                     CoreType.functionType(JavaType.BOOLEAN));
2124             Value condVal = toValue(cond);
2125 
2126             // Yield the boolean result of the condition
2127             append(CoreOp.core_yield(condVal));
2128             bodies.add(stack.body);
2129 
2130             // Pop condition
2131             popBody();
2132 
2133             if (tree.detail != null) {
2134                 JCTree.JCExpression detail = TreeInfo.skipParens(tree.detail);
2135 
2136                 pushBody(detail,
2137                         CoreType.functionType(typeToTypeElement(tree.detail.type)));
2138                 Value detailVal = toValue(detail);
2139 
2140                 append(CoreOp.core_yield(detailVal));
2141                 bodies.add(stack.body);
2142 
2143                 //Pop detail
2144                 popBody();
2145             }
2146 
2147             result = append(JavaOp.assert_(bodies));
2148 
2149         }
2150 
2151         @Override
2152         public void visitBlock(JCTree.JCBlock tree) {
2153             if (stack.tree == tree) {
2154                 // Block is associated with the visit of a parent structure
2155                 scan(tree.stats);
2156             } else {
2157                 // Otherwise, independent block structure
2158                 // Push block
2159                 pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2160                 scan(tree.stats);
2161                 appendTerminating(CoreOp::core_yield);
2162                 Body.Builder body = stack.body;
2163 
2164                 // Pop block
2165                 popBody();
2166 
2167                 append(JavaOp.block(body));
2168             }
2169             result = null;
2170         }
2171 
2172         @Override
2173         public void visitSynchronized(JCTree.JCSynchronized tree) {
2174             // Push expr
2175             pushBody(tree.lock, CoreType.functionType(typeToTypeElement(tree.lock.type)));
2176             Value last = toValue(tree.lock);
2177             append(CoreOp.core_yield(last));
2178             Body.Builder expr = stack.body;
2179 
2180             // Pop expr
2181             popBody();
2182 
2183             // Push body block
2184             pushBody(tree.body, CoreType.FUNCTION_TYPE_VOID);
2185             // Scan body block statements
2186             scan(tree.body.stats);
2187             appendTerminating(CoreOp::core_yield);
2188             Body.Builder blockBody = stack.body;
2189 
2190             // Pop body block
2191             popBody();
2192 
2193             append(JavaOp.synchronized_(expr, blockBody));
2194         }
2195 
2196         @Override
2197         public void visitLabelled(JCTree.JCLabeledStatement tree) {
2198             // Push block
2199             pushBody(tree, CoreType.FUNCTION_TYPE_VOID);
2200             // Create constant for label
2201             String labelName = tree.label.toString();
2202             Op.Result label = append(CoreOp.constant(JavaType.J_L_STRING, labelName));
2203             // Set label on body stack
2204             stack.setLabel(labelName, label);
2205             scan(tree.body);
2206             appendTerminating(CoreOp::core_yield);
2207             Body.Builder body = stack.body;
2208 
2209             // Pop block
2210             popBody();
2211 
2212             result = append(JavaOp.labeled(body));
2213         }
2214 
2215         @Override
2216         public void visitTry(JCTree.JCTry tree) {
2217             List<JCVariableDecl> rVariableDecls = new ArrayList<>();
2218             List<TypeElement> rTypes = new ArrayList<>();
2219             Body.Builder resources;
2220             if (!tree.resources.isEmpty()) {
2221                 // Resources body returns a tuple that contains the resource variables/values
2222                 // in order of declaration
2223                 for (JCTree resource : tree.resources) {
2224                     if (resource instanceof JCVariableDecl vdecl) {
2225                         rVariableDecls.add(vdecl);
2226                         rTypes.add(CoreType.varType(typeToTypeElement(vdecl.type)));
2227                     } else {
2228                         rTypes.add(typeToTypeElement(resource.type));
2229                     }
2230                 }
2231 
2232                 // Push resources body
2233                 pushBody(null, CoreType.functionType(CoreType.tupleType(rTypes)));
2234 
2235                 List<Value> rValues = new ArrayList<>();
2236                 for (JCTree resource : tree.resources) {
2237                     if (resource instanceof JCTree.JCExpression e) {
2238                         rValues.add(toValue(e));
2239                     } else if (resource instanceof JCTree.JCStatement s) {
2240                         rValues.add(toValue(s));
2241                     }
2242                 }
2243 
2244                 append(CoreOp.core_yield(append(CoreOp.tuple(rValues))));
2245                 resources = stack.body;
2246 
2247                 // Pop resources body
2248                 popBody();
2249             } else {
2250                 resources = null;
2251             }
2252 
2253             // Push body
2254             // Try body accepts the resource variables (in order of declaration).
2255             List<VarType> rVarTypes = rTypes.stream().<VarType>mapMulti((t, c) -> {
2256                 if (t instanceof VarType vt) {
2257                     c.accept(vt);
2258                 }
2259             }).toList();
2260             pushBody(tree.body, CoreType.functionType(JavaType.VOID, rVarTypes));
2261             for (int i = 0; i < rVariableDecls.size(); i++) {
2262                 stack.localToOp.put(rVariableDecls.get(i).sym, 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, typeToTypeElement(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);
2394                 Value rhs = toValue(tree.rhs, 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(typeToTypeElement(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(typeToTypeElement(t), (char)0);
2512                 case BOOLEAN -> CoreOp.constant(typeToTypeElement(t), false);
2513                 case FLOAT -> CoreOp.constant(typeToTypeElement(t), 0f);
2514                 case LONG -> CoreOp.constant(typeToTypeElement(t), 0L);
2515                 case DOUBLE -> CoreOp.constant(typeToTypeElement(t), 0d);
2516                 default -> CoreOp.constant(typeToTypeElement(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(typeToTypeElement(t), (char)1);
2524                 case FLOAT -> CoreOp.constant(typeToTypeElement(t), 1f);
2525                 case LONG -> CoreOp.constant(typeToTypeElement(t), 1L);
2526                 case DOUBLE -> CoreOp.constant(typeToTypeElement(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.op(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 typeToTypeElement(s.erasure(types));
2787     }
2788 
2789     JavaType typeToTypeElement(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(typeToTypeElement(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, typeToTypeElement(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                             typeToTypeElement(ub)) :
2821                     JavaType.typeVar(t.tsym.name.toString(),
2822                             (jdk.incubator.code.dialect.java.ClassType)symbolToErasedDesc(t.tsym.owner),
2823                             typeToTypeElement(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(typeToTypeElement(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(typeToTypeElement(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 typeElementToType(TypeElement 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::typeElementToType).orElse(Type.noType);
2864                 com.sun.tools.javac.util.List<Type> typeArgs = com.sun.tools.javac.util.List.from(ct.typeArguments()).map(this::typeElementToType);
2865                 yield new Type.ClassType(enclosing, typeArgs, typeElementToType(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(typeElementToType(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                 typeToTypeElement(s.owner.erasure(types)),
2890                 s.name.toString(),
2891                 typeToTypeElement(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                 typeToTypeElement(s.owner.erasure(types)),
2905                 s.name.toString(),
2906                 typeToTypeElement(erasedType.getReturnType()),
2907                 erasedType.getParameterTypes().stream().map(this::typeToTypeElement).toArray(TypeElement[]::new));
2908     }
2909 
2910     FunctionType typeToFunctionType(Type t) {
2911         return CoreType.functionType(
2912                 typeToTypeElement(t.getReturnType()),
2913                 t.getParameterTypes().stream().map(this::typeToTypeElement).toArray(TypeElement[]::new));
2914     }
2915 
2916     RecordTypeRef symbolToRecordTypeRef(Symbol.ClassSymbol s) {
2917         TypeElement recordType = typeToTypeElement(s.type);
2918         List<RecordTypeRef.ComponentRef> components = s.getRecordComponents().stream()
2919                 .map(rc -> new RecordTypeRef.ComponentRef(typeToTypeElement(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         // TypeElement 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 }