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