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