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