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