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