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