1 /*
   2  * Copyright (c) 2024, 2026, 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.dialect.java;
  27 
  28 import java.lang.constant.ClassDesc;
  29 import jdk.incubator.code.*;
  30 import jdk.incubator.code.extern.DialectFactory;
  31 import jdk.incubator.code.dialect.core.*;
  32 import jdk.incubator.code.extern.ExternalizedOp;
  33 import jdk.incubator.code.extern.OpFactory;
  34 import jdk.incubator.code.internal.ArithmeticAndConvOpImpls;
  35 import jdk.incubator.code.internal.BranchTarget;
  36 import jdk.incubator.code.internal.OpDeclaration;
  37 
  38 import java.lang.invoke.MethodHandles;
  39 import java.lang.invoke.VarHandle;
  40 import java.lang.reflect.Field;
  41 import java.lang.reflect.Modifier;
  42 import java.util.*;
  43 import java.util.concurrent.atomic.AtomicBoolean;
  44 import java.util.function.BiFunction;
  45 import java.util.function.Consumer;
  46 import java.util.function.Function;
  47 import java.util.function.Predicate;
  48 import java.util.stream.Stream;
  49 
  50 import static jdk.incubator.code.Op.Lowerable.*;
  51 import static jdk.incubator.code.dialect.core.CoreOp.*;
  52 import static jdk.incubator.code.dialect.java.JavaType.*;
  53 import static jdk.incubator.code.dialect.java.JavaType.VOID;
  54 import static jdk.incubator.code.internal.ArithmeticAndConvOpImpls.*;
  55 
  56 /**
  57  * The top-level operation class for Java operations.
  58  * <p>
  59  * A code model, produced by the Java compiler from Java program source, may consist of core operations and Java
  60  * operations. Such a model represents the same Java program and preserves the program meaning as defined by the
  61  * Java Language Specification.
  62  * <p>
  63  * Java operations model specific Java language constructs or Java program behavior. Some Java operations model
  64  * structured control flow and nested code. These operations are transformable, commonly referred to as lowering, into
  65  * a sequence of other core or Java operations. Those that implement {@link Op.Lowerable} can transform themselves and
  66  * will transform associated operations that are not explicitly lowerable.
  67  * <p>
  68  * A code model, produced by the Java compiler from source, and consisting of core operations and Java operations
  69  * can be transformed to one consisting only of non-lowerable operations, where all lowerable operations are lowered.
  70  * This transformation preserves programming meaning. The resulting lowered code model also represents the same Java
  71  * program.
  72  */
  73 public sealed abstract class JavaOp extends Op {
  74 
  75     JavaOp(Op that, CodeContext cc) {
  76         super(that, cc);
  77     }
  78 
  79     JavaOp(List<? extends Value> operands) {
  80         super(operands);
  81     }
  82 
  83     @Override
  84     public String externalizeOpName() {
  85         OpDeclaration opDecl = this.getClass().getDeclaredAnnotation(OpDeclaration.class);
  86         assert opDecl != null : this.getClass().getName();
  87         return opDecl.value();
  88     }
  89 
  90     /**
  91      * An operation that models a Java expression
  92      *
  93      * @jls 15 Expressions
  94      */
  95     public sealed interface JavaExpression permits
  96             ArithmeticOperation,
  97             ArrayAccessOp.ArrayLoadOp,
  98             ArrayAccessOp.ArrayStoreOp,
  99             ArrayLengthOp,
 100             CastOp,
 101             ConvOp,
 102             ConcatOp,
 103             ConstantOp,
 104             FieldAccessOp.FieldLoadOp,
 105             FieldAccessOp.FieldStoreOp,
 106             InstanceOfOp,
 107             InvokeOp,
 108             LambdaOp,
 109             NewOp,
 110             VarAccessOp.VarLoadOp,
 111             VarAccessOp.VarStoreOp,
 112             ConditionalExpressionOp,
 113             JavaConditionalOp,
 114             SwitchExpressionOp {
 115 
 116         /**
 117          * Evaluates an operation result whose operation models a constant expression.
 118          * <p>
 119          * This method deviates from the language specification of a constant expression in the following cases.
 120          * <ul>
 121          * <li>A name that refers to a final class variable of primitive type or type String, is evaluated as if a constant variable.
 122          * Such referral is modeled as field load operation to a static final field. At runtime, it is not possible to
 123          * determine if that class variable, the static final field, is initialized with a constant expression.
 124          * <li>A name that refers to constant variable that is an instance variable is evaluated as if it is a
 125          * non-constant variable, and therefore any expression referring to such a variable is not considered a constant
 126          * expression.
 127          * Such referral is modeled as field load operation to a non-static final field. At runtime, it is not possible
 128          * to access the value of the field, since the instance of the class that has the field that is the instance
 129          * variable is unknown. And, same as the first case, at runtime it is not possible to determine if the variable
 130          * is initialized with a constant expression, whose value is independent of the class instance.
 131          * <li>An effectively final local variable is evaluated as if a constant variable.
 132          * Such a variable is modelled as a variable operation, which does not model if the variable is a final
 133          * variable.
 134          *</ul>
 135          *
 136          * @param l the {@link MethodHandles.Lookup} to provide name resolution and access control context
 137          * @param v the value to evaluate
 138          * @return an {@code Optional} containing the evaluated result, otherwise an empty {@code Optional} if the value
 139          * is not an instance of {@link Op.Result} or the operation does not model a constant expression
 140          * @throws IllegalArgumentException if a failure to resolve
 141          * @jls 15.29 Constant Expressions
 142          *}
 143          */
 144         static Optional<Object> evaluate(MethodHandles.Lookup l, Value v) {
 145             return new ConstantExpressionEvaluator(l).evaluate(v);
 146         }
 147 
 148         /**
 149          * Evaluates an operation that models a constant expression.
 150          * <p>
 151          * This method deviates from the language specification of a constant expression in the following cases.
 152          * <ul>
 153          * <li>A name that refers to a final class variable of primitive type or type String, is evaluated as if a constant variable.
 154          * Such referral is modeled as field load operation to a static final field. At runtime, it is not possible to
 155          * determine if that class variable, the static final field, is initialized with a constant expression.
 156          * <li>A name that refers to constant variable that is an instance variable is evaluated as if it is a
 157          * non-constant variable, and therefore any expression referring to such a variable is not considered a constant
 158          * expression.
 159          * Such referral is modeled as field load operation to a non-static final field. At runtime, it is not possible
 160          * to access the value of the field, since the instance of the class that has the field that is the instance
 161          * variable is unknown. And, same as the first case, at runtime it is not possible to determine if the variable
 162          * is initialized with a constant expression, whose value is independent of the class instance.
 163          * <li>An effectively final local variable is evaluated as if a constant variable.
 164          * Such a variable is modelled as a variable operation, which does not model if the variable is a final
 165          * variable.
 166          *</ul>
 167          *
 168          * @param l the {@link MethodHandles.Lookup} to provide name resolution and access control context
 169          * @param op the operation to evaluate
 170          * @param <T> the type of the operation
 171          * @return an {@code Optional} containing the evaluated result, otherwise an empty {@code Optional} if the
 172          * operation does not model a constant expression
 173          * @throws IllegalArgumentException if a failure to resolve
 174          * @jls 15.29 Constant Expressions
 175          */
 176         static <T extends Op & JavaExpression> Optional<Object> evaluate(MethodHandles.Lookup l, T op) {
 177             return new ConstantExpressionEvaluator(l).evaluate(op);
 178         }
 179 
 180         class ConstantExpressionEvaluator {
 181             private final MethodHandles.Lookup l;
 182             private final Map<Value, Object> m = new HashMap<>();
 183 
 184             ConstantExpressionEvaluator(MethodHandles.Lookup l) {
 185                 this.l = l;
 186             }
 187 
 188             <T extends Op & JavaExpression> Optional<Object> evaluate(T op) {
 189                 try {
 190                     Object v = this.eval(op);
 191                     return Optional.ofNullable(v);
 192                 } catch (NonConstantExpression e) {
 193                     return Optional.empty();
 194                 }
 195             }
 196 
 197             Optional<Object> evaluate(Value v) {
 198                 try {
 199                     Object o = this.eval(v);
 200                     return Optional.ofNullable(o);
 201                 } catch (NonConstantExpression e) {
 202                     return Optional.empty();
 203                 }
 204             }
 205 
 206             private Object eval(Op op) {
 207                 if (m.containsKey(op.result())) {
 208                     return m.get(op.result());
 209                 }
 210                 Object r = switch (op) {
 211                     case ConstantOp cop when isConstant(cop) -> {
 212                         Object v = cop.value();
 213                         yield v instanceof String s ? s.intern() : v;
 214                     }
 215                     case VarAccessOp.VarLoadOp varLoadOp when varLoadOp.operands().getFirst() instanceof Result &&
 216                             isConstant(varLoadOp.varOp()) -> eval(varLoadOp.varOp().initOperand());
 217                     case ConvOp _ -> {
 218                         // we expect cast to primitive type
 219                         var v = eval(op.operands().getFirst());
 220                         yield ArithmeticAndConvOpImpls.evaluate(op, List.of(v));
 221                     }
 222                     case CastOp castOp -> {
 223                         // we expect cast to String
 224                         Value operand = castOp.operands().getFirst();
 225                         if (!castOp.resultType().equals(J_L_STRING) || !operand.type().equals(J_L_STRING)) {
 226                             throw new NonConstantExpression();
 227                         }
 228                         Object v = eval(operand);
 229                         if (!(v instanceof String s)) {
 230                             throw new NonConstantExpression();
 231                         }
 232                         yield s;
 233                     }
 234                     case ConcatOp concatOp -> {
 235                         Object first = eval(concatOp.operands().getFirst());
 236                         Object second = eval(concatOp.operands().getLast());
 237                         yield (first.toString() + second).intern();
 238                     }
 239                     case FieldAccessOp.FieldLoadOp fieldLoadOp -> {
 240                         Field field;
 241                         VarHandle vh;
 242                         try {
 243                             field = fieldLoadOp.fieldReference().resolveToField(l);
 244                             vh = fieldLoadOp.fieldReference().resolveToHandle(l);
 245                         } catch (ReflectiveOperationException | IllegalArgumentException _) {
 246                             // we cann't reflectivelly get the field
 247                             throw new NonConstantExpression();
 248                         }
 249                         // Requirement: the field must be a constant variable.
 250                         // Current checks:
 251                         // 1) The field is declared final.
 252                         // 2) The field type is a primitive or String.
 253                         // Missing check:
 254                         // 3) Verify the field is initialized and the initializer is a constant expression.
 255                         if ((field.getModifiers() & Modifier.FINAL) == 0 ||
 256                                 !isConstantType(fieldLoadOp.fieldReference().type())) {
 257                             throw new NonConstantExpression();
 258                         }
 259                         if ((field.getModifiers() & Modifier.STATIC) != 0) {
 260                             Object v;
 261                             try {
 262                                 v = vh.get();
 263                             } catch (Throwable t) {
 264                                 throw new NonConstantExpression();
 265                             }
 266                             if (!isConstantValue(v)) {
 267                                 throw new NonConstantExpression();
 268                             }
 269                             yield v instanceof String s ? s.intern() : v;
 270                         } else {
 271                             // we can't get the value of an instance field from the model
 272                             // we need the value of the receiver
 273                             throw new NonConstantExpression();
 274                         }
 275                     }
 276                     case ArithmeticOperation _ -> {
 277                         List<Object> values = op.operands().stream().map(this::eval).toList();
 278                         yield ArithmeticAndConvOpImpls.evaluate(op, values);
 279                     }
 280                     case ConditionalExpressionOp _ -> {
 281                         boolean p = evalBoolean(op.bodies().get(0));
 282                         Object t = eval(op.bodies().get(1));
 283                         Object f = eval(op.bodies().get(2));
 284                         yield p ? t : f;
 285                     }
 286                     case ConditionalAndOp _ -> {
 287                         boolean left = evalBoolean(op.bodies().get(0));
 288                         boolean right = evalBoolean(op.bodies().get(1));
 289                         yield left && right;
 290                     }
 291                     case ConditionalOrOp _ -> {
 292                         boolean left = evalBoolean(op.bodies().get(0));
 293                         boolean right = evalBoolean(op.bodies().get(1));
 294                         yield left || right;
 295                     }
 296                     default -> throw new NonConstantExpression();
 297                 };
 298                 m.put(op.result(), r);
 299                 return r;
 300             }
 301 
 302             private Object eval(Value v) {
 303                 if (v.declaringElement() instanceof JavaExpression e) {
 304                     return eval((Op & JavaExpression) e);
 305                 }
 306                 throw new NonConstantExpression();
 307             }
 308 
 309             private Object eval(Body body) throws NonConstantExpression {
 310                 if (body.blocks().size() != 1 ||
 311                         !(body.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
 312                         yop.yieldValue() == null ||
 313                         !isConstantType(yop.yieldValue().type())) {
 314                     throw new NonConstantExpression();
 315                 }
 316                 return eval(yop.yieldValue());
 317             }
 318 
 319             private boolean evalBoolean(Body body) throws NonConstantExpression {
 320                 Object eval = eval(body);
 321                 if (!(eval instanceof Boolean b)) {
 322                     throw new NonConstantExpression();
 323                 }
 324                 return b;
 325             }
 326 
 327             private static boolean isConstant(CoreOp.ConstantOp op) {
 328                 return isConstantType(op.resultType()) && isConstantValue(op.value());
 329             }
 330 
 331             private static boolean isConstant(VarOp op) {
 332                 // Requirement: the local variable must be a constant variable.
 333                 // Current checks:
 334                 // 1) The variable is initialized, and the initializer is a constant expression.
 335                 // 2) The variable type is a primitive or String.
 336                 // Missing check:
 337                 // 3) Ensure the variable is declared final
 338                 return isConstantType(op.varValueType()) &&
 339                         !op.isUninitialized() &&
 340                         // @@@ Add to VarOp
 341                         op.result().uses().stream().noneMatch(u -> u.op() instanceof CoreOp.VarAccessOp.VarStoreOp);
 342             }
 343 
 344             private static boolean isConstantValue(Object o) {
 345                 return switch (o) {
 346                     case String _ -> true;
 347                     case Boolean _, Byte _, Short _, Character _, Integer _, Long _, Float _, Double _ -> true;
 348                     case null, default -> false;
 349                 };
 350             }
 351 
 352             private static boolean isConstantType(CodeType e) {
 353                 return (e instanceof PrimitiveType && !VOID.equals(e)) || J_L_STRING.equals(e);
 354             }
 355         }
 356     }
 357 
 358     /**
 359      * An operation that models a Java statement.
 360      *
 361      * @jls 14.5 Statements
 362      */
 363     public sealed interface JavaStatement permits
 364             ArrayAccessOp.ArrayStoreOp,
 365             AssertOp,
 366             FieldAccessOp.FieldStoreOp,
 367             InvokeOp,
 368             NewOp,
 369             ReturnOp,
 370             ThrowOp,
 371             VarAccessOp.VarStoreOp,
 372             VarOp,
 373             BlockOp,
 374             DoWhileOp,
 375             EnhancedForOp,
 376             ForOp,
 377             IfOp,
 378             StatementTargetOp,
 379             LabeledOp,
 380             SynchronizedOp,
 381             TryOp,
 382             WhileOp,
 383             YieldOp,
 384             SwitchStatementOp {
 385     }
 386 
 387     /**
 388      * An operation characteristic indicating the operation's behavior may be emulated using Java reflection.
 389      * A reference is derived from or declared by the operation that can be resolved at runtime to
 390      * an instance of a reflective handle or member. That handle or member can be operated on to
 391      * emulate the operation's behavior, specifically as bytecode behavior.
 392      */
 393     public sealed interface ReflectiveOp {
 394     }
 395 
 396     /**
 397      * An operation that performs access.
 398      */
 399     public sealed interface AccessOp permits
 400         CoreOp.VarAccessOp,
 401         FieldAccessOp,
 402         ArrayAccessOp {
 403     }
 404 
 405 
 406 
 407     /**
 408      * The lambda operation, that can model Java language lambda expressions.
 409      * <p>
 410      * Lambda operations are associated with a {@linkplain #functionalInterface() functional interface type}.
 411      * They feature one body, the {@linkplain #body() function body}.
 412      * The result type of a lambda operation is its functional interface type.
 413      * <p>
 414      * The function body takes as many arguments as the function type associated with the functional interface type.
 415      * The function body yields a value if that function type has a non-{@linkplain JavaType#VOID void} return type.
 416      * <p>
 417      * Lambda operations can also model Java language method reference expressions. A method reference is modeled as a
 418      * lambda operation whose function body forwards its parameters to a corresponding {@link InvokeOp}, and that
 419      * yields the result (if any) of that operation.
 420      * <p>
 421      * Some lambda operations are <em>reflectable</em> (see {@link Reflect}), meaning their code model is persisted at
 422      * runtime.
 423      *
 424      * @jls 15.27 Lambda Expressions
 425      * @jls 15.13 Method Reference Expressions
 426      * @jls 9.8 Functional Interfaces
 427      * @jls 9.9 Function Types
 428      */
 429     @OpDeclaration(LambdaOp.NAME)
 430     public static final class LambdaOp extends JavaOp
 431             implements Invokable, Lowerable, JavaExpression {
 432 
 433         /**
 434          * A builder for constructing a lambda operation.
 435          */
 436         public static class Builder {
 437             final Body.Builder connectedAncestorBody;
 438             final FunctionType signature;
 439             final CodeType functionalInterface;
 440             final boolean isReflectable;
 441 
 442             Builder(Body.Builder connectedAncestorBody, FunctionType signature, CodeType functionalInterface) {
 443                 this.connectedAncestorBody = connectedAncestorBody;
 444                 this.signature = signature;
 445                 this.functionalInterface = functionalInterface;
 446                 this.isReflectable = false;
 447             }
 448 
 449             Builder(Body.Builder connectedAncestorBody, FunctionType signature, CodeType functionalInterface,
 450                     boolean isReflectable) {
 451                 this.connectedAncestorBody = connectedAncestorBody;
 452                 this.signature = signature;
 453                 this.functionalInterface = functionalInterface;
 454                 this.isReflectable = isReflectable;
 455             }
 456 
 457             /**
 458              * Completes the lambda operation by adding the function body.
 459              *
 460              * @param c a consumer that populates the function body
 461              * @return the completed lambda operation
 462              */
 463             public LambdaOp body(Consumer<Block.Builder> c) {
 464                 Body.Builder body = Body.Builder.of(connectedAncestorBody, signature);
 465                 c.accept(body.entryBlock());
 466                 return new LambdaOp(functionalInterface, body, isReflectable);
 467             }
 468 
 469             /**
 470              * Returns a builder that constructs a reflectable lambda operation.
 471              *
 472              * @return this builder
 473              * @see Reflect
 474              */
 475             public Builder reflectable() {
 476                 return new Builder(connectedAncestorBody, signature, functionalInterface, true);
 477             }
 478         }
 479 
 480         static final String NAME = "lambda";
 481         static final String ATTRIBUTE_LAMBDA_IS_REFLECTABLE = NAME + ".isReflectable";
 482 
 483         final CodeType functionalInterface;
 484         final Body body;
 485         final boolean isReflectable;
 486 
 487         LambdaOp(ExternalizedOp def) {
 488             boolean isReflectable = def.extractAttributeValue(ATTRIBUTE_LAMBDA_IS_REFLECTABLE,
 489                     false, v -> switch (v) {
 490                         case Boolean b -> b;
 491                         case null, default -> false;
 492                     });
 493 
 494             this(def.resultType(), def.bodyDefinitions().get(0), isReflectable);
 495         }
 496 
 497         LambdaOp(LambdaOp that, CodeContext cc, CodeTransformer ct) {
 498             super(that, cc);
 499 
 500             this.functionalInterface = that.functionalInterface;
 501             this.body = that.body.transform(cc, ct).build(this);
 502             this.isReflectable = that.isReflectable;
 503         }
 504 
 505         @Override
 506         public LambdaOp transform(CodeContext cc, CodeTransformer ct) {
 507             return new LambdaOp(this, cc, ct);
 508         }
 509 
 510         LambdaOp(CodeType functionalInterface, Body.Builder bodyC, boolean isReflectable) {
 511             super(List.of());
 512 
 513             this.functionalInterface = functionalInterface;
 514             this.body = bodyC.build(this);
 515             this.isReflectable = isReflectable;
 516         }
 517 
 518         @Override
 519         public List<Body> bodies() {
 520             return List.of(body);
 521         }
 522 
 523         /**
 524          * {@return the functional interface type modeled by this lambda operation}
 525          */
 526         public CodeType functionalInterface() {
 527             return functionalInterface;
 528         }
 529 
 530         @Override
 531         public Body body() {
 532             return body;
 533         }
 534 
 535         @Override
 536         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> _ignore) {
 537             // Isolate body with respect to ancestor transformations
 538             b.withContextAndTransformer(b.context(), CodeTransformer.LOWERING_TRANSFORMER).add(this);
 539             return b;
 540         }
 541 
 542         @Override
 543         public CodeType resultType() {
 544             return functionalInterface();
 545         }
 546 
 547         /**
 548          * {@return whether this lambda operation is reflectable}
 549          * @see Reflect
 550          */
 551         public boolean isReflectable() {
 552             return isReflectable;
 553         }
 554 
 555         @Override
 556         public Map<String, Object> externalize() {
 557             return Map.of(ATTRIBUTE_LAMBDA_IS_REFLECTABLE, isReflectable);
 558         }
 559 
 560         /**
 561          * Determines if this lambda operation could have originated from a
 562          * method reference declared in Java source code.
 563          * <p>
 564          * Such a lambda operation is one with the following constraints:
 565          * <ol>
 566          *     <li>Zero or one captured value (assuming correspondence to the {@code this} variable).
 567          *     <li>A body with only one (entry) block that contains only variable declaration
 568          *     operations, variable load operations, invoke operations to box or unbox
 569          *     primitive values, a single invoke operation to the method that is
 570          *     referenced, and a return operation.
 571          *     <li>if the return operation returns a non-void result then that result is,
 572          *     or uniquely depends on, the result of the referencing invoke operation.
 573          *     <li>If the lambda operation captures one value then the first operand corresponds
 574          *     to captured the value, and subsequent operands of the referencing invocation
 575          *     operation are, or uniquely depend on, the lambda operation's parameters, in order.
 576          *     Otherwise, the first and subsequent operands of the referencing invocation
 577          *     operation are, or uniquely depend on, the lambda operation's parameters, in order.
 578          * </ol>
 579          * A value, V2, uniquely depends on another value, V1, if the graph of what V2 depends on
 580          * contains only nodes with single edges terminating in V1, and the graph of what depends on V1
 581          * is bidirectionally equal to the graph of what V2 depends on.
 582          *
 583          * @return the invocation operation to the method referenced by the lambda
 584          * operation, otherwise empty.
 585          */
 586         public Optional<InvokeOp> methodReference() {
 587             // Single block
 588             if (body().blocks().size() > 1) {
 589                 return Optional.empty();
 590             }
 591 
 592             // Zero or one (this) capture
 593             List<Value> cvs = capturedValues();
 594             if (cvs.size() > 1) {
 595                 return Optional.empty();
 596             }
 597 
 598             Map<Value, Value> valueMapping = new HashMap<>();
 599             InvokeOp methodRefInvokeOp = extractMethodInvoke(valueMapping, body().entryBlock().ops());
 600             if (methodRefInvokeOp == null) {
 601                 return Optional.empty();
 602             }
 603 
 604             // Lambda's parameters map in encounter order with the invocation's operands
 605             List<Value> lambdaParameters = new ArrayList<>();
 606             if (cvs.size() == 1) {
 607                 lambdaParameters.add(cvs.getFirst());
 608             }
 609             lambdaParameters.addAll(parameters());
 610             List<Value> methodRefOperands = methodRefInvokeOp.operands().stream().map(valueMapping::get).toList();
 611             if (!lambdaParameters.equals(methodRefOperands)) {
 612                 return Optional.empty();
 613             }
 614 
 615             return Optional.of(methodRefInvokeOp);
 616         }
 617 
 618         /**
 619          * Determines if this lambda operation contains a direct invocation of a method.
 620          * <p>
 621          * Such a lambda operation is one with the following constraints:
 622          * <ol>
 623          *     <li>A body with only one (entry) block that contains only variable declaration
 624          *     operations, variable load operations, invoke operations to box or unbox
 625          *     primitive values, a single invoke operation to the method that is
 626          *     referenced, and a return operation.
 627          *     <li>if the return operation returns a non-void result then that result is,
 628          *     or uniquely depends on, the result of the referencing invoke operation.
 629          * </ol>
 630          * A value, V2, uniquely depends on another value, V1, if the graph of what V2 depends on
 631          * contains only nodes with single edges terminating in V1, and the graph of what depends on V1
 632          * is bidirectionally equal to the graph of what V2 depends on.
 633          *
 634          * @return the invocation operation to the method referenced by the lambda
 635          * operation, otherwise empty.
 636          */
 637         public Optional<InvokeOp> directInvocation() {
 638             // Single block
 639             if (body().blocks().size() > 1) {
 640                 return Optional.empty();
 641             }
 642 
 643             Map<Value, Value> valueMapping = new HashMap<>();
 644             InvokeOp methodRefInvokeOp = extractMethodInvoke(valueMapping, body().entryBlock().ops());
 645             if (methodRefInvokeOp == null) {
 646                 return Optional.empty();
 647             }
 648 
 649             return Optional.of(methodRefInvokeOp);
 650         }
 651 
 652         /**
 653          * Converts this lambda operation to an equivalent function operation.
 654          *
 655          * @param lambdaName the name to use for the resulting function (may be empty, or {@code null})
 656          * @return a function operation that models this lambda
 657          */
 658         public CoreOp.FuncOp toFuncOp(String lambdaName) {
 659             if (lambdaName == null) lambdaName = "";
 660             List<CodeType> parameters = new ArrayList<>(this.invokableSignature().parameterTypes());
 661             for (Value v : this.capturedValues()) {
 662                 CodeType capturedType = v.type() instanceof VarType varType ? varType.valueType() : v.type();
 663                 parameters.add(capturedType);
 664             }
 665             return CoreOp.func(lambdaName, CoreType.functionType(this.invokableSignature().returnType(), parameters)).body(builder -> {
 666                 int idx = this.invokableSignature().parameterTypes().size();
 667                 for (Value v : capturedValues()) {
 668                     Block.Parameter p = builder.parameters().get(idx++);
 669                     Value functionValue = v.type() instanceof VarType ? builder.add(CoreOp.var(p)) : p;
 670                     builder.context().mapValue(v, functionValue);
 671                 }
 672                 List<Block.Parameter> outputValues = builder.parameters().subList(0, this.invokableSignature().parameterTypes().size());
 673                 builder.transformBody(this.body(), outputValues, CodeTransformer.COPYING_TRANSFORMER);
 674             });
 675         }
 676 
 677         static InvokeOp extractMethodInvoke(Map<Value, Value> valueMapping, List<Op> ops) {
 678             InvokeOp methodRefInvokeOp = null;
 679             for (Op op : ops) {
 680                 switch (op) {
 681                     case VarOp varOp -> {
 682                         if (isValueUsedWithOp(varOp.result(), o -> o instanceof VarAccessOp.VarStoreOp)) {
 683                             return null;
 684                         }
 685                     }
 686                     case VarAccessOp.VarLoadOp varLoadOp -> {
 687                         Value v = varLoadOp.varOp().operands().getFirst();
 688                         valueMapping.put(varLoadOp.result(), valueMapping.getOrDefault(v, v));
 689                     }
 690                     case InvokeOp iop when isBoxOrUnboxInvocation(iop) -> {
 691                         Value v = iop.operands().getFirst();
 692                         valueMapping.put(iop.result(), valueMapping.getOrDefault(v, v));
 693                     }
 694                     case InvokeOp iop -> {
 695                         if (methodRefInvokeOp != null) {
 696                             return null;
 697                         }
 698 
 699                         for (Value o : iop.operands()) {
 700                             valueMapping.put(o, valueMapping.getOrDefault(o, o));
 701                         }
 702                         methodRefInvokeOp = iop;
 703                     }
 704                     case ReturnOp rop -> {
 705                         if (methodRefInvokeOp == null) {
 706                             return null;
 707                         }
 708                         Value r = rop.returnValue();
 709                         if (r == null) break;
 710                         if (!(valueMapping.getOrDefault(r, r) instanceof Result invokeResult)) {
 711                             return null;
 712                         }
 713                         if (invokeResult.op() != methodRefInvokeOp) {
 714                             return null;
 715                         }
 716                         assert methodRefInvokeOp.result().uses().size() == 1;
 717                     }
 718                     default -> {
 719                         return null;
 720                     }
 721                 }
 722             }
 723 
 724             return methodRefInvokeOp;
 725         }
 726 
 727         private static boolean isValueUsedWithOp(Value value, Predicate<Op> opPredicate) {
 728             for (Result user : value.uses()) {
 729                 if (opPredicate.test(user.op())) {
 730                     return true;
 731                 }
 732             }
 733             return false;
 734         }
 735 
 736         // @@@ Move to functionality on JavaType(s)
 737         static final Set<String> UNBOX_NAMES = Set.of(
 738                 "byteValue",
 739                 "shortValue",
 740                 "charValue",
 741                 "intValue",
 742                 "longValue",
 743                 "floatValue",
 744                 "doubleValue",
 745                 "booleanValue");
 746 
 747         private static boolean isBoxOrUnboxInvocation(InvokeOp iop) {
 748             MethodRef mr = iop.invokeReference();
 749             return mr.refType() instanceof ClassType ct && ct.unbox().isPresent() &&
 750                     (UNBOX_NAMES.contains(mr.name()) || mr.name().equals("valueOf"));
 751         }
 752     }
 753 
 754     /**
 755      * The throw operation, that can model the Java language throw statement.
 756      * <p>
 757      * A throw operation is a body-terminating operation that features one operand, the value being thrown.
 758      * <p>
 759      * The result type of a throw operation is {@link JavaType#VOID}.
 760      *
 761      * @jls 14.18 The throw Statement
 762      */
 763     @OpDeclaration(ThrowOp.NAME)
 764     public static final class ThrowOp extends JavaOp
 765             implements BodyTerminating, JavaStatement {
 766         static final String NAME = "throw";
 767 
 768         ThrowOp(ExternalizedOp def) {
 769             if (def.operands().size() != 1) {
 770                 throw new IllegalArgumentException("Operation must have one operand " + def.name());
 771             }
 772 
 773             this(def.operands().get(0));
 774         }
 775 
 776         ThrowOp(ThrowOp that, CodeContext cc) {
 777             super(that, cc);
 778         }
 779 
 780         @Override
 781         public ThrowOp transform(CodeContext cc, CodeTransformer ct) {
 782             return new ThrowOp(this, cc);
 783         }
 784 
 785         ThrowOp(Value e) {
 786             super(List.of(e));
 787         }
 788 
 789         /**
 790          * {@return the value being thrown}
 791          */
 792         public Value argumentOperand() {
 793             return operands().get(0);
 794         }
 795 
 796         @Override
 797         public CodeType resultType() {
 798             return VOID;
 799         }
 800     }
 801 
 802     /**
 803      * The assertion operation, that can model Java language assert statements.
 804      * <p>
 805      * Assert operations feature one or two bodies. The first body, called the <em>predicate body</em>, models the
 806      * assertion condition. If present, the second body, called the <em>details body</em>, models the detail
 807      * expression.
 808      * <p>
 809      * The predicate body should accept no arguments and yield a {@link JavaType#BOOLEAN} value.
 810      * If present, the details body should accept no arguments and yield a value.
 811      * <p>
 812      * The result type of an assert operation is {@link JavaType#VOID}.
 813      *
 814      * @jls 14.10 The assert Statement
 815      */
 816     @OpDeclaration(AssertOp.NAME)
 817     public static final class AssertOp extends JavaOp
 818             implements Nested, JavaStatement {
 819         static final String NAME = "assert";
 820 
 821         private final List<Body> bodies;
 822 
 823         AssertOp(ExternalizedOp def) {
 824             this(def.bodyDefinitions());
 825         }
 826 
 827         AssertOp(List<Body.Builder> bodies) {
 828             super(List.of());
 829 
 830             if (bodies.size() != 1 && bodies.size() != 2) {
 831                 throw new IllegalArgumentException("Assert must have one or two bodies.");
 832             }
 833             this.bodies = bodies.stream().map(b -> b.build(this)).toList();
 834         }
 835 
 836         AssertOp(AssertOp that, CodeContext cc, CodeTransformer ct) {
 837             super(that, cc);
 838             this.bodies = that.bodies.stream().map(b -> b.transform(cc, ct).build(this)).toList();
 839         }
 840 
 841         @Override
 842         public Op transform(CodeContext cc, CodeTransformer ct) {
 843             return new AssertOp(this, cc, ct);
 844         }
 845 
 846         @Override
 847         public CodeType resultType() {
 848             return VOID;
 849         }
 850 
 851         @Override
 852         public List<Body> bodies() {
 853             return bodies;
 854         }
 855 
 856         /**
 857          * {@return the predicate body}
 858          */
 859         public Body predicateBody() {
 860             return bodies.get(0);
 861         }
 862 
 863         /**
 864          * {@return the details body, or {@code null} if not present}
 865          */
 866         public Body detailsBody() {
 867             return bodies.size() == 2 ? bodies.get(1) : null;
 868         }
 869     }
 870 
 871     /**
 872      * A monitor operation.
 873      */
 874     public sealed abstract static class MonitorOp extends JavaOp {
 875         MonitorOp(MonitorOp that, CodeContext cc) {
 876             super(that, cc);
 877         }
 878 
 879         MonitorOp(Value monitor) {
 880             super(List.of(monitor));
 881         }
 882 
 883         /**
 884          * {@return the monitor value}
 885          */
 886         public Value monitorOperand() {
 887             return operands().getFirst();
 888         }
 889 
 890         @Override
 891         public CodeType resultType() {
 892             return VOID;
 893         }
 894 
 895         /**
 896          * The monitor enter operation.
 897          */
 898         @OpDeclaration(MonitorEnterOp.NAME)
 899         public static final class MonitorEnterOp extends MonitorOp {
 900             static final String NAME = "monitor.enter";
 901 
 902             MonitorEnterOp(ExternalizedOp def) {
 903                 if (def.operands().size() != 1) {
 904                     throw new IllegalArgumentException("Operation must have one operand " + def.name());
 905                 }
 906 
 907                 this(def.operands().get(0));
 908             }
 909 
 910             MonitorEnterOp(MonitorEnterOp that, CodeContext cc) {
 911                 super(that, cc);
 912             }
 913 
 914             @Override
 915             public MonitorEnterOp transform(CodeContext cc, CodeTransformer ct) {
 916                 return new MonitorEnterOp(this, cc);
 917             }
 918 
 919             MonitorEnterOp(Value monitor) {
 920                 super(monitor);
 921             }
 922         }
 923 
 924         /**
 925          * The monitor exit operation.
 926          */
 927         @OpDeclaration(MonitorExitOp.NAME)
 928         public static final class MonitorExitOp extends MonitorOp {
 929             static final String NAME = "monitor.exit";
 930 
 931             MonitorExitOp(ExternalizedOp def) {
 932                 if (def.operands().size() != 1) {
 933                     throw new IllegalArgumentException("Operation must have one operand " + def.name());
 934                 }
 935 
 936                 this(def.operands().get(0));
 937             }
 938 
 939             MonitorExitOp(MonitorExitOp that, CodeContext cc) {
 940                 super(that, cc);
 941             }
 942 
 943             @Override
 944             public MonitorExitOp transform(CodeContext cc, CodeTransformer ct) {
 945                 return new MonitorExitOp(this, cc);
 946             }
 947 
 948             MonitorExitOp(Value monitor) {
 949                 super(monitor);
 950             }
 951         }
 952     }
 953 
 954     /**
 955      * The invoke operation, that can model Java language method invocation expressions.
 956      * <p>
 957      * The method invoked by an invoke operation is specified using a
 958      * {@linkplain MethodRef method reference}.
 959      * The operands of an invoke operation are specified as follows:
 960      * <ul>
 961      *     <li>For {@linkplain InvokeKind#STATIC static} invocations, operands are the invocation arguments.</li>
 962      *     <li>For {@linkplain InvokeKind#INSTANCE instance} and {@linkplain InvokeKind#SUPER super} invocations, the first
 963      *         operand is the receiver and the remaining operands are the invocation arguments.</li>
 964      * </ul>
 965      *
 966      * @jls 15.12 Method Invocation Expressions
 967      */
 968     @OpDeclaration(InvokeOp.NAME)
 969     public static final class InvokeOp extends JavaOp
 970             implements ReflectiveOp, JavaExpression, JavaStatement {
 971 
 972         /**
 973          * The kind of invocation.
 974          */
 975         public enum InvokeKind {
 976             /**
 977              * An invocation on a class (static) method.
 978              */
 979             STATIC,
 980             /**
 981              * An invocation on an instance method.
 982              */
 983             INSTANCE,
 984             /**
 985              * A super invocation on an instance method.
 986              */
 987             SUPER
 988         }
 989 
 990         static final String NAME = "invoke";
 991         /** The externalized attribute key for a method invocation reference. */
 992         static final String ATTRIBUTE_INVOKE_REF = NAME + ".ref";
 993         /** The externalized attribute key indicating the invocation kind. */
 994         static final String ATTRIBUTE_INVOKE_KIND = NAME + ".kind";
 995         /** The externalized attribute key for marking a varargs invocation. */
 996         static final String ATTRIBUTE_INVOKE_VARARGS = NAME + ".varargs";
 997 
 998         final InvokeKind invokeKind;
 999         final boolean isVarArgs;
1000         final MethodRef invokeReference;
1001         final CodeType resultType;
1002 
1003         InvokeOp(ExternalizedOp def) {
1004             // Required attribute
1005             MethodRef invokeRef = def.extractAttributeValue(ATTRIBUTE_INVOKE_REF,
1006                     true, v -> switch (v) {
1007                         case MethodRef md -> md;
1008                         case null, default ->
1009                                 throw new UnsupportedOperationException("Unsupported invoke reference value:" + v);
1010                     });
1011 
1012             // If not present defaults to false
1013             boolean isVarArgs = def.extractAttributeValue(ATTRIBUTE_INVOKE_VARARGS,
1014                     false, v -> switch (v) {
1015                         case Boolean b -> b;
1016                         case null, default -> false;
1017                     });
1018 
1019             // If not present and is not varargs defaults to class or instance invocation
1020             // based on number of operands and parameters
1021             InvokeKind ik = def.extractAttributeValue(ATTRIBUTE_INVOKE_KIND,
1022                     false, v -> switch (v) {
1023                         case String s -> InvokeKind.valueOf(s);
1024                         case InvokeKind k -> k;
1025                         case null, default -> {
1026                             if (isVarArgs) {
1027                                 // If varargs then we cannot infer invoke kind
1028                                 throw new UnsupportedOperationException("Unsupported invoke kind value:" + v);
1029                             }
1030                             int paramCount = invokeRef.signature().parameterTypes().size();
1031                             int argCount = def.operands().size();
1032                             yield (argCount == paramCount + 1)
1033                                     ? InvokeKind.INSTANCE
1034                                     : InvokeKind.STATIC;
1035                         }
1036                     });
1037 
1038 
1039             this(ik, isVarArgs, def.resultType(), invokeRef, def.operands());
1040         }
1041 
1042         InvokeOp(InvokeOp that, CodeContext cc) {
1043             super(that, cc);
1044 
1045             this.invokeKind = that.invokeKind;
1046             this.isVarArgs = that.isVarArgs;
1047             this.invokeReference = that.invokeReference;
1048             this.resultType = that.resultType;
1049         }
1050 
1051         @Override
1052         public InvokeOp transform(CodeContext cc, CodeTransformer ct) {
1053             return new InvokeOp(this, cc);
1054         }
1055 
1056         InvokeOp(InvokeKind invokeKind, boolean isVarArgs, CodeType resultType, MethodRef invokeReference, List<Value> args) {
1057             super(args);
1058 
1059             validateArgCount(invokeKind, isVarArgs, invokeReference, args);
1060 
1061             this.invokeKind = invokeKind;
1062             this.isVarArgs = isVarArgs;
1063             this.invokeReference = invokeReference;
1064             this.resultType = resultType;
1065         }
1066 
1067         static void validateArgCount(InvokeKind invokeKind, boolean isVarArgs, MethodRef invokeRef, List<Value> operands) {
1068             int paramCount = invokeRef.signature().parameterTypes().size();
1069             int argCount = operands.size() - (invokeKind == InvokeKind.STATIC ? 0 : 1);
1070             if ((!isVarArgs && argCount != paramCount)
1071                     || argCount < paramCount - 1) {
1072                 throw new IllegalArgumentException(invokeKind + " " + isVarArgs + " " + invokeRef);
1073             }
1074         }
1075 
1076         @Override
1077         public Map<String, Object> externalize() {
1078             HashMap<String, Object> m = new HashMap<>();
1079             m.put("", invokeReference);
1080             if (isVarArgs) {
1081                 // If varargs then we need to declare the invoke.kind attribute
1082                 // Given a method `A::m(A... more)` and an invocation with one
1083                 // operand, we don't know if that operand corresponds to the
1084                 // receiver or a method argument
1085                 m.put(ATTRIBUTE_INVOKE_KIND, invokeKind);
1086                 m.put(ATTRIBUTE_INVOKE_VARARGS, isVarArgs);
1087             } else if (invokeKind == InvokeKind.SUPER) {
1088                 m.put(ATTRIBUTE_INVOKE_KIND, invokeKind);
1089             }
1090             return Collections.unmodifiableMap(m);
1091         }
1092 
1093         /**
1094          * {@return the invocation kind}
1095          */
1096         public InvokeKind invokeKind() {
1097             return invokeKind;
1098         }
1099 
1100         /**
1101          * {@return {@code true} if this invocation uses a variable number of arguments}
1102          */
1103         public boolean isVarArgs() {
1104             return isVarArgs;
1105         }
1106 
1107         /**
1108          * {@return the method invocation reference}
1109          */
1110         public MethodRef invokeReference() {
1111             return invokeReference;
1112         }
1113 
1114         /**
1115          * {@return {@code true} if this invocation refers to an instance method)}
1116          */
1117         public boolean hasReceiver() {
1118             return invokeKind != InvokeKind.STATIC;
1119         }
1120 
1121         /**
1122          * {@return the receiver, otherwise {@code null} if no receiver}
1123          */
1124         public Value receiverOperand() {
1125             return hasReceiver() ? operands().getFirst() : null;
1126         }
1127 
1128         /**
1129          * {@return the operands used as varargs, if this is a varargs invocation,
1130          * or {@code null}}
1131          */
1132         public List<Value> varArgOperands() {
1133             if (!isVarArgs) {
1134                 return null;
1135             }
1136 
1137             int operandCount = operands().size();
1138             int argCount = operandCount - (invokeKind == InvokeKind.STATIC ? 0 : 1);
1139             int paramCount = invokeReference.signature().parameterTypes().size();
1140             int varArgCount = argCount - (paramCount - 1);
1141             return operands().subList(operandCount - varArgCount, operandCount);
1142         }
1143 
1144         /**
1145          * {@return the method invocation arguments, including the receiver as the first argument if present}
1146          */
1147         public List<Value> argOperands() {
1148             if (!isVarArgs) {
1149                 return operands();
1150             }
1151             int paramCount = invokeReference().signature().parameterTypes().size();
1152             int argOperandsCount = paramCount - (invokeKind() == InvokeKind.STATIC ? 1 : 0);
1153             return operands().subList(0, argOperandsCount);
1154         }
1155 
1156         @Override
1157         public CodeType resultType() {
1158             return resultType;
1159         }
1160     }
1161 
1162     /**
1163      * The conversion operation, that can model Java language cast expressions
1164      * for numerical conversion, or such implicit conversion.
1165      * <p>
1166      * Conversion operations feature one operand, the value to convert.
1167      *
1168      * @jls 15.16 Cast Expressions
1169      * @jls 5.1.2 Widening Primitive Conversion
1170      * @jls 5.1.3 Narrowing Primitive Conversion
1171      */
1172     @OpDeclaration(ConvOp.NAME)
1173     public static final class ConvOp extends JavaOp
1174             implements Pure, JavaExpression {
1175         static final String NAME = "conv";
1176 
1177         final CodeType resultType;
1178 
1179         ConvOp(ExternalizedOp def) {
1180             this(def.resultType(), def.operands().get(0));
1181         }
1182 
1183         ConvOp(ConvOp that, CodeContext cc) {
1184             super(that, cc);
1185 
1186             this.resultType = that.resultType;
1187         }
1188 
1189         @Override
1190         public Op transform(CodeContext cc, CodeTransformer ct) {
1191             return new ConvOp(this, cc);
1192         }
1193 
1194         ConvOp(CodeType resultType, Value arg) {
1195             super(List.of(arg));
1196 
1197             this.resultType = resultType;
1198         }
1199 
1200         /**
1201          * {@return the value to convert}
1202          */
1203         public Value valueOperand() {
1204             return operands().getFirst();
1205         }
1206 
1207         @Override
1208         public CodeType resultType() {
1209             return resultType;
1210         }
1211     }
1212 
1213     /**
1214      * The new operation, that can model Java language instance creation expressions and array creation expressions.
1215      * <p>
1216      * The constructor invoked by a new operation is specified using a
1217      * {@linkplain MethodRef constructor reference}.
1218      * New operations feature operands corresponding to the constructor arguments.
1219      *
1220      * @jls 15.9 Class Instance Creation Expressions
1221      * @jls 15.10.1 Array Creation Expressions
1222      */
1223     @OpDeclaration(NewOp.NAME)
1224     public static final class NewOp extends JavaOp
1225             implements ReflectiveOp, JavaExpression, JavaStatement {
1226 
1227         static final String NAME = "new";
1228         /**
1229          * The externalized attribute key for a constructor reference in a new operation.
1230          */
1231         static final String ATTRIBUTE_NEW_REF = NAME + ".ref";
1232         /**
1233          * The externalized attribute key indicating a varargs constructor in a new operation.
1234          */
1235         static final String ATTRIBUTE_NEW_VARARGS = NAME + ".varargs";
1236 
1237         final boolean isVarArgs;
1238         final MethodRef constructorReference;
1239         final CodeType resultType;
1240 
1241         NewOp(ExternalizedOp def) {
1242             // Required attribute
1243             MethodRef constructorRef = def.extractAttributeValue(ATTRIBUTE_NEW_REF,
1244                     true, v -> switch (v) {
1245                         case MethodRef cd -> cd;
1246                         case null, default ->
1247                                 throw new UnsupportedOperationException("Unsupported constructor reference value:" + v);
1248                     });
1249 
1250             // If not present defaults to false
1251             boolean isVarArgs = def.extractAttributeValue(ATTRIBUTE_NEW_VARARGS,
1252                     false, v -> switch (v) {
1253                         case Boolean b -> b;
1254                         case null, default -> false;
1255                     });
1256 
1257             this(isVarArgs, def.resultType(), constructorRef, def.operands());
1258         }
1259 
1260         NewOp(NewOp that, CodeContext cc) {
1261             super(that, cc);
1262 
1263             this.isVarArgs = that.isVarArgs;
1264             this.constructorReference = that.constructorReference;
1265             this.resultType = that.resultType;
1266         }
1267 
1268         @Override
1269         public NewOp transform(CodeContext cc, CodeTransformer ct) {
1270             return new NewOp(this, cc);
1271         }
1272 
1273         NewOp(boolean isVarargs, CodeType resultType, MethodRef ctorRef, List<Value> args) {
1274             super(args);
1275 
1276             validateArgCount(isVarargs, ctorRef, args);
1277             if (!ctorRef.isConstructor()) {
1278                 throw new IllegalArgumentException("Not a constructor reference: " + ctorRef);
1279             }
1280 
1281             this.isVarArgs = isVarargs;
1282             this.constructorReference = ctorRef;
1283             this.resultType = resultType;
1284         }
1285 
1286         static void validateArgCount(boolean isVarArgs, MethodRef ctorRef, List<Value> operands) {
1287             int paramCount = ctorRef.signature().parameterTypes().size();
1288             int argCount = operands.size();
1289             if ((!isVarArgs && argCount != paramCount)
1290                     || argCount < paramCount - 1) {
1291                 throw new IllegalArgumentException(isVarArgs + " " + ctorRef);
1292             }
1293         }
1294 
1295         @Override
1296         public Map<String, Object> externalize() {
1297             HashMap<String, Object> m = new HashMap<>();
1298             m.put("", constructorReference);
1299             if (isVarArgs) {
1300                 m.put(ATTRIBUTE_NEW_VARARGS, isVarArgs);
1301             }
1302             return Collections.unmodifiableMap(m);
1303         }
1304 
1305         /**
1306          * {@return {@code true}, if this instance creation operation is a varargs constructor call}
1307          */
1308         public boolean isVarargs() {
1309             return isVarArgs;
1310         }
1311 
1312         /**
1313          * {@return the constructor reference for this instance creation operation}
1314          */
1315         public MethodRef constructorReference() {
1316             return constructorReference;
1317         }
1318 
1319         @Override
1320         public CodeType resultType() {
1321             return resultType;
1322         }
1323     }
1324 
1325     /**
1326      * A field access operation, that can model Java language field access expressions.
1327      * <p>
1328      * The field accessed by a field access operation is specified using a {@linkplain FieldRef field
1329      * reference}.
1330      * <p>
1331      * Instance field accesses feature a receiver operand. Static field accesses have no receiver operand.
1332      *
1333      * @see CoreOp.VarAccessOp
1334      * @jls 15.11 Field Access Expressions
1335      */
1336     public sealed abstract static class FieldAccessOp extends JavaOp
1337             implements AccessOp, ReflectiveOp {
1338         /**
1339          * The externalized attribute modeling the field reference.
1340          */
1341         static final String ATTRIBUTE_FIELD_REF = "field.ref";
1342 
1343         final FieldRef fieldReference;
1344 
1345         FieldAccessOp(FieldAccessOp that, CodeContext cc) {
1346             super(that, cc);
1347             this.fieldReference = that.fieldReference;
1348         }
1349 
1350         FieldAccessOp(List<Value> operands,
1351                       FieldRef fieldReference) {
1352             super(operands);
1353 
1354             this.fieldReference = fieldReference;
1355         }
1356 
1357         @Override
1358         public Map<String, Object> externalize() {
1359             return Map.of("", fieldReference);
1360         }
1361 
1362         /**
1363          * {@return the reference to the accessed field}
1364          */
1365         public final FieldRef fieldReference() {
1366             return fieldReference;
1367         }
1368 
1369         /**
1370          * {@return the value of the receiver, or {@code null} if no receiver}
1371          */
1372         public Value receiverOperand() {
1373             return operands().isEmpty() ? null : operands().getFirst();
1374         }
1375 
1376         /**
1377          * The field load operation, that can model Java language field access expressions used to read a field value.
1378          *
1379          * @see CoreOp.VarAccessOp.VarLoadOp
1380          * @jls 15.11 Field Access Expressions
1381          */
1382         @OpDeclaration(FieldLoadOp.NAME)
1383         public static final class FieldLoadOp extends FieldAccessOp
1384                 implements Pure, JavaExpression {
1385             static final String NAME = "field.load";
1386 
1387             final CodeType resultType;
1388 
1389             FieldLoadOp(ExternalizedOp def) {
1390                 if (def.operands().size() > 1) {
1391                     throw new IllegalArgumentException("Operation must accept zero or one operand");
1392                 }
1393 
1394                 FieldRef fieldRef = def.extractAttributeValue(ATTRIBUTE_FIELD_REF, true,
1395                         v -> switch (v) {
1396                             case FieldRef fd -> fd;
1397                             case null, default ->
1398                                     throw new UnsupportedOperationException("Unsupported field reference value:" + v);
1399                         });
1400 
1401                 super(def.operands(), fieldRef);
1402 
1403                 this.resultType = def.resultType();
1404             }
1405 
1406             FieldLoadOp(FieldLoadOp that, CodeContext cc) {
1407                 super(that, cc);
1408 
1409                 resultType = that.resultType();
1410             }
1411 
1412             @Override
1413             public FieldLoadOp transform(CodeContext cc, CodeTransformer ct) {
1414                 return new FieldLoadOp(this, cc);
1415             }
1416 
1417             // instance
1418             FieldLoadOp(CodeType resultType, FieldRef fieldRef, Value receiver) {
1419                 super(List.of(receiver), fieldRef);
1420 
1421                 this.resultType = resultType;
1422             }
1423 
1424             // static
1425             FieldLoadOp(CodeType resultType, FieldRef fieldRef) {
1426                 super(List.of(), fieldRef);
1427 
1428                 this.resultType = resultType;
1429             }
1430 
1431             @Override
1432             public CodeType resultType() {
1433                 return resultType;
1434             }
1435         }
1436 
1437         /**
1438          * The field store operation, that can model Java language field access expressions used to write a field value.
1439          * <p>
1440          * The result type is always {@link JavaType#VOID}.
1441          *
1442          * @see CoreOp.VarAccessOp.VarStoreOp
1443          * @jls 15.11 Field Access Expressions
1444          */
1445         @OpDeclaration(FieldStoreOp.NAME)
1446         public static final class FieldStoreOp extends FieldAccessOp
1447                 implements JavaExpression, JavaStatement {
1448             static final String NAME = "field.store";
1449 
1450             FieldStoreOp(ExternalizedOp def) {
1451                 if (def.operands().isEmpty() || def.operands().size() > 2) {
1452                     throw new IllegalArgumentException("Operation must accept one or two operands");
1453                 }
1454 
1455                 FieldRef fieldRef = def.extractAttributeValue(ATTRIBUTE_FIELD_REF, true,
1456                         v -> switch (v) {
1457                             case FieldRef fd -> fd;
1458                             case null, default ->
1459                                     throw new UnsupportedOperationException("Unsupported field reference value:" + v);
1460                         });
1461 
1462                 super(def.operands(), fieldRef);
1463             }
1464 
1465             FieldStoreOp(FieldStoreOp that, CodeContext cc) {
1466                 super(that, cc);
1467             }
1468 
1469             @Override
1470             public FieldStoreOp transform(CodeContext cc, CodeTransformer ct) {
1471                 return new FieldStoreOp(this, cc);
1472             }
1473 
1474             // instance
1475             FieldStoreOp(FieldRef fieldRef, Value receiver, Value v) {
1476                 super(List.of(receiver, v), fieldRef);
1477             }
1478 
1479             // static
1480             FieldStoreOp(FieldRef fieldRef, Value v) {
1481                 super(List.of(v), fieldRef);
1482             }
1483 
1484             /**
1485              * {@return the value to store}
1486              */
1487             public Value valueOperand() {
1488                 return operands().get(operands().size() - 1);
1489             }
1490 
1491             @Override
1492             public CodeType resultType() {
1493                 return VOID;
1494             }
1495         }
1496     }
1497 
1498     /**
1499      * The array length operation, that can model Java language field access expressions to the length field of an
1500      * array.
1501      * <p>
1502      * Array length operations feature one operand, the array value.
1503      * The result type of an array length operation is {@link JavaType#INT}.
1504      *
1505      * @jls 15.11 Field Access Expressions
1506      */
1507     @OpDeclaration(ArrayLengthOp.NAME)
1508     public static final class ArrayLengthOp extends JavaOp
1509             implements ReflectiveOp, JavaExpression {
1510         static final String NAME = "array.length";
1511 
1512         ArrayLengthOp(ExternalizedOp def) {
1513             this(def.operands().get(0));
1514         }
1515 
1516         ArrayLengthOp(ArrayLengthOp that, CodeContext cc) {
1517             super(that, cc);
1518         }
1519 
1520         @Override
1521         public ArrayLengthOp transform(CodeContext cc, CodeTransformer ct) {
1522             return new ArrayLengthOp(this, cc);
1523         }
1524 
1525         ArrayLengthOp(Value array) {
1526             super(List.of(array));
1527         }
1528 
1529         /**
1530          * {@return the larray}
1531          */
1532         public Value arrayOperand() {
1533             return operands().getFirst();
1534         }
1535 
1536         @Override
1537         public CodeType resultType() {
1538             return INT;
1539         }
1540     }
1541 
1542     /**
1543      * The array access operation, that can model Java language array access expressions.
1544      * <p>
1545      * Array load operations feature two operands, the array value and the index value.
1546      * Array store operations feature an additional operand, the stored value.
1547      *
1548      * @jls 15.10.3 Array Access Expressions
1549      */
1550     public sealed abstract static class ArrayAccessOp extends JavaOp
1551             implements AccessOp, ReflectiveOp {
1552 
1553         ArrayAccessOp(ArrayAccessOp that, CodeContext cc) {
1554             super(that, cc);
1555         }
1556 
1557         ArrayAccessOp(Value array, Value index, Value v) {
1558             super(operands(array, index, v));
1559         }
1560 
1561         static List<Value> operands(Value array, Value index, Value v) {
1562             return v == null
1563                     ? List.of(array, index)
1564                     : List.of(array, index, v);
1565         }
1566 
1567         /**
1568          * {@return the array}
1569          */
1570         public Value arrayOperand() {
1571             return operands().get(0);
1572         }
1573 
1574         /**
1575          * {@return the array index}
1576          */
1577         public Value indexOperand() {
1578             return operands().get(1);
1579         }
1580 
1581         /**
1582          * The array load operation, that can model Java language array expressions combined with load access to the
1583          * components of an array.
1584          *
1585          * @jls 15.10.3 Array Access Expressions
1586          */
1587         @OpDeclaration(ArrayLoadOp.NAME)
1588         public static final class ArrayLoadOp extends ArrayAccessOp
1589                 implements Pure, JavaExpression {
1590             static final String NAME = "array.load";
1591             final CodeType componentType;
1592 
1593             ArrayLoadOp(ExternalizedOp def) {
1594                 if (def.operands().size() != 2) {
1595                     throw new IllegalArgumentException("Operation must have two operands");
1596                 }
1597 
1598                 this(def.operands().get(0), def.operands().get(1), def.resultType());
1599             }
1600 
1601             ArrayLoadOp(ArrayLoadOp that, CodeContext cc) {
1602                 super(that, cc);
1603                 this.componentType = that.componentType;
1604             }
1605 
1606             @Override
1607             public ArrayLoadOp transform(CodeContext cc, CodeTransformer ct) {
1608                 return new ArrayLoadOp(this, cc);
1609             }
1610 
1611             ArrayLoadOp(Value array, Value index) {
1612                 // @@@ revisit this when the component type is not explicitly given (see VarOp.resultType as an example)
1613                 this(array, index, ((ArrayType)array.type()).componentType());
1614             }
1615 
1616             ArrayLoadOp(Value array, Value index, CodeType componentType) {
1617                 super(array, index, null);
1618                 this.componentType = componentType;
1619             }
1620 
1621             @Override
1622             public CodeType resultType() {
1623                 return componentType;
1624             }
1625         }
1626 
1627         /**
1628          * The array store operation, that can model Java language array expressions combined with store access to the
1629          * components of an array.
1630          * <p>
1631          * The result type of an array store operation is {@link JavaType#VOID}.
1632          *
1633          * @jls 15.10.3 Array Access Expressions
1634          */
1635         @OpDeclaration(ArrayStoreOp.NAME)
1636         public static final class ArrayStoreOp extends ArrayAccessOp
1637                 implements JavaExpression, JavaStatement {
1638             static final String NAME = "array.store";
1639 
1640             ArrayStoreOp(ExternalizedOp def) {
1641                 if (def.operands().size() != 3) {
1642                     throw new IllegalArgumentException("Operation must have two operands");
1643                 }
1644 
1645                 this(def.operands().get(0), def.operands().get(1), def.operands().get(2));
1646             }
1647 
1648             ArrayStoreOp(ArrayStoreOp that, CodeContext cc) {
1649                 super(that, cc);
1650             }
1651 
1652             @Override
1653             public ArrayStoreOp transform(CodeContext cc, CodeTransformer ct) {
1654                 return new ArrayStoreOp(this, cc);
1655             }
1656 
1657             ArrayStoreOp(Value array, Value index, Value v) {
1658                 super(array, index, v);
1659             }
1660 
1661             /**
1662              * {@return the value to store}
1663              */
1664             public Value valueOperand() {
1665                 return operands().get(2);
1666             }
1667 
1668             @Override
1669             public CodeType resultType() {
1670                 return VOID;
1671             }
1672         }
1673     }
1674 
1675     /**
1676      * The instanceof operation, that can model Java language instanceof expressions that use the
1677      * {@code instanceof} keyword as the <em>type comparison operator</em>.
1678      * <p>
1679      * Instanceof operations feature one operand, the value being tested, and are associated with a
1680      * {@linkplain JavaType type} modeling the target type of the type comparison operator.
1681      *
1682      * @jls 15.20.2 The instanceof Operator
1683      */
1684     @OpDeclaration(InstanceOfOp.NAME)
1685     public static final class InstanceOfOp extends JavaOp
1686             implements Pure, ReflectiveOp, JavaExpression {
1687         static final String NAME = "instanceof";
1688         /** The externalized attribute key for the code type modeling the instanceof target type. */
1689         static final String ATTRIBUTE_INSTANCEOF_TYPE = NAME + ".type";
1690 
1691         final CodeType targetType;
1692 
1693         InstanceOfOp(ExternalizedOp def) {
1694             if (def.operands().size() != 1) {
1695                 throw new IllegalArgumentException("Operation must have one operand " + def.name());
1696             }
1697 
1698             CodeType targetType = def.extractAttributeValue(ATTRIBUTE_INSTANCEOF_TYPE, true,
1699                     v -> switch (v) {
1700                         case JavaType td -> td;
1701                         case null, default -> throw new UnsupportedOperationException("Unsupported type value:" + v);
1702                     });
1703 
1704             this(targetType, def.operands().get(0));
1705         }
1706 
1707         InstanceOfOp(InstanceOfOp that, CodeContext cc) {
1708             super(that, cc);
1709 
1710             this.targetType = that.targetType;
1711         }
1712 
1713         @Override
1714         public InstanceOfOp transform(CodeContext cc, CodeTransformer ct) {
1715             return new InstanceOfOp(this, cc);
1716         }
1717 
1718         InstanceOfOp(CodeType t, Value v) {
1719             super(List.of(v));
1720 
1721             this.targetType = t;
1722         }
1723 
1724         @Override
1725         public Map<String, Object> externalize() {
1726             return Map.of("", targetType);
1727         }
1728 
1729         /**
1730          * {@return the value to test}
1731          */
1732         public Value valueOperand() {
1733             return operands().getFirst();
1734         }
1735 
1736         /**
1737          * {@return the code type modeling the target type of this instanceof operation}
1738          */
1739         public CodeType targetType() {
1740             return targetType;
1741         }
1742 
1743         @Override
1744         public CodeType resultType() {
1745             return BOOLEAN;
1746         }
1747     }
1748 
1749     /**
1750      * The cast operation, that can model Java language cast expressions for reference types.
1751      * <p>
1752      * Cast operations feature one operand, the value being cast, and are associated with a
1753      * {@linkplain JavaType type} modeling the target type of the cast.
1754      *
1755      * @jls 15.16 Cast Expressions
1756      */
1757     @OpDeclaration(CastOp.NAME)
1758     public static final class CastOp extends JavaOp
1759             implements Pure, ReflectiveOp, JavaExpression {
1760         static final String NAME = "cast";
1761         /** The externalized attribute key for the code type modeling the target type of the cast. */
1762         static final String ATTRIBUTE_CAST_TYPE = NAME + ".type";
1763 
1764         final CodeType resultType;
1765         final CodeType targetType;
1766 
1767         CastOp(ExternalizedOp def) {
1768             if (def.operands().size() != 1) {
1769                 throw new IllegalArgumentException("Operation must have one operand " + def.name());
1770             }
1771 
1772             CodeType type = def.extractAttributeValue(ATTRIBUTE_CAST_TYPE, true,
1773                     v -> switch (v) {
1774                         case JavaType td -> td;
1775                         case null, default -> throw new UnsupportedOperationException("Unsupported type value:" + v);
1776                     });
1777 
1778             this(def.resultType(), type, def.operands().get(0));
1779         }
1780 
1781         CastOp(CastOp that, CodeContext cc) {
1782             super(that, cc);
1783 
1784             this.resultType = that.resultType;
1785             this.targetType = that.targetType;
1786         }
1787 
1788         @Override
1789         public CastOp transform(CodeContext cc, CodeTransformer ct) {
1790             return new CastOp(this, cc);
1791         }
1792 
1793         CastOp(CodeType resultType, CodeType t, Value v) {
1794             super(List.of(v));
1795 
1796             this.resultType = resultType;
1797             this.targetType = t;
1798         }
1799 
1800         @Override
1801         public Map<String, Object> externalize() {
1802             return Map.of("", targetType);
1803         }
1804 
1805         /**
1806          * {@return the value to cast}
1807          */
1808         public Value valueOperand() {
1809             return operands().get(0);
1810         }
1811 
1812         /**
1813          * {@return the code type modeling the target type of this cast operation}
1814          */
1815         public CodeType targetType() {
1816             return targetType;
1817         }
1818 
1819         @Override
1820         public CodeType resultType() {
1821             return resultType;
1822         }
1823     }
1824 
1825     /**
1826      * The exception region start operation, that can model entry into an exception region.
1827      * <p>
1828      * An exception region start operation is a block-terminating operation whose first successor is the starting
1829      * block of the exception region, and whose remaining successors are the catch blocks for that region.
1830      */
1831     @OpDeclaration(ExceptionRegionEnter.NAME)
1832     public static final class ExceptionRegionEnter extends JavaOp
1833             implements BlockTerminating {
1834         static final String NAME = "exception.region.enter";
1835 
1836         // First successor is the non-exceptional successor whose target indicates
1837         // the first block in the exception region.
1838         // One or more subsequent successors target the exception catching blocks
1839         // each of which have one block argument whose type is an exception type.
1840         final List<Block.Reference> references;
1841 
1842         ExceptionRegionEnter(ExternalizedOp def) {
1843             this(def.successors());
1844         }
1845 
1846         ExceptionRegionEnter(ExceptionRegionEnter that, CodeContext cc) {
1847             super(that, cc);
1848 
1849             this.references = that.references.stream().map(cc::getReferenceOrCreate).toList();
1850         }
1851 
1852         @Override
1853         public ExceptionRegionEnter transform(CodeContext cc, CodeTransformer ct) {
1854             return new ExceptionRegionEnter(this, cc);
1855         }
1856 
1857         ExceptionRegionEnter(List<Block.Reference> references) {
1858             super(List.of());
1859 
1860             if (references.size() < 2) {
1861                 throw new IllegalArgumentException("Operation must have two or more successors " + this);
1862             }
1863 
1864             this.references = List.copyOf(references);
1865         }
1866 
1867         @Override
1868         public List<Block.Reference> successors() {
1869             return references;
1870         }
1871 
1872         /**
1873          * {@return the starting block reference of this exception region}
1874          */
1875         public Block.Reference startReference() {
1876             return references.get(0);
1877         }
1878 
1879         /**
1880          * {@return the catch block references of this exception region}
1881          */
1882         public List<Block.Reference> catchReferences() {
1883             return references.subList(1, references.size());
1884         }
1885 
1886         @Override
1887         public CodeType resultType() {
1888             return VOID;
1889         }
1890     }
1891 
1892     /**
1893      * The exception region end operation, that can model exit from an exception region.
1894      * <p>
1895      * An exception region end operation is a block-terminating operation with one operand and one successor.
1896      * The operand is the result of the dominant {@link ExceptionRegionEnter}. The successor is the block that
1897      * follows the exception region.
1898      */
1899     @OpDeclaration(ExceptionRegionExit.NAME)
1900     public static final class ExceptionRegionExit extends JavaOp
1901             implements BlockTerminating {
1902         static final String NAME = "exception.region.exit";
1903 
1904         // Non-exceptional successor
1905         final Block.Reference end;
1906 
1907         ExceptionRegionExit(ExternalizedOp def) {
1908             if (def.operands().size() != 1) {
1909                 throw new IllegalArgumentException("Operation must have one operand" + def.name());
1910             }
1911 
1912             if (!(def.operands().getFirst().asResult().op() instanceof ExceptionRegionEnter)) {
1913                 throw new IllegalArgumentException("Value's is not an exception region entry: " + def.operands().getFirst());
1914             }
1915 
1916             if (def.successors().size() != 1) {
1917                 throw new IllegalArgumentException("Operation must have one successor" + def.name());
1918             }
1919 
1920             this(def.operands().getFirst(), def.successors().getFirst());
1921         }
1922 
1923         ExceptionRegionExit(ExceptionRegionExit that, CodeContext cc) {
1924             super(that, cc);
1925 
1926             this.end = cc.getReferenceOrCreate(that.end);
1927         }
1928 
1929         @Override
1930         public ExceptionRegionExit transform(CodeContext cc, CodeTransformer ct) {
1931             return new ExceptionRegionExit(this, cc);
1932         }
1933 
1934         ExceptionRegionExit(Value enter, Block.Reference end) {
1935             super(List.of(enter));
1936             this.end = end;
1937         }
1938 
1939         @Override
1940         public List<Block.Reference> successors() {
1941             return List.of(end);
1942         }
1943 
1944         /**
1945          * {@return the block reference reached after exiting this exception region}
1946          */
1947         public Block.Reference endReference() {
1948             return end;
1949         }
1950 
1951         /**
1952          * {@return the dominant exception region enter operation}
1953          */
1954         public ExceptionRegionEnter enterOp() {
1955             return (ExceptionRegionEnter)operands().getFirst().asResult().op();
1956         }
1957 
1958         @Override
1959         public CodeType resultType() {
1960             return VOID;
1961         }
1962     }
1963 
1964     /**
1965      * The string concatenation operation, that can model the Java language string concatenation operator
1966      * {@code +}.
1967      * <p>
1968      * Concatenation operations feature two operands.
1969      * The result type of a string concatenation operation is {@linkplain JavaType#J_L_STRING java.lang.String}.
1970      *
1971      * @jls 15.18.1 String Concatenation Operator +
1972      */
1973     @OpDeclaration(ConcatOp.NAME)
1974     public static final class ConcatOp extends JavaOp
1975             implements Pure, JavaExpression {
1976         static final String NAME = "concat";
1977 
1978         ConcatOp(ConcatOp that, CodeContext cc) {
1979             super(that, cc);
1980         }
1981 
1982         ConcatOp(ExternalizedOp def) {
1983             if (def.operands().size() != 2) {
1984                 throw new IllegalArgumentException("Concatenation Operation must have two operands.");
1985             }
1986 
1987             this(def.operands().get(0), def.operands().get(1));
1988         }
1989 
1990         ConcatOp(Value lhs, Value rhs) {
1991             super(List.of(lhs, rhs));
1992         }
1993 
1994         @Override
1995         public Op transform(CodeContext cc, CodeTransformer ct) {
1996             return new ConcatOp(this, cc);
1997         }
1998 
1999         /**
2000          * {@return the left hand operand}
2001          */
2002         public Value lhsOperand() {
2003             return operands().get(0);
2004         }
2005 
2006         /**
2007          * {@return the right hand operand}
2008          */
2009         public Value rhsOperand() {
2010             return operands().get(1);
2011         }
2012 
2013         @Override
2014         public CodeType resultType() {
2015             return J_L_STRING;
2016         }
2017     }
2018 
2019     /**
2020      * The arithmetic operation.
2021      */
2022     public sealed static abstract class ArithmeticOperation extends JavaOp
2023             implements Pure, JavaExpression {
2024         ArithmeticOperation(ArithmeticOperation that, CodeContext cc) {
2025             super(that, cc);
2026         }
2027 
2028         ArithmeticOperation(List<Value> operands) {
2029             super(operands);
2030         }
2031     }
2032 
2033     /**
2034      * A binary arithmetic operation.
2035      * <p>
2036      * Binary arithmetic operations feature two operands. Usually, both operands have the same type,
2037      * although that is not always the case. The result type of a binary arithmetic operation is
2038      * the type of the first operand.
2039      */
2040     public sealed static abstract class BinaryOp extends ArithmeticOperation {
2041         BinaryOp(BinaryOp that, CodeContext cc) {
2042             super(that, cc);
2043         }
2044 
2045         BinaryOp(Value lhs, Value rhs) {
2046             super(List.of(lhs, rhs));
2047         }
2048 
2049         /**
2050          * {@return the left hand operand}
2051          */
2052         public Value lhsOperand() {
2053             return operands().get(0);
2054         }
2055 
2056         /**
2057          * {@return the right hand operand}
2058          */
2059         public Value rhsOperand() {
2060             return operands().get(1);
2061         }
2062 
2063         @Override
2064         public CodeType resultType() {
2065             return operands().get(0).type();
2066         }
2067     }
2068 
2069     /**
2070      * The unary arithmetic operation.
2071      * <p>
2072      * Unary arithmetic operations feature one operand.
2073      * The result type of a unary arithmetic operation is the type of its operand.
2074      */
2075     public sealed static abstract class UnaryOp extends ArithmeticOperation {
2076         UnaryOp(UnaryOp that, CodeContext cc) {
2077             super(that, cc);
2078         }
2079 
2080         UnaryOp(Value v) {
2081             super(List.of(v));
2082         }
2083 
2084         /**
2085          * {@return the operand}
2086          */
2087         public Value operand() {
2088             return operands().get(0);
2089         }
2090 
2091         @Override
2092         public CodeType resultType() {
2093             return operands().get(0).type();
2094         }
2095     }
2096 
2097     /**
2098      * The compare operation.
2099      * <p>
2100      * Compare operations feature two operands, and yield a {@link JavaType#BOOLEAN} value.
2101      */
2102     public sealed static abstract class CompareOp extends ArithmeticOperation {
2103         CompareOp(CompareOp that, CodeContext cc) {
2104             super(that, cc);
2105         }
2106 
2107         CompareOp(Value lhs, Value rhs) {
2108             super(List.of(lhs, rhs));
2109         }
2110 
2111         /**
2112          * {@return the left hand operand}
2113          */
2114         public Value lhsOperand() {
2115             return operands().get(0);
2116         }
2117 
2118         /**
2119          * {@return the right hand operand}
2120          */
2121         public Value rhsOperand() {
2122             return operands().get(1);
2123         }
2124 
2125         @Override
2126         public CodeType resultType() {
2127             return BOOLEAN;
2128         }
2129     }
2130 
2131     /**
2132      * The add operation, that can model the Java language binary {@code +} operator for numeric types
2133      *
2134      * @jls 15.18.2 Additive Operators (+ and -) for Numeric Types
2135      */
2136     @OpDeclaration(AddOp.NAME)
2137     public static final class AddOp extends BinaryOp {
2138         static final String NAME = "add";
2139 
2140         AddOp(ExternalizedOp def) {
2141             this(def.operands().get(0), def.operands().get(1));
2142         }
2143 
2144         AddOp(AddOp that, CodeContext cc) {
2145             super(that, cc);
2146         }
2147 
2148         @Override
2149         public AddOp transform(CodeContext cc, CodeTransformer ct) {
2150             return new AddOp(this, cc);
2151         }
2152 
2153         AddOp(Value lhs, Value rhs) {
2154             super(lhs, rhs);
2155         }
2156     }
2157 
2158     /**
2159      * The sub operation, that can model the Java language binary {@code -} operator for numeric types
2160      *
2161      * @jls 15.18.2 Additive Operators (+ and -) for Numeric Types
2162      */
2163     @OpDeclaration(SubOp.NAME)
2164     public static final class SubOp extends BinaryOp {
2165         static final String NAME = "sub";
2166 
2167         SubOp(ExternalizedOp def) {
2168             this(def.operands().get(0), def.operands().get(1));
2169         }
2170 
2171         SubOp(SubOp that, CodeContext cc) {
2172             super(that, cc);
2173         }
2174 
2175         @Override
2176         public SubOp transform(CodeContext cc, CodeTransformer ct) {
2177             return new SubOp(this, cc);
2178         }
2179 
2180         SubOp(Value lhs, Value rhs) {
2181             super(lhs, rhs);
2182         }
2183     }
2184 
2185     /**
2186      * The mul operation, that can model the Java language binary {@code *} operator for numeric types
2187      *
2188      * @jls 15.17.1 Multiplication Operator *
2189      */
2190     @OpDeclaration(MulOp.NAME)
2191     public static final class MulOp extends BinaryOp {
2192         static final String NAME = "mul";
2193 
2194         MulOp(ExternalizedOp def) {
2195             this(def.operands().get(0), def.operands().get(1));
2196         }
2197 
2198         MulOp(MulOp that, CodeContext cc) {
2199             super(that, cc);
2200         }
2201 
2202         @Override
2203         public MulOp transform(CodeContext cc, CodeTransformer ct) {
2204             return new MulOp(this, cc);
2205         }
2206 
2207         MulOp(Value lhs, Value rhs) {
2208             super(lhs, rhs);
2209         }
2210     }
2211 
2212     /**
2213      * The div operation, that can model the Java language binary {@code /} operator for numeric types
2214      *
2215      * @jls 15.17.2 Division Operator /
2216      */
2217     @OpDeclaration(DivOp.NAME)
2218     public static final class DivOp extends BinaryOp {
2219         static final String NAME = "div";
2220 
2221         DivOp(ExternalizedOp def) {
2222             this(def.operands().get(0), def.operands().get(1));
2223         }
2224 
2225         DivOp(DivOp that, CodeContext cc) {
2226             super(that, cc);
2227         }
2228 
2229         @Override
2230         public DivOp transform(CodeContext cc, CodeTransformer ct) {
2231             return new DivOp(this, cc);
2232         }
2233 
2234         DivOp(Value lhs, Value rhs) {
2235             super(lhs, rhs);
2236         }
2237     }
2238 
2239     /**
2240      * The mod operation, that can model the Java language binary {@code %} operator for numeric types
2241      *
2242      * @jls 15.17.3 Remainder Operator %
2243      */
2244     @OpDeclaration(ModOp.NAME)
2245     public static final class ModOp extends BinaryOp {
2246         static final String NAME = "mod";
2247 
2248         ModOp(ExternalizedOp def) {
2249             this(def.operands().get(0), def.operands().get(1));
2250         }
2251 
2252         ModOp(ModOp that, CodeContext cc) {
2253             super(that, cc);
2254         }
2255 
2256         @Override
2257         public ModOp transform(CodeContext cc, CodeTransformer ct) {
2258             return new ModOp(this, cc);
2259         }
2260 
2261         ModOp(Value lhs, Value rhs) {
2262             super(lhs, rhs);
2263         }
2264     }
2265 
2266     /**
2267      * The bitwise/logical or operation, that can model the Java language binary {@code |} operator for integral types
2268      * and booleans
2269      *
2270      * @jls 15.22 Bitwise and Logical Operators
2271      */
2272     @OpDeclaration(OrOp.NAME)
2273     public static final class OrOp extends BinaryOp {
2274         static final String NAME = "or";
2275 
2276         OrOp(ExternalizedOp def) {
2277             this(def.operands().get(0), def.operands().get(1));
2278         }
2279 
2280         OrOp(OrOp that, CodeContext cc) {
2281             super(that, cc);
2282         }
2283 
2284         @Override
2285         public OrOp transform(CodeContext cc, CodeTransformer ct) {
2286             return new OrOp(this, cc);
2287         }
2288 
2289         OrOp(Value lhs, Value rhs) {
2290             super(lhs, rhs);
2291         }
2292     }
2293 
2294     /**
2295      * The bitwise/logical and operation, that can model the Java language binary {@code &} operator for integral types
2296      * and booleans
2297      *
2298      * @jls 15.22 Bitwise and Logical Operators
2299      */
2300     @OpDeclaration(AndOp.NAME)
2301     public static final class AndOp extends BinaryOp {
2302         static final String NAME = "and";
2303 
2304         AndOp(ExternalizedOp def) {
2305             this(def.operands().get(0), def.operands().get(1));
2306         }
2307 
2308         AndOp(AndOp that, CodeContext cc) {
2309             super(that, cc);
2310         }
2311 
2312         @Override
2313         public AndOp transform(CodeContext cc, CodeTransformer ct) {
2314             return new AndOp(this, cc);
2315         }
2316 
2317         AndOp(Value lhs, Value rhs) {
2318             super(lhs, rhs);
2319         }
2320     }
2321 
2322     /**
2323      * The xor operation, that can model the Java language binary {@code ^} operator for integral types
2324      * and booleans
2325      *
2326      * @jls 15.22 Bitwise and Logical Operators
2327      */
2328     @OpDeclaration(XorOp.NAME)
2329     public static final class XorOp extends BinaryOp {
2330         static final String NAME = "xor";
2331 
2332         XorOp(ExternalizedOp def) {
2333             this(def.operands().get(0), def.operands().get(1));
2334         }
2335 
2336         XorOp(XorOp that, CodeContext cc) {
2337             super(that, cc);
2338         }
2339 
2340         @Override
2341         public XorOp transform(CodeContext cc, CodeTransformer ct) {
2342             return new XorOp(this, cc);
2343         }
2344 
2345         XorOp(Value lhs, Value rhs) {
2346             super(lhs, rhs);
2347         }
2348     }
2349 
2350     /**
2351      * The (logical) shift left operation, that can model the Java language binary {@code <<} operator for integral types
2352      *
2353      * @jls 15.19 Shift Operators
2354      */
2355     @OpDeclaration(LshlOp.NAME)
2356     public static final class LshlOp extends BinaryOp {
2357         static final String NAME = "lshl";
2358 
2359         LshlOp(ExternalizedOp def) {
2360             this(def.operands().get(0), def.operands().get(1));
2361         }
2362 
2363         LshlOp(LshlOp that, CodeContext cc) {
2364             super(that, cc);
2365         }
2366 
2367         @Override
2368         public LshlOp transform(CodeContext cc, CodeTransformer ct) {
2369             return new LshlOp(this, cc);
2370         }
2371 
2372         LshlOp(Value lhs, Value rhs) {
2373             super(lhs, rhs);
2374         }
2375     }
2376 
2377     /**
2378      * The (arithmetic) shift right operation, that can model the Java language binary {@code >>} operator for integral types
2379      *
2380      * @jls 15.19 Shift Operators
2381      */
2382     @OpDeclaration(AshrOp.NAME)
2383     public static final class AshrOp extends JavaOp.BinaryOp {
2384         static final String NAME = "ashr";
2385 
2386         AshrOp(ExternalizedOp def) {
2387             this(def.operands().get(0), def.operands().get(1));
2388         }
2389 
2390         AshrOp(AshrOp that, CodeContext cc) {
2391             super(that, cc);
2392         }
2393 
2394         @Override
2395         public AshrOp transform(CodeContext cc, CodeTransformer ct) {
2396             return new AshrOp(this, cc);
2397         }
2398 
2399         AshrOp(Value lhs, Value rhs) {
2400             super(lhs, rhs);
2401         }
2402     }
2403 
2404     /**
2405      * The unsigned (logical) shift right operation, that can model the Java language binary {@code >>>} operator for integral types
2406      *
2407      * @jls 15.19 Shift Operators
2408      */
2409     @OpDeclaration(LshrOp.NAME)
2410     public static final class LshrOp extends JavaOp.BinaryOp {
2411         static final String NAME = "lshr";
2412 
2413         LshrOp(ExternalizedOp def) {
2414             this(def.operands().get(0), def.operands().get(1));
2415         }
2416 
2417         LshrOp(LshrOp that, CodeContext cc) {
2418             super(that, cc);
2419         }
2420 
2421         @Override
2422         public LshrOp transform(CodeContext cc, CodeTransformer ct) {
2423             return new LshrOp(this, cc);
2424         }
2425 
2426         LshrOp(Value lhs, Value rhs) {
2427             super(lhs, rhs);
2428         }
2429     }
2430 
2431     /**
2432      * The neg operation, that can model the Java language unary {@code -} operator for numeric types
2433      *
2434      * @jls 15.15.4 Unary Minus Operator {@code -}
2435      */
2436     @OpDeclaration(NegOp.NAME)
2437     public static final class NegOp extends UnaryOp {
2438         static final String NAME = "neg";
2439 
2440         NegOp(ExternalizedOp def) {
2441             this(def.operands().get(0));
2442         }
2443 
2444         NegOp(NegOp that, CodeContext cc) {
2445             super(that, cc);
2446         }
2447 
2448         @Override
2449         public NegOp transform(CodeContext cc, CodeTransformer ct) {
2450             return new NegOp(this, cc);
2451         }
2452 
2453         NegOp(Value v) {
2454             super(v);
2455         }
2456     }
2457 
2458     /**
2459      * The bitwise complement operation, that can model the Java language unary {@code ~} operator for integral types
2460      *
2461      * @jls 15.15.5 Bitwise Complement Operator {@code ~}
2462      */
2463     @OpDeclaration(ComplOp.NAME)
2464     public static final class ComplOp extends UnaryOp {
2465         static final String NAME = "compl";
2466 
2467         ComplOp(ExternalizedOp def) {
2468             this(def.operands().get(0));
2469         }
2470 
2471         ComplOp(ComplOp that, CodeContext cc) {
2472             super(that, cc);
2473         }
2474 
2475         @Override
2476         public ComplOp transform(CodeContext cc, CodeTransformer ct) {
2477             return new ComplOp(this, cc);
2478         }
2479 
2480         ComplOp(Value v) {
2481             super(v);
2482         }
2483     }
2484 
2485     /**
2486      * The not operation, that can model the Java language unary {@code !} operator for boolean types
2487      *
2488      * @jls 15.15.6 Logical Complement Operator {@code !}
2489      */
2490     @OpDeclaration(NotOp.NAME)
2491     public static final class NotOp extends UnaryOp {
2492         static final String NAME = "not";
2493 
2494         NotOp(ExternalizedOp def) {
2495             this(def.operands().get(0));
2496         }
2497 
2498         NotOp(NotOp that, CodeContext cc) {
2499             super(that, cc);
2500         }
2501 
2502         @Override
2503         public NotOp transform(CodeContext cc, CodeTransformer ct) {
2504             return new NotOp(this, cc);
2505         }
2506 
2507         NotOp(Value v) {
2508             super(v);
2509         }
2510     }
2511 
2512     /**
2513      * The equals operation, that can model the Java language equality {@code ==} operator for numeric, boolean
2514      * and reference types
2515      *
2516      * @jls 15.21 Equality Operators
2517      */
2518     @OpDeclaration(EqOp.NAME)
2519     public static final class EqOp extends CompareOp {
2520         static final String NAME = "eq";
2521 
2522         EqOp(ExternalizedOp def) {
2523             this(def.operands().get(0), def.operands().get(1));
2524         }
2525 
2526         EqOp(EqOp that, CodeContext cc) {
2527             super(that, cc);
2528         }
2529 
2530         @Override
2531         public EqOp transform(CodeContext cc, CodeTransformer ct) {
2532             return new EqOp(this, cc);
2533         }
2534 
2535         EqOp(Value lhs, Value rhs) {
2536             super(lhs, rhs);
2537         }
2538     }
2539 
2540     /**
2541      * The not equals operation, that can model the Java language equality {@code !=} operator for numeric, boolean
2542      * and reference types
2543      *
2544      * @jls 15.21 Equality Operators
2545      */
2546     @OpDeclaration(NeqOp.NAME)
2547     public static final class NeqOp extends CompareOp {
2548         static final String NAME = "neq";
2549 
2550         NeqOp(ExternalizedOp def) {
2551             this(def.operands().get(0), def.operands().get(1));
2552         }
2553 
2554         NeqOp(NeqOp that, CodeContext cc) {
2555             super(that, cc);
2556         }
2557 
2558         @Override
2559         public NeqOp transform(CodeContext cc, CodeTransformer ct) {
2560             return new NeqOp(this, cc);
2561         }
2562 
2563         NeqOp(Value lhs, Value rhs) {
2564             super(lhs, rhs);
2565         }
2566     }
2567 
2568     /**
2569      * The greater than operation, that can model the Java language relational {@code >} operator for numeric types
2570      *
2571      * @jls 15.20.1 Numerical Comparison Operators {@code <}, {@code <=}, {@code >}, and {@code >=}
2572      */
2573     @OpDeclaration(GtOp.NAME)
2574     public static final class GtOp extends CompareOp {
2575         static final String NAME = "gt";
2576 
2577         GtOp(ExternalizedOp def) {
2578             this(def.operands().get(0), def.operands().get(1));
2579         }
2580 
2581         GtOp(GtOp that, CodeContext cc) {
2582             super(that, cc);
2583         }
2584 
2585         @Override
2586         public GtOp transform(CodeContext cc, CodeTransformer ct) {
2587             return new GtOp(this, cc);
2588         }
2589 
2590         GtOp(Value lhs, Value rhs) {
2591             super(lhs, rhs);
2592         }
2593     }
2594 
2595     /**
2596      * The greater than or equal to operation, that can model the Java language relational {@code >=} operator for
2597      * numeric types
2598      *
2599      * @jls 15.20.1 Numerical Comparison Operators {@code <}, {@code <=}, {@code >}, and {@code >=}
2600      */
2601     @OpDeclaration(GeOp.NAME)
2602     public static final class GeOp extends CompareOp {
2603         static final String NAME = "ge";
2604 
2605         GeOp(ExternalizedOp def) {
2606             this(def.operands().get(0), def.operands().get(1));
2607         }
2608 
2609         GeOp(GeOp that, CodeContext cc) {
2610             super(that, cc);
2611         }
2612 
2613         @Override
2614         public GeOp transform(CodeContext cc, CodeTransformer ct) {
2615             return new GeOp(this, cc);
2616         }
2617 
2618         GeOp(Value lhs, Value rhs) {
2619             super(lhs, rhs);
2620         }
2621     }
2622 
2623     /**
2624      * The less than operation, that can model the Java language relational {@code <} operator for
2625      * numeric types
2626      *
2627      * @jls 15.20.1 Numerical Comparison Operators {@code <}, {@code <=}, {@code >}, and {@code >=}
2628      */
2629     @OpDeclaration(LtOp.NAME)
2630     public static final class LtOp extends CompareOp {
2631         static final String NAME = "lt";
2632 
2633         LtOp(ExternalizedOp def) {
2634             this(def.operands().get(0), def.operands().get(1));
2635         }
2636 
2637         LtOp(LtOp that, CodeContext cc) {
2638             super(that, cc);
2639         }
2640 
2641         @Override
2642         public LtOp transform(CodeContext cc, CodeTransformer ct) {
2643             return new LtOp(this, cc);
2644         }
2645 
2646         LtOp(Value lhs, Value rhs) {
2647             super(lhs, rhs);
2648         }
2649     }
2650 
2651     /**
2652      * The less than or equal to operation, that can model the Java language relational {@code <=} operator for
2653      * numeric types
2654      *
2655      * @jls 15.20.1 Numerical Comparison Operators {@code <}, {@code <=}, {@code >}, and {@code >=}
2656      */
2657     @OpDeclaration(LeOp.NAME)
2658     public static final class LeOp extends CompareOp {
2659         static final String NAME = "le";
2660 
2661         LeOp(ExternalizedOp def) {
2662             this(def.operands().get(0), def.operands().get(1));
2663         }
2664 
2665         LeOp(LeOp that, CodeContext cc) {
2666             super(that, cc);
2667         }
2668 
2669         @Override
2670         public LeOp transform(CodeContext cc, CodeTransformer ct) {
2671             return new LeOp(this, cc);
2672         }
2673 
2674         LeOp(Value lhs, Value rhs) {
2675             super(lhs, rhs);
2676         }
2677     }
2678 
2679     /**
2680      * A statement target operation, that can model Java language statements associated with label identifiers.
2681      * <p>
2682      * A statement target operation is a body-terminating operation that features zero or one operand, the label
2683      * identifier. If present, the label identifier is modeled as a {@link ConstantOp} value.
2684      * <p>
2685      * The result type of a statement target operation is {@link JavaType#VOID}.
2686      *
2687      * @jls 14.15 The break Statement
2688      * @jls 14.16 The continue Statement
2689      */
2690     public sealed static abstract class StatementTargetOp extends JavaOp
2691             implements Op.Lowerable, Op.BodyTerminating, JavaStatement {
2692         StatementTargetOp(StatementTargetOp that, CodeContext cc) {
2693             super(that, cc);
2694         }
2695 
2696         StatementTargetOp(Value label) {
2697             super(checkLabel(label));
2698         }
2699 
2700         static List<Value> checkLabel(Value label) {
2701             return label == null ? List.of() : List.of(label);
2702         }
2703 
2704         Op innerMostEnclosingTarget() {
2705             /*
2706                 A break statement with no label attempts to transfer control to the
2707                 innermost enclosing switch, while, do, or for statement; this enclosing statement,
2708                 which is called the break target, then immediately completes normally.
2709 
2710                 A break statement with label Identifier attempts to transfer control to the
2711                 enclosing labeled statement (14.7) that has the same Identifier as its label;
2712                 this enclosing statement, which is called the break target, then immediately completes normally.
2713                 In this case, the break target need not be a switch, while, do, or for statement.
2714              */
2715 
2716             // No label
2717             // Get innermost enclosing loop operation
2718             Op op = this;
2719             Body b;
2720             do {
2721                 b = op.ancestorBody();
2722                 op = b.ancestorOp();
2723                 if (op == null) {
2724                     throw new IllegalStateException("No enclosing loop");
2725                 }
2726             } while (!(op instanceof Op.Loop || op instanceof SwitchStatementOp));
2727 
2728             return switch (op) {
2729                 case Op.Loop lop -> lop.loopBody() == b ? op : null;
2730                 case SwitchStatementOp swStat -> swStat.bodies().contains(b) ? op : null;
2731                 default -> throw new IllegalStateException();
2732             };
2733         }
2734 
2735         boolean isUnlabeled() {
2736             return operands().isEmpty();
2737         }
2738 
2739         Op target() {
2740             // If unlabeled then find the nearest enclosing op
2741             // Otherwise obtain the label target
2742             if (isUnlabeled()) {
2743                 return innerMostEnclosingTarget();
2744             }
2745 
2746             Value value = operands().get(0);
2747             if (value instanceof Result r && r.op().ancestorOp() instanceof LabeledOp lop) {
2748                 return lop.target();
2749             } else {
2750                 throw new IllegalStateException("Bad label value: " + value + " " + ((Result) value).op());
2751             }
2752         }
2753 
2754         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
2755             Op opt = target();
2756             BranchTarget t = BranchTarget.getBranchTarget(b.context(), opt);
2757             if (t != null) {
2758                 b.add(branch(f.apply(t).reference()));
2759             } else {
2760                 throw new IllegalStateException("No branch target for operation: " + opt);
2761             }
2762             return b;
2763         }
2764 
2765         /**
2766          * {@return the label identifier, otherwise {@code null} if no label}
2767          */
2768         public Value labelOperand() {
2769             return operands().isEmpty() ? null : operands().getFirst();
2770         }
2771 
2772         @Override
2773         public CodeType resultType() {
2774             return VOID;
2775         }
2776     }
2777 
2778     /**
2779      * The break operation, that can model Java language break statements.
2780      * <p>
2781      * A break operation is a body-terminating statement target operation.
2782      *
2783      * @jls 14.15 The break Statement
2784      */
2785     @OpDeclaration(BreakOp.NAME)
2786     public static final class BreakOp extends StatementTargetOp {
2787         static final String NAME = "java.break";
2788 
2789         BreakOp(ExternalizedOp def) {
2790             this(def.operands().isEmpty() ? null : def.operands().get(0));
2791         }
2792 
2793         BreakOp(BreakOp that, CodeContext cc) {
2794             super(that, cc);
2795         }
2796 
2797         @Override
2798         public BreakOp transform(CodeContext cc, CodeTransformer ct) {
2799             return new BreakOp(this, cc);
2800         }
2801 
2802         BreakOp(Value label) {
2803             super(label);
2804         }
2805 
2806         @Override
2807         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
2808             return lower(b, BranchTarget::breakBlock);
2809         }
2810     }
2811 
2812     /**
2813      * The continue operation, that can model Java language continue statements.
2814      * <p>
2815      * A continue operation is a body-terminating statement target operation.
2816      *
2817      * @jls 14.16 The continue Statement
2818      */
2819     @OpDeclaration(ContinueOp.NAME)
2820     public static final class ContinueOp extends StatementTargetOp {
2821         static final String NAME = "java.continue";
2822 
2823         ContinueOp(ExternalizedOp def) {
2824             this(def.operands().isEmpty() ? null : def.operands().get(0));
2825         }
2826 
2827         ContinueOp(ContinueOp that, CodeContext cc) {
2828             super(that, cc);
2829         }
2830 
2831         @Override
2832         public ContinueOp transform(CodeContext cc, CodeTransformer ct) {
2833             return new ContinueOp(this, cc);
2834         }
2835 
2836         ContinueOp(Value label) {
2837             super(label);
2838         }
2839 
2840         @Override
2841         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
2842             return lower(b, BranchTarget::continueBlock);
2843         }
2844     }
2845 
2846     /**
2847      * The yield operation, that can model Java language yield statements.
2848      * <p>
2849      * A yield operation is a body-terminating operation that features one operand, the yielded value.
2850      * <p>
2851      * The result type of a yield operation is {@link JavaType#VOID}.
2852      *
2853      * @jls 14.21 The yield Statement
2854      */
2855     @OpDeclaration(YieldOp.NAME)
2856     public static final class YieldOp extends JavaOp
2857             implements Op.BodyTerminating, JavaStatement, Op.Lowerable {
2858         static final String NAME = "java.yield";
2859 
2860         YieldOp(ExternalizedOp def) {
2861             if (def.operands().size() != 1) {
2862                 throw new IllegalArgumentException("Operation must have one operand " + def.name());
2863             }
2864 
2865             this(def.operands().get(0));
2866         }
2867 
2868         YieldOp(YieldOp that, CodeContext cc) {
2869             super(that, cc);
2870         }
2871 
2872         @Override
2873         public YieldOp transform(CodeContext cc, CodeTransformer ct) {
2874             return new YieldOp(this, cc);
2875         }
2876 
2877         YieldOp(Value operand) {
2878             super(List.of(Objects.requireNonNull(operand)));
2879         }
2880 
2881         /**
2882          * {@return the yielded value}
2883          */
2884         public Value yieldOperand() {
2885             return operands().get(0);
2886         }
2887 
2888         @Override
2889         public CodeType resultType() {
2890             return VOID;
2891         }
2892 
2893         @Override
2894         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
2895             // for now, we will use breakBlock field to indicate java.yield target block
2896             return lower(b, BranchTarget::breakBlock);
2897         }
2898 
2899         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
2900             Op opt = target();
2901             BranchTarget t = BranchTarget.getBranchTarget(b.context(), opt);
2902             if (t != null) {
2903                 b.add(branch(f.apply(t).reference(b.context().getValue(yieldOperand()))));
2904             } else {
2905                 throw new IllegalStateException("No branch target for operation: " + opt);
2906             }
2907             return b;
2908         }
2909 
2910         Op target() {
2911             return innerMostEnclosingTarget();
2912         }
2913 
2914         Op innerMostEnclosingTarget() {
2915             Op op = this;
2916             Body b;
2917             do {
2918                 b = op.ancestorBody();
2919                 op = b.ancestorOp();
2920                 if (op == null) {
2921                     throw new IllegalStateException("No enclosing switch");
2922                 }
2923             } while (!(op instanceof SwitchExpressionOp));
2924             return op;
2925         }
2926     }
2927 
2928     /**
2929      * The block operation, that can model Java language blocks.
2930      * <p>
2931      * Block operations feature one statements body, modeling the list of statements enclosed by the Java block.
2932      * The statements body should accept no arguments and yield {@linkplain JavaType#VOID no value}.
2933      * <p>
2934      * The result type of a block operation is {@link JavaType#VOID}.
2935      *
2936      * @jls 14.2 Blocks
2937      */
2938     @OpDeclaration(BlockOp.NAME)
2939     public static final class BlockOp extends JavaOp
2940             implements Op.Nested, Op.Lowerable, JavaStatement {
2941         static final String NAME = "java.block";
2942 
2943         final Body body;
2944 
2945         BlockOp(ExternalizedOp def) {
2946             if (!def.operands().isEmpty()) {
2947                 throw new IllegalStateException("Operation must have no operands");
2948             }
2949 
2950             this(def.bodyDefinitions().get(0));
2951         }
2952 
2953         BlockOp(BlockOp that, CodeContext cc, CodeTransformer ct) {
2954             super(that, cc);
2955 
2956             // Copy body
2957             this.body = that.body.transform(cc, ct).build(this);
2958         }
2959 
2960         @Override
2961         public BlockOp transform(CodeContext cc, CodeTransformer ct) {
2962             return new BlockOp(this, cc, ct);
2963         }
2964 
2965         BlockOp(Body.Builder bodyC) {
2966             super(List.of());
2967 
2968             this.body = bodyC.build(this);
2969             if (!body.bodySignature().returnType().equals(VOID)) {
2970                 throw new IllegalArgumentException("Body should return void: " + body.bodySignature());
2971             }
2972             if (!body.bodySignature().parameterTypes().isEmpty()) {
2973                 throw new IllegalArgumentException("Body should have zero parameters: " + body.bodySignature());
2974             }
2975         }
2976 
2977         @Override
2978         public List<Body> bodies() {
2979             return List.of(body);
2980         }
2981 
2982         /**
2983          * {@return the block operation body}
2984          */
2985         public Body body() {
2986             return body;
2987         }
2988 
2989         @Override
2990         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
2991             Block.Builder exit = b.block();
2992             BranchTarget.setBranchTarget(b.context(), this, exit, null);
2993 
2994             b.transformBody(body, List.of(), loweringTransformer(inherited, (block, op) -> {
2995                 if (op instanceof CoreOp.YieldOp) {
2996                     block.add(branch(exit.reference()));
2997                     return block;
2998                 } else {
2999                     return null;
3000                 }
3001             }));
3002 
3003             return exit;
3004         }
3005 
3006         @Override
3007         public CodeType resultType() {
3008             return VOID;
3009         }
3010     }
3011 
3012     /**
3013      * The synchronized operation, that can model Java synchronized statements.
3014      * <p>
3015      * Synchronized operations feature two bodies. The <em>expression body</em> accepts no arguments
3016      * and yields a value, the object associated with the monitor that will be acquired by the synchronized
3017      * operation. The <em>block body</em> models the statements to execute while holding the monitor,
3018      * and yields {@linkplain JavaType#VOID no value}.
3019      * <p>
3020      * The result type of a synchronized operation is {@link JavaType#VOID}.
3021      *
3022      * @jls 14.19 The synchronized Statement
3023      */
3024     @OpDeclaration(SynchronizedOp.NAME)
3025     public static final class SynchronizedOp extends JavaOp
3026             implements Op.Nested, Op.Lowerable, JavaStatement {
3027         static final String NAME = "java.synchronized";
3028 
3029         final Body exprBody;
3030         final Body blockBody;
3031 
3032         SynchronizedOp(ExternalizedOp def) {
3033             this(def.bodyDefinitions().get(0), def.bodyDefinitions().get(1));
3034         }
3035 
3036         SynchronizedOp(SynchronizedOp that, CodeContext cc, CodeTransformer ct) {
3037             super(that, cc);
3038 
3039             // Copy bodies
3040             this.exprBody = that.exprBody.transform(cc, ct).build(this);
3041             this.blockBody = that.blockBody.transform(cc, ct).build(this);
3042         }
3043 
3044         @Override
3045         public SynchronizedOp transform(CodeContext cc, CodeTransformer ct) {
3046             return new SynchronizedOp(this, cc, ct);
3047         }
3048 
3049         // @@@: builder?
3050         SynchronizedOp(Body.Builder exprC, Body.Builder bodyC) {
3051             super(List.of());
3052 
3053             this.exprBody = exprC.build(this);
3054             if (exprBody.bodySignature().returnType().equals(VOID)) {
3055                 throw new IllegalArgumentException("Expression body should return non-void value: " + exprBody.bodySignature());
3056             }
3057             if (!exprBody.bodySignature().parameterTypes().isEmpty()) {
3058                 throw new IllegalArgumentException("Expression body should have zero parameters: " + exprBody.bodySignature());
3059             }
3060 
3061             this.blockBody = bodyC.build(this);
3062             if (!blockBody.bodySignature().returnType().equals(VOID)) {
3063                 throw new IllegalArgumentException("Block body should return void: " + blockBody.bodySignature());
3064             }
3065             if (!blockBody.bodySignature().parameterTypes().isEmpty()) {
3066                 throw new IllegalArgumentException("Block body should have zero parameters: " + blockBody.bodySignature());
3067             }
3068         }
3069 
3070         @Override
3071         public List<Body> bodies() {
3072             return List.of(exprBody, blockBody);
3073         }
3074 
3075         /**
3076          * {@return the expression body whose result is the monitor object for synchronization}
3077          */
3078         public Body exprBody() {
3079             return exprBody;
3080         }
3081 
3082         /**
3083          * {@return the body that is executed within the synchronized block}
3084          */
3085         public Body blockBody() {
3086             return blockBody;
3087         }
3088 
3089         @Override
3090         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
3091             // Lower the expression body, yielding a monitor target
3092             b = lowerExpr(b, inherited);
3093             Value monitorTarget = b.parameters().get(0);
3094 
3095             // Monitor enter
3096             b.add(monitorEnter(monitorTarget));
3097 
3098             Block.Builder exit = b.block();
3099             BranchTarget.setBranchTarget(b.context(), this, exit, null);
3100 
3101             // Exception region for the body
3102             Block.Builder syncRegionEnter = b.block();
3103             Block.Builder catcherFinally = b.block();
3104             Op.Result enter = b.add(exceptionRegionEnter(
3105                     syncRegionEnter.reference(), catcherFinally.reference()));
3106 
3107             BiFunction<Block.Builder, Op, Block.Builder> syncExitTransformer = composeFirst(inherited, (block, op) -> {
3108                 if (op instanceof CoreOp.ReturnOp ||
3109                     (op instanceof StatementTargetOp lop && ifExitFromSynchronized(lop))) {
3110                     // Monitor exit
3111                     block.add(monitorExit(monitorTarget));
3112                     // Exit the exception region
3113                     Block.Builder exitRegion = block.block();
3114                     block.add(exceptionRegionExit(enter, exitRegion.reference()));
3115                     return exitRegion;
3116                 } else {
3117                     return block;
3118                 }
3119             });
3120 
3121             syncRegionEnter.transformBody(blockBody, List.of(), loweringTransformer(syncExitTransformer, (block, op) -> {
3122                 if (op instanceof CoreOp.YieldOp) {
3123                     // Monitor exit
3124                     block.add(monitorExit(monitorTarget));
3125                     // Exit the exception region
3126                     block.add(exceptionRegionExit(enter, exit.reference()));
3127                     return block;
3128                 } else {
3129                     return null;
3130                 }
3131             }));
3132 
3133             // The catcher, with an exception region back branching to itself
3134             Block.Builder catcherFinallyRegionEnter = b.block();
3135             Op.Result catcherEnter = catcherFinally.add(exceptionRegionEnter(
3136                     catcherFinallyRegionEnter.reference(), catcherFinally.reference()));
3137 
3138             // Monitor exit
3139             catcherFinallyRegionEnter.add(monitorExit(monitorTarget));
3140             Block.Builder catcherFinallyRegionExit = b.block();
3141             // Exit the exception region
3142             catcherFinallyRegionEnter.add(exceptionRegionExit(
3143                     catcherEnter, catcherFinallyRegionExit.reference()));
3144             // Rethrow outside of region
3145             Block.Parameter t = catcherFinally.parameter(type(Throwable.class));
3146             catcherFinallyRegionExit.add(throw_(t));
3147 
3148             return exit;
3149         }
3150 
3151         Block.Builder lowerExpr(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
3152             Block.Builder exprExit = b.block(exprBody.bodySignature().returnType());
3153             b.transformBody(exprBody, List.of(), loweringTransformer(inherited, (block, op) -> {
3154                 if (op instanceof CoreOp.YieldOp yop) {
3155                     Value monitorTarget = block.context().getValue(yop.yieldValue());
3156                     block.add(branch(exprExit.reference(monitorTarget)));
3157                     return block;
3158                 } else {
3159                     return null;
3160                 }
3161             }));
3162             return exprExit;
3163         }
3164 
3165         boolean ifExitFromSynchronized(StatementTargetOp lop) {
3166             Op target = lop.target();
3167             return target == this || target.isAncestorOf(this);
3168         }
3169 
3170         @Override
3171         public CodeType resultType() {
3172             return VOID;
3173         }
3174     }
3175 
3176     /**
3177      * The labeled operation, that can model Java language labeled statements.
3178      * <p>
3179      * Labeled operations feature one body, the labeled body. The labeled body accepts no arguments and
3180      * yield {@linkplain JavaType#VOID no value}.
3181      * <p>
3182      * The entry block of the labeled body always begins with a {@linkplain ConstantOp} constant modeling
3183      * the label associated with the labeled statement, followed by the statement being labeled.
3184      * <p>
3185      * The result type of a labeled operation is {@link JavaType#VOID}.
3186      *
3187      * @jls 14.7 Labeled Statements
3188      */
3189     @OpDeclaration(LabeledOp.NAME)
3190     public static final class LabeledOp extends JavaOp
3191             implements Op.Nested, Op.Lowerable, JavaStatement {
3192         static final String NAME = "java.labeled";
3193 
3194         final Body body;
3195 
3196         LabeledOp(ExternalizedOp def) {
3197             if (!def.operands().isEmpty()) {
3198                 throw new IllegalStateException("Operation must have no operands");
3199             }
3200 
3201             this(def.bodyDefinitions().get(0));
3202         }
3203 
3204         LabeledOp(LabeledOp that, CodeContext cc, CodeTransformer ct) {
3205             super(that, cc);
3206 
3207             // Copy body
3208             this.body = that.body.transform(cc, ct).build(this);
3209         }
3210 
3211         @Override
3212         public LabeledOp transform(CodeContext cc, CodeTransformer ct) {
3213             return new LabeledOp(this, cc, ct);
3214         }
3215 
3216         LabeledOp(Body.Builder bodyC) {
3217             super(List.of());
3218 
3219             this.body = bodyC.build(this);
3220             if (!body.bodySignature().returnType().equals(VOID)) {
3221                 throw new IllegalArgumentException("Body should return void: " + body.bodySignature());
3222             }
3223             if (!body.bodySignature().parameterTypes().isEmpty()) {
3224                 throw new IllegalArgumentException("Body should have zero parameters: " + body.bodySignature());
3225             }
3226         }
3227 
3228         @Override
3229         public List<Body> bodies() {
3230             return List.of(body);
3231         }
3232 
3233         /**
3234          * {@return the labeled body}
3235          */
3236         public Body body() {
3237             return body;
3238         }
3239 
3240         /**
3241          * {@return the label associated with this labeled operation}
3242          */
3243         public Op label() {
3244             return body.entryBlock().firstOp();
3245         }
3246 
3247         /**
3248          * {@return the label identifier, the operation result of the label}
3249          */
3250         public Op.Result labelIdentifier() {
3251             return label().result();
3252         }
3253 
3254         /**
3255          * {@return the first operation associated with this labeled operation}
3256          */
3257         public Op target() {
3258             return body.entryBlock().nextOp(label());
3259         }
3260 
3261         @Override
3262         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
3263             Block.Builder exit = b.block();
3264             BranchTarget.setBranchTarget(b.context(), this, exit, null);
3265 
3266             AtomicBoolean first = new AtomicBoolean();
3267             b.transformBody(body, List.of(), loweringTransformer(inherited, (block, op) -> {
3268                 // Drop first operation that corresponds to the label
3269                 if (!first.get()) {
3270                     first.set(true);
3271                     return block;
3272                 }
3273 
3274                 if (op instanceof CoreOp.YieldOp) {
3275                     block.add(branch(exit.reference()));
3276                     return block;
3277                 } else {
3278                     return null;
3279                 }
3280             }));
3281 
3282             return exit;
3283         }
3284 
3285         @Override
3286         public CodeType resultType() {
3287             return VOID;
3288         }
3289     }
3290 
3291     /**
3292      * The if operation, that can model Java language if statements.
3293      * <p>
3294      * If operations feature multiple bodies. Some bodies, called <em>predicate bodies</em>, model conditions that
3295      * determine which execution path the evaluation of the if operation should take. Other bodies, called
3296      * <em>action bodies</em>, model the statements to be executed when the preceding predicate is satisfied.
3297      * <p>
3298      * Each predicate body has a corresponding action body, and there may be a trailing action body with no
3299      * predicate, modeling the code after the Java {@code else} keyword.
3300      * <p>
3301      * Predicate bodies should accept no arguments and yield a {@link JavaType#BOOLEAN} value.
3302      * Action bodies similarly accept no arguments, and yield {@linkplain JavaType#VOID no value}.
3303      * <p>
3304      * The result type of an if operation is {@link JavaType#VOID}.
3305      *
3306      * @jls 14.9 The if Statement
3307      */
3308     @OpDeclaration(IfOp.NAME)
3309     public static final class IfOp extends JavaOp
3310             implements Op.Nested, Op.Lowerable, JavaStatement {
3311 
3312         static final FunctionType PREDICATE_SIGNATURE = CoreType.functionType(BOOLEAN);
3313 
3314         static final FunctionType ACTION_SIGNATURE = CoreType.FUNCTION_TYPE_VOID;
3315 
3316         /**
3317          * Builder for the initial predicate body of an if operation.
3318          */
3319         public static class IfBuilder {
3320             final Body.Builder connectedAncestorBody;
3321             final List<Body.Builder> bodies;
3322 
3323             IfBuilder(Body.Builder connectedAncestorBody) {
3324                 this.connectedAncestorBody = connectedAncestorBody;
3325                 this.bodies = new ArrayList<>();
3326             }
3327 
3328             /**
3329              * Begins an if operation by adding the initial predicate body.
3330              *
3331              * @param c a consumer that populates the predicate body
3332              * @return a builder to add an action body to the if operation
3333              */
3334             public ThenBuilder if_(Consumer<Block.Builder> c) {
3335                 Body.Builder body = Body.Builder.of(connectedAncestorBody, PREDICATE_SIGNATURE);
3336                 c.accept(body.entryBlock());
3337                 bodies.add(body);
3338 
3339                 return new ThenBuilder(connectedAncestorBody, bodies);
3340             }
3341         }
3342 
3343         /**
3344          * Builder for the action body of an if operation.
3345          */
3346         public static class ThenBuilder {
3347             final Body.Builder connectedAncestorBody;
3348             final List<Body.Builder> bodies;
3349 
3350             ThenBuilder(Body.Builder connectedAncestorBody, List<Body.Builder> bodies) {
3351                 this.connectedAncestorBody = connectedAncestorBody;
3352                 this.bodies = bodies;
3353             }
3354 
3355             /**
3356              * Adds an action body to the if operation.
3357              *
3358              * @param c a consumer that populates the action body
3359              * @return a builder for further predicate and action bodies
3360              */
3361             public ElseIfBuilder then(Consumer<Block.Builder> c) {
3362                 Body.Builder body = Body.Builder.of(connectedAncestorBody, ACTION_SIGNATURE);
3363                 c.accept(body.entryBlock());
3364                 bodies.add(body);
3365 
3366                 return new ElseIfBuilder(connectedAncestorBody, bodies);
3367             }
3368 
3369             /**
3370              * Adds an empty action body to the if operation.
3371              * @return a builder for further predicate and action bodies
3372              */
3373             public ElseIfBuilder then() {
3374                 Body.Builder body = Body.Builder.of(connectedAncestorBody, ACTION_SIGNATURE);
3375                 body.entryBlock().add(core_yield());
3376                 bodies.add(body);
3377 
3378                 return new ElseIfBuilder(connectedAncestorBody, bodies);
3379             }
3380         }
3381 
3382         /**
3383          * Builder for additional predicate and action bodies of an if operation.
3384          */
3385         public static class ElseIfBuilder {
3386             final Body.Builder connectedAncestorBody;
3387             final List<Body.Builder> bodies;
3388 
3389             ElseIfBuilder(Body.Builder connectedAncestorBody, List<Body.Builder> bodies) {
3390                 this.connectedAncestorBody = connectedAncestorBody;
3391                 this.bodies = bodies;
3392             }
3393 
3394             /**
3395              * Adds a predicate body to the if operation.
3396              *
3397              * @param c a consumer that populates the predicate body
3398              * @return a builder to add an action body to the if operation
3399              */
3400             public ThenBuilder elseif(Consumer<Block.Builder> c) {
3401                 Body.Builder body = Body.Builder.of(connectedAncestorBody, PREDICATE_SIGNATURE);
3402                 c.accept(body.entryBlock());
3403                 bodies.add(body);
3404 
3405                 return new ThenBuilder(connectedAncestorBody, bodies);
3406             }
3407 
3408             /**
3409              * Completes the if operation by adding the final action body.
3410              *
3411              * @param c a consumer that populates the action body
3412              * @return the completed if operation
3413              */
3414             public IfOp else_(Consumer<Block.Builder> c) {
3415                 Body.Builder body = Body.Builder.of(connectedAncestorBody, ACTION_SIGNATURE);
3416                 c.accept(body.entryBlock());
3417                 bodies.add(body);
3418 
3419                 return new IfOp(bodies);
3420             }
3421 
3422             /**
3423              * Complete the if operation with an empty action body.
3424              * @return the completed if operation
3425              */
3426             public IfOp else_() {
3427                 Body.Builder body = Body.Builder.of(connectedAncestorBody, ACTION_SIGNATURE);
3428                 body.entryBlock().add(core_yield());
3429                 bodies.add(body);
3430 
3431                 return new IfOp(bodies);
3432             }
3433         }
3434 
3435         static final String NAME = "java.if";
3436 
3437         final List<Body> bodies;
3438 
3439         IfOp(ExternalizedOp def) {
3440             if (!def.operands().isEmpty()) {
3441                 throw new IllegalStateException("Operation must have no operands");
3442             }
3443 
3444             this(def.bodyDefinitions());
3445         }
3446 
3447         IfOp(IfOp that, CodeContext cc, CodeTransformer ct) {
3448             super(that, cc);
3449 
3450             // Copy body
3451             this.bodies = that.bodies.stream()
3452                     .map(b -> b.transform(cc, ct).build(this)).toList();
3453         }
3454 
3455         @Override
3456         public IfOp transform(CodeContext cc, CodeTransformer ct) {
3457             return new IfOp(this, cc, ct);
3458         }
3459 
3460         IfOp(List<Body.Builder> bodyCs) {
3461             super(List.of());
3462 
3463             // Normalize by adding an empty else action
3464             // @@@ Is this needed?
3465             if (bodyCs.size() % 2 == 0) {
3466                 bodyCs = new ArrayList<>(bodyCs);
3467                 Body.Builder end = Body.Builder.of(bodyCs.get(0).connectedAncestorBody(),
3468                         CoreType.FUNCTION_TYPE_VOID);
3469                 end.entryBlock().add(core_yield());
3470                 bodyCs.add(end);
3471             }
3472 
3473             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
3474 
3475             if (bodies.size() < 2) {
3476                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
3477             }
3478             for (int i = 0; i < bodies.size(); i += 2) {
3479                 Body action;
3480                 if (i == bodies.size() - 1) {
3481                     action = bodies.get(i);
3482                 } else {
3483                     action = bodies.get(i + 1);
3484                     Body fromPred = bodies.get(i);
3485                     if (!fromPred.bodySignature().equals(CoreType.functionType(BOOLEAN))) {
3486                 throw new IllegalArgumentException("Illegal predicate body signature: " + fromPred.bodySignature());
3487                     }
3488                 }
3489                 if (!action.bodySignature().equals(CoreType.FUNCTION_TYPE_VOID)) {
3490                 throw new IllegalArgumentException("Illegal action body signature: " + action.bodySignature());
3491                 }
3492             }
3493         }
3494 
3495         @Override
3496         public List<Body> bodies() {
3497             return bodies;
3498         }
3499 
3500         @Override
3501         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
3502             Block.Builder exit = b.block();
3503             BranchTarget.setBranchTarget(b.context(), this, exit, null);
3504 
3505             // Create predicate and action blocks
3506             List<Block.Builder> builders = new ArrayList<>();
3507             for (int i = 0; i < bodies.size(); i += 2) {
3508                 if (i == bodies.size() - 1) {
3509                     builders.add(b.block());
3510                 } else {
3511                     builders.add(i == 0 ? b : b.block());
3512                     builders.add(b.block());
3513                 }
3514             }
3515 
3516             for (int i = 0; i < bodies.size(); i += 2) {
3517                 Body actionBody;
3518                 Block.Builder action;
3519                 if (i == bodies.size() - 1) {
3520                     actionBody = bodies.get(i);
3521                     action = builders.get(i);
3522                 } else {
3523                     Body predBody = bodies.get(i);
3524                     actionBody = bodies.get(i + 1);
3525 
3526                     Block.Builder pred = builders.get(i);
3527                     action = builders.get(i + 1);
3528                     Block.Builder next = builders.get(i + 2);
3529 
3530                     pred.transformBody(predBody, List.of(), loweringTransformer(inherited, (block, op) -> {
3531                         if (op instanceof CoreOp.YieldOp yo) {
3532                             block.add(conditionalBranch(block.context().getValue(yo.yieldValue()),
3533                                     action.reference(), next.reference()));
3534                             return block;
3535                         } else {
3536                             return null;
3537                         }
3538                     }));
3539                 }
3540 
3541                 action.transformBody(actionBody, List.of(), loweringTransformer(inherited, (block, op) -> {
3542                     if (op instanceof CoreOp.YieldOp) {
3543                         block.add(branch(exit.reference()));
3544                         return block;
3545                     } else {
3546                         return null;
3547                     }
3548                 }));
3549             }
3550 
3551             return exit;
3552         }
3553 
3554         @Override
3555         public CodeType resultType() {
3556             return VOID;
3557         }
3558     }
3559 
3560     /**
3561      * An operation modeling a Java switch statement or expression.
3562      * <p>
3563      * Switch operations are parameterized by a selector value.
3564      * They feature a sequence of case bodies, each modeled as a pair of bodies: a <em>predicate body</em> and an
3565      * <em>action body</em>.
3566      * <p>
3567      * Each predicate body accepts one argument, the selector value, and yields a {@link JavaType#BOOLEAN} value.
3568      * Each action body yields a value of the same type {@code T}. For switch statement operations, {@code T} is
3569      * {@code void}. For switch expression operations, {@code T} is the switch expression type.
3570      *
3571      * @jls 14.11 The switch Statement
3572      * @jls 15.28 {@code switch} Expressions
3573      */
3574     public abstract static sealed class JavaSwitchOp extends JavaOp implements Op.Nested, Op.Lowerable
3575             permits SwitchStatementOp, SwitchExpressionOp {
3576 
3577         final List<Body> bodies;
3578 
3579         JavaSwitchOp(JavaSwitchOp that, CodeContext cc, CodeTransformer ct) {
3580             super(that, cc);
3581 
3582             // Copy body
3583             this.bodies = that.bodies.stream()
3584                     .map(b -> b.transform(cc, ct).build(this)).toList();
3585         }
3586 
3587         JavaSwitchOp(Value target, List<Body.Builder> bodyCs) {
3588             super(List.of(target));
3589 
3590             // Each case is modeled as a contiguous pair of bodies
3591             // The first body models the case labels, and the second models the case statements
3592             // The labels body has a parameter whose type is target operand's type and returns a boolean value
3593             // The action body has no parameters and returns void
3594             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
3595         }
3596 
3597         @Override
3598         public List<Body> bodies() {
3599             return bodies;
3600         }
3601 
3602         @Override
3603         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
3604             Value selectorExpression = b.context().getValue(operands().get(0));
3605 
3606             // @@@ we can add this during model generation
3607             // if no case null, add one that throws NPE
3608             if (!(selectorExpression.type() instanceof PrimitiveType) && !haveNullCase()) {
3609                 Block.Builder throwBlock = b.block();
3610                 throwBlock.add(throw_(
3611                         throwBlock.add(new_(MethodRef.constructor(NullPointerException.class)))
3612                 ));
3613 
3614                 Block.Builder continueBlock = b.block();
3615 
3616                 Result p = b.add(invoke(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
3617                         selectorExpression, b.add(constant(J_L_OBJECT, null))));
3618                 b.add(conditionalBranch(p, throwBlock.reference(), continueBlock.reference()));
3619 
3620                 b = continueBlock;
3621             }
3622 
3623             int defLabelIndex = -1;
3624             for (int i = 0; i < bodies().size(); i+=2) {
3625                 Block eb = bodies().get(i).entryBlock();
3626                 // @@@ confusing YieldOp with Core.YieldOp in checks
3627                 if (eb.terminatingOp() instanceof CoreOp.YieldOp yop && yop.yieldValue() instanceof Op.Result r
3628                         && r.op() instanceof ConstantOp cop && cop.resultType().equals(BOOLEAN)) {
3629                     defLabelIndex = i;
3630                     break;
3631                 }
3632             }
3633             if (defLabelIndex == -1 && this instanceof SwitchExpressionOp) {
3634                 // if it's a switch expression, it must have a default
3635                 // if not explicit, it's an unconditional pattern which is the last label
3636                 defLabelIndex = bodies().size() - 2;
3637             }
3638 
3639             List<Block.Builder> blocks = new ArrayList<>();
3640             for (int i = 0; i < bodies().size(); i++) {
3641                 Block.Builder bb;
3642                 if (i == defLabelIndex) {
3643                     // we don't need a block for default label
3644                     bb = null;
3645                 } else {
3646                     bb = b.block();
3647                 }
3648                 blocks.add(bb);
3649             }
3650             // append ops of the first non default label to b
3651             for (int i = 0; i < blocks.size(); i+=2) {
3652                 if (blocks.get(i) == null) {
3653                     continue;
3654                 }
3655                 blocks.set(i, b);
3656                 break;
3657             }
3658 
3659             Block.Builder exit;
3660             if (bodies().isEmpty()) {
3661                 exit = b;
3662             } else {
3663                 exit = resultType() == VOID ? b.block() : b.block(resultType());
3664                 if (!exit.parameters().isEmpty()) {
3665                     exit.context().mapValue(result(), exit.parameters().get(0));
3666                 }
3667             }
3668 
3669             BranchTarget.setBranchTarget(b.context(), this, exit, null);
3670             // map statement body to nextExprBlock
3671             // this mapping will be used for lowering SwitchFallThroughOp
3672             for (int i = 1; i < bodies().size() - 2; i+=2) {
3673                 BranchTarget.setBranchTarget(b.context(), bodies().get(i), null, blocks.get(i + 2));
3674             }
3675 
3676             for (int i = 0; i < bodies().size(); i+=2) {
3677                 if (i == defLabelIndex) {
3678                     continue;
3679                 }
3680                 Block.Builder statement = blocks.get(i + 1);
3681                 boolean isLastLabel = i == blocks.size() - 2;
3682                 Block.Builder nextLabel = isLastLabel ? null : blocks.get(i + 2);
3683                 int finalDefLabelIndex = defLabelIndex;
3684                 blocks.get(i).transformBody(bodies().get(i), List.of(selectorExpression), loweringTransformer(inherited,
3685                         (block, op) -> switch (op) {
3686                             case CoreOp.YieldOp yop -> {
3687                                 Block.Reference falseTarget;
3688                                 if (nextLabel != null) {
3689                                     falseTarget = nextLabel.reference();
3690                                 } else if (finalDefLabelIndex != -1) {
3691                                     falseTarget = blocks.get(finalDefLabelIndex + 1).reference();
3692                                 } else {
3693                                     falseTarget = exit.reference();
3694                                 }
3695                                 block.add(conditionalBranch(block.context().getValue(yop.yieldValue()),
3696                                         statement.reference(), falseTarget));
3697                                 yield block;
3698                             }
3699                             default -> null;
3700                         }));
3701 
3702                 blocks.get(i + 1).transformBody(bodies().get(i + 1), List.of(), loweringTransformer(inherited,
3703                         (block, op) -> switch (op) {
3704                             case CoreOp.YieldOp yop -> {
3705                                 List<Value> args = yop.yieldValue() == null ? List.of() : List.of(block.context().getValue(yop.yieldValue()));
3706                                 block.add(branch(exit.reference(args)));
3707                                 yield block;
3708                             }
3709                             default -> null;
3710                         }));
3711             }
3712 
3713             if (defLabelIndex != -1) {
3714                 blocks.get(defLabelIndex + 1).transformBody(bodies().get(defLabelIndex + 1), List.of(), loweringTransformer(inherited,
3715                         (block, op) -> switch (op) {
3716                             case CoreOp.YieldOp yop -> {
3717                                 List<Value> args = yop.yieldValue() == null ? List.of() : List.of(block.context().getValue(yop.yieldValue()));
3718                                 block.add(branch(exit.reference(args)));
3719                                 yield block;
3720                             }
3721                             default -> null;
3722                         }));
3723             }
3724 
3725             return exit;
3726         }
3727 
3728         boolean haveNullCase() {
3729             /*
3730             case null is modeled like this:
3731             (%4 : T)boolean -> {
3732                 %5 : java.lang.Object = constant @null;
3733                 %6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
3734                 yield %6;
3735             }
3736             * */
3737             for (int i = 0; i < bodies().size() - 2; i+=2) {
3738                 Body labelBody = bodies().get(i);
3739                 if (labelBody.blocks().size() != 1) {
3740                     continue; // we skip, for now
3741                 }
3742                 Op terminatingOp = bodies().get(i).entryBlock().terminatingOp();
3743                 //@@@ when op pattern matching is ready, we can use it
3744                 if (terminatingOp instanceof CoreOp.YieldOp yieldOp &&
3745                         yieldOp.yieldValue() instanceof Op.Result opr &&
3746                         opr.op() instanceof InvokeOp invokeOp &&
3747             invokeOp.invokeReference().equals(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class)) &&
3748                         invokeOp.operands().stream().anyMatch(o -> o instanceof Op.Result r && r.op() instanceof ConstantOp cop && cop.value() == null)) {
3749                     return true;
3750                 }
3751             }
3752             return false;
3753         }
3754     }
3755 
3756     /**
3757      * The switch expression operation, that can model Java language switch expressions.
3758      * <p>
3759      * For switch expression operations, action bodies yield a value of type {@code T}, where {@code T} is also the
3760      * type of the switch expression operation.
3761      *
3762      * @jls 15.28 {@code switch} Expressions
3763      */
3764     @OpDeclaration(SwitchExpressionOp.NAME)
3765     public static final class SwitchExpressionOp extends JavaSwitchOp
3766             implements JavaExpression {
3767         static final String NAME = "java.switch.expression";
3768 
3769         final CodeType resultType;
3770 
3771         SwitchExpressionOp(ExternalizedOp def) {
3772             this(def.resultType(), def.operands().get(0), def.bodyDefinitions());
3773         }
3774 
3775         SwitchExpressionOp(SwitchExpressionOp that, CodeContext cc, CodeTransformer ct) {
3776             super(that, cc, ct);
3777 
3778             this.resultType = that.resultType;
3779         }
3780 
3781         @Override
3782         public SwitchExpressionOp transform(CodeContext cc, CodeTransformer ct) {
3783             return new SwitchExpressionOp(this, cc, ct);
3784         }
3785 
3786         SwitchExpressionOp(CodeType resultType, Value target, List<Body.Builder> bodyCs) {
3787             super(target, bodyCs);
3788 
3789             this.resultType = resultType == null ? bodies.get(1).yieldType() : resultType;
3790         }
3791 
3792         @Override
3793         public CodeType resultType() {
3794             return resultType;
3795         }
3796     }
3797 
3798     /**
3799      * The switch statement operation, that can model Java language switch statement.
3800      * <p>
3801      * For switch statement operations, action bodies yield {@linkplain JavaType#VOID no value}.
3802      * <p>
3803      * The result type of a switch statement operation is {@link JavaType#VOID}.
3804      *
3805      * @jls 14.11 The switch Statement
3806      */
3807     @OpDeclaration(SwitchStatementOp.NAME)
3808     public static final class SwitchStatementOp extends JavaSwitchOp
3809             implements JavaStatement {
3810         static final String NAME = "java.switch.statement";
3811 
3812         SwitchStatementOp(ExternalizedOp def) {
3813             this(def.operands().get(0), def.bodyDefinitions());
3814         }
3815 
3816         SwitchStatementOp(SwitchStatementOp that, CodeContext cc, CodeTransformer ct) {
3817             super(that, cc, ct);
3818         }
3819 
3820         @Override
3821         public SwitchStatementOp transform(CodeContext cc, CodeTransformer ct) {
3822             return new SwitchStatementOp(this, cc, ct);
3823         }
3824 
3825         SwitchStatementOp(Value target, List<Body.Builder> bodyCs) {
3826             super(target, bodyCs);
3827         }
3828 
3829         @Override
3830         public CodeType resultType() {
3831             return VOID;
3832         }
3833     }
3834 
3835     /**
3836      * The switch fall-through operation, that can model fall-through to the next statement in the switch block after
3837      * the last statement of the current switch label.
3838      * <p>
3839      * A switch fall-through operation is a body-terminating operation.
3840      */
3841     @OpDeclaration(SwitchFallthroughOp.NAME)
3842     public static final class SwitchFallthroughOp extends JavaOp
3843             implements Op.BodyTerminating, Op.Lowerable {
3844         static final String NAME = "java.switch.fallthrough";
3845 
3846         SwitchFallthroughOp(ExternalizedOp def) {
3847             this();
3848         }
3849 
3850         SwitchFallthroughOp(SwitchFallthroughOp that, CodeContext cc) {
3851             super(that, cc);
3852         }
3853 
3854         @Override
3855         public SwitchFallthroughOp transform(CodeContext cc, CodeTransformer ct) {
3856             return new SwitchFallthroughOp(this, cc);
3857         }
3858 
3859         SwitchFallthroughOp() {
3860             super(List.of());
3861         }
3862 
3863         @Override
3864         public CodeType resultType() {
3865             return VOID;
3866         }
3867 
3868         @Override
3869         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
3870             return lower(b, BranchTarget::continueBlock);
3871         }
3872 
3873         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
3874             BranchTarget t = BranchTarget.getBranchTarget(b.context(), ancestorBody());
3875             if (t != null) {
3876                 b.add(branch(f.apply(t).reference()));
3877             } else {
3878                 throw new IllegalStateException("No branch target for operation: " + this);
3879             }
3880             return b;
3881         }
3882     }
3883 
3884     /**
3885      * The for operation, that can model a Java language basic for statement.
3886      * <p>
3887      * For operations feature four bodies that model a basic {@code for} statement:
3888      * an <em>initialization body</em>, a <em>predicate body</em>, an <em>update body</em>, and a <em>loop body</em>.
3889      * <p>
3890      * The initialization body accepts no arguments and yields the loop state, of type {@code S}. For instance,
3891      * a loop with a single loop variable of type {@code T} might use a loop state of type {@code T}.
3892      * A loop with two loop variables of type {@code X} and {@code Y} might use a loop state whose type is
3893      * a {@linkplain TupleType tuple type}, such as {@code (X, Y)}. A loop with no loop variables might use
3894      * a loop state of type {@link JavaType#VOID}, and have its initialization body yield no value.
3895      * <p>
3896      * The predicate body accepts an argument of type {@code S} and yields a {@link JavaType#BOOLEAN} value.
3897      * The update and loop bodies accept an argument of type {@code S} and yield {@linkplain JavaType#VOID no value}.
3898      * <p>
3899      * The result type of a for operation is {@link JavaType#VOID}.
3900      *
3901      * @jls 14.14.1 The basic for Statement
3902      */
3903     @OpDeclaration(ForOp.NAME)
3904     public static final class ForOp extends JavaOp
3905             implements Op.Loop, Op.Lowerable, JavaStatement {
3906 
3907         /**
3908          * Builder for the initialization body of a for operation.
3909          */
3910         public static final class InitBuilder {
3911             final Body.Builder connectedAncestorBody;
3912             final List<? extends CodeType> initTypes;
3913 
3914             InitBuilder(Body.Builder connectedAncestorBody,
3915                         List<? extends CodeType> initTypes) {
3916                 this.connectedAncestorBody = connectedAncestorBody;
3917                 this.initTypes = initTypes.stream().map(CoreType::varType).toList();
3918             }
3919 
3920             /**
3921              * Builds the initialization body of a for-loop.
3922              *
3923              * @param c a consumer that populates the initialization body
3924              * @return a builder for specifying the loop predicate body
3925              */
3926             public ForOp.CondBuilder init(Consumer<Block.Builder> c) {
3927                 Body.Builder init = Body.Builder.of(connectedAncestorBody,
3928                         CoreType.functionType(CoreType.tupleType(initTypes)));
3929                 c.accept(init.entryBlock());
3930 
3931                 return new CondBuilder(connectedAncestorBody, initTypes, init);
3932             }
3933         }
3934 
3935         /**
3936          * Builder for the predicate body of a for operation.
3937          */
3938         public static final class CondBuilder {
3939             final Body.Builder connectedAncestorBody;
3940             final List<? extends CodeType> initTypes;
3941             final Body.Builder init;
3942 
3943             CondBuilder(Body.Builder connectedAncestorBody,
3944                                List<? extends CodeType> initTypes,
3945                                Body.Builder init) {
3946                 this.connectedAncestorBody = connectedAncestorBody;
3947                 this.initTypes = initTypes;
3948                 this.init = init;
3949             }
3950 
3951             /**
3952              * Builds the predicate body of a for-loop.
3953              *
3954              * @param c a consumer that populates the predicate body
3955              * @return a builder for specifying the update body
3956              */
3957             public ForOp.UpdateBuilder cond(Consumer<Block.Builder> c) {
3958                 Body.Builder cond = Body.Builder.of(connectedAncestorBody,
3959                         CoreType.functionType(BOOLEAN, initTypes));
3960                 c.accept(cond.entryBlock());
3961 
3962                 return new UpdateBuilder(connectedAncestorBody, initTypes, init, cond);
3963             }
3964         }
3965 
3966         /**
3967          * Builder for the update body of a for operation.
3968          */
3969         public static final class UpdateBuilder {
3970             final Body.Builder connectedAncestorBody;
3971             final List<? extends CodeType> initTypes;
3972             final Body.Builder init;
3973             final Body.Builder cond;
3974 
3975             UpdateBuilder(Body.Builder connectedAncestorBody,
3976                                  List<? extends CodeType> initTypes,
3977                                  Body.Builder init, Body.Builder cond) {
3978                 this.connectedAncestorBody = connectedAncestorBody;
3979                 this.initTypes = initTypes;
3980                 this.init = init;
3981                 this.cond = cond;
3982             }
3983 
3984             /**
3985              * Builds the update body of a for-loop.
3986              *
3987              * @param c a consumer that populates the update body
3988              * @return a builder for specifying the loop body
3989              */
3990             public ForOp.BodyBuilder update(Consumer<Block.Builder> c) {
3991                 Body.Builder update = Body.Builder.of(connectedAncestorBody,
3992                         CoreType.functionType(VOID, initTypes));
3993                 c.accept(update.entryBlock());
3994 
3995                 return new BodyBuilder(connectedAncestorBody, initTypes, init, cond, update);
3996             }
3997         }
3998 
3999         /**
4000          * Builder for the body (main logic) portion of a for-loop.
4001          */
4002         public static final class BodyBuilder {
4003             final Body.Builder connectedAncestorBody;
4004             final List<? extends CodeType> initTypes;
4005             final Body.Builder init;
4006             final Body.Builder cond;
4007             final Body.Builder update;
4008 
4009             BodyBuilder(Body.Builder connectedAncestorBody,
4010                                List<? extends CodeType> initTypes,
4011                                Body.Builder init, Body.Builder cond, Body.Builder update) {
4012                 this.connectedAncestorBody = connectedAncestorBody;
4013                 this.initTypes = initTypes;
4014                 this.init = init;
4015                 this.cond = cond;
4016                 this.update = update;
4017             }
4018 
4019             /**
4020              * Completes for operation by adding the loop body.
4021              *
4022              * @param c a consumer that populates the loop body
4023              * @return the completed for-loop operation
4024              */
4025             public ForOp body(Consumer<Block.Builder> c) {
4026                 Body.Builder body = Body.Builder.of(connectedAncestorBody,
4027                         CoreType.functionType(VOID, initTypes));
4028                 c.accept(body.entryBlock());
4029 
4030                 return new ForOp(init, cond, update, body);
4031             }
4032         }
4033 
4034         static final String NAME = "java.for";
4035 
4036         final Body initBody;
4037         final Body condBody;
4038         final Body updateBody;
4039         final Body loopBody;
4040 
4041         ForOp(ExternalizedOp def) {
4042             this(def.bodyDefinitions().get(0),
4043                     def.bodyDefinitions().get(1),
4044                     def.bodyDefinitions().get(2),
4045                     def.bodyDefinitions().get(3));
4046         }
4047 
4048         ForOp(ForOp that, CodeContext cc, CodeTransformer ct) {
4049             super(that, cc);
4050 
4051             this.initBody = that.initBody.transform(cc, ct).build(this);
4052             this.condBody = that.condBody.transform(cc, ct).build(this);
4053             this.updateBody = that.updateBody.transform(cc, ct).build(this);
4054             this.loopBody = that.loopBody.transform(cc, ct).build(this);
4055         }
4056 
4057         @Override
4058         public ForOp transform(CodeContext cc, CodeTransformer ct) {
4059             return new ForOp(this, cc, ct);
4060         }
4061 
4062         ForOp(Body.Builder initC,
4063               Body.Builder condC,
4064               Body.Builder updateC,
4065               Body.Builder bodyC) {
4066             super(List.of());
4067 
4068             this.initBody = initC.build(this);
4069 
4070             this.condBody = condC.build(this);
4071 
4072             this.updateBody = updateC.build(this);
4073             if (!updateBody.bodySignature().returnType().equals(VOID)) {
4074                 throw new IllegalArgumentException("Update should return void: " + updateBody.bodySignature());
4075             }
4076 
4077             this.loopBody = bodyC.build(this);
4078             if (!loopBody.bodySignature().returnType().equals(VOID)) {
4079                 throw new IllegalArgumentException("Body should return void: " + loopBody.bodySignature());
4080             }
4081         }
4082 
4083         @Override
4084         public List<Body> bodies() {
4085             return List.of(initBody, condBody, updateBody, loopBody);
4086         }
4087 
4088         /**
4089          * {@return the initialization body}
4090          */
4091         public Body initBody() {
4092             return initBody;
4093         }
4094 
4095         /**
4096          * {@return the loop condition (predicate) body}
4097          */
4098         public Body condBody() {
4099             return condBody;
4100         }
4101 
4102         /**
4103          * {@return the update body}
4104          */
4105         public Body updateBody() {
4106             return updateBody;
4107         }
4108 
4109         @Override
4110         public Body loopBody() {
4111             return loopBody;
4112         }
4113 
4114         @Override
4115         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
4116             Block.Builder header = b.block();
4117             Block.Builder body = b.block();
4118             Block.Builder update = b.block();
4119             Block.Builder exit = b.block();
4120 
4121             List<Value> initValues = new ArrayList<>();
4122             // @@@ Init body has one yield operation yielding
4123             //  void, a single variable, or a tuple of one or more variables
4124             b.transformBody(initBody, List.of(), loweringTransformer(inherited, (block, op) -> switch (op) {
4125                 case TupleOp _ -> {
4126                     // Drop Tuple if a yielded
4127                     boolean isResult = op.result().uses().size() == 1 &&
4128                             op.result().uses().stream().allMatch(r -> r.op() instanceof CoreOp.YieldOp);
4129                     if (!isResult) {
4130                         block.add(op);
4131                     }
4132                     yield block;
4133                 }
4134                 case CoreOp.YieldOp yop -> {
4135                     if (yop.yieldValue() == null) {
4136                         block.add(branch(header.reference()));
4137                         yield block;
4138                     } else if (yop.yieldValue() instanceof Result or) {
4139                         if (or.op() instanceof TupleOp top) {
4140                             initValues.addAll(block.context().getValues(top.operands()));
4141                         } else {
4142                             initValues.addAll(block.context().getValues(yop.operands()));
4143                         }
4144                         block.add(branch(header.reference()));
4145                         yield block;
4146                     }
4147 
4148                     throw new IllegalStateException("Bad yield operation");
4149                 }
4150                 default -> null;
4151             }));
4152 
4153             header.transformBody(condBody, initValues, loweringTransformer(inherited, (block, op) -> {
4154                 if (op instanceof CoreOp.YieldOp yo) {
4155                     block.add(conditionalBranch(block.context().getValue(yo.yieldValue()),
4156                             body.reference(), exit.reference()));
4157                     return block;
4158                 } else {
4159                     return null;
4160                 }
4161             }));
4162 
4163             BranchTarget.setBranchTarget(b.context(), this, exit, update);
4164 
4165             body.transformBody(this.loopBody, initValues, loweringTransformer(inherited, (_, _) -> null));
4166 
4167             update.transformBody(this.updateBody, initValues, loweringTransformer(inherited, (block, op) -> {
4168                 if (op instanceof CoreOp.YieldOp) {
4169                     block.add(branch(header.reference()));
4170                     return block;
4171                 } else {
4172                     return null;
4173                 }
4174             }));
4175 
4176             return exit;
4177         }
4178 
4179         @Override
4180         public CodeType resultType() {
4181             return VOID;
4182         }
4183     }
4184 
4185     /**
4186      * The enhanced for operation, that can model a Java language enhanced for statement.
4187      * <p>
4188      * Enhanced-for operations feature three bodies. The <em>expression body</em> models the expression to be
4189      * iterated. The <em>definition body</em> models the definition of the loop variable. The <em>loop body</em>
4190      * models the statements to execute.
4191      * <p>
4192      * The expression body accepts no arguments and yields a value of type {@code I}, corresponding to the type of the
4193      * expression to be iterated. The definition body accepts one argument of type {@code E}, corresponding to an element
4194      * type derived from {@code I}, and yields a value of type {@code V}, the type of the loop variable. Finally, the loop
4195      * body accepts that value and yields {@linkplain JavaType#VOID no value}.
4196      * <p>
4197      * The result type of an enhanced-for operation is {@link JavaType#VOID}.
4198      *
4199      * @jls 14.14.2 The enhanced for statement
4200      */
4201     @OpDeclaration(EnhancedForOp.NAME)
4202     public static final class EnhancedForOp extends JavaOp
4203             implements Op.Loop, Op.Lowerable, JavaStatement {
4204 
4205         /**
4206          * Builder for the expression body of an enhanced-for operation.
4207          */
4208         public static final class ExpressionBuilder {
4209             final Body.Builder connectedAncestorBody;
4210             final CodeType iterableType;
4211             final CodeType elementType;
4212 
4213             ExpressionBuilder(Body.Builder connectedAncestorBody,
4214                               CodeType iterableType, CodeType elementType) {
4215                 this.connectedAncestorBody = connectedAncestorBody;
4216                 this.iterableType = iterableType;
4217                 this.elementType = elementType;
4218             }
4219 
4220             /**
4221              * Builds the expression body of an enhanced-for operation.
4222              *
4223              * @param c a consumer that populates the expression body
4224              * @return a builder for specifying the definition body
4225              */
4226             public DefinitionBuilder expression(Consumer<Block.Builder> c) {
4227                 Body.Builder expression = Body.Builder.of(connectedAncestorBody,
4228                         CoreType.functionType(iterableType));
4229                 c.accept(expression.entryBlock());
4230 
4231                 return new DefinitionBuilder(connectedAncestorBody, elementType, expression);
4232             }
4233         }
4234 
4235         /**
4236          * Builder for the definition body of an enhanced-for operation.
4237          */
4238         public static final class DefinitionBuilder {
4239             final Body.Builder connectedAncestorBody;
4240             final CodeType elementType;
4241             final Body.Builder expression;
4242 
4243             DefinitionBuilder(Body.Builder connectedAncestorBody,
4244                               CodeType elementType, Body.Builder expression) {
4245                 this.connectedAncestorBody = connectedAncestorBody;
4246                 this.elementType = elementType;
4247                 this.expression = expression;
4248             }
4249 
4250             /**
4251              * Builds the definition body of an enhanced-for operation, using a type derived from the type
4252              * of the loop expression.
4253              *
4254              * @param c a consumer that populates the definition body
4255              * @return a builder for specifying the loop body
4256              */
4257             public BodyBuilder definition(Consumer<Block.Builder> c) {
4258                 return definition(elementType, c);
4259             }
4260 
4261             /**
4262              * Builds the definition body of an enhanced-for operation with the provided type.
4263              *
4264              * @param bodyElementType the type to provide to the loop body
4265              * @param c a consumer that populates the definition body
4266              * @return a builder for specifying the loop body
4267              */
4268             public BodyBuilder definition(CodeType bodyElementType, Consumer<Block.Builder> c) {
4269                 Body.Builder definition = Body.Builder.of(connectedAncestorBody,
4270                         CoreType.functionType(bodyElementType, elementType));
4271                 c.accept(definition.entryBlock());
4272 
4273                 return new BodyBuilder(connectedAncestorBody, elementType, expression, definition);
4274             }
4275         }
4276 
4277         /**
4278          * Builder for the loop body of an enhanced-for operation.
4279          */
4280         public static final class BodyBuilder {
4281             final Body.Builder connectedAncestorBody;
4282             final CodeType elementType;
4283             final Body.Builder expression;
4284             final Body.Builder definition;
4285 
4286             BodyBuilder(Body.Builder connectedAncestorBody,
4287                         CodeType elementType, Body.Builder expression, Body.Builder definition) {
4288                 this.connectedAncestorBody = connectedAncestorBody;
4289                 this.elementType = elementType;
4290                 this.expression = expression;
4291                 this.definition = definition;
4292             }
4293 
4294             /**
4295              * Completes the enhanced-for operation by adding the loop body.
4296              *
4297              * @param c a consumer that populates the loop body
4298              * @return the completed enhanced-for operation
4299              */
4300             public EnhancedForOp body(Consumer<Block.Builder> c) {
4301                 Body.Builder body = Body.Builder.of(connectedAncestorBody,
4302                         CoreType.functionType(VOID, elementType));
4303                 c.accept(body.entryBlock());
4304 
4305                 return new EnhancedForOp(expression, definition, body);
4306             }
4307         }
4308 
4309         static final String NAME = "java.enhancedFor";
4310 
4311         final Body exprBody;
4312         final Body initBody;
4313         final Body loopBody;
4314 
4315         EnhancedForOp(ExternalizedOp def) {
4316             this(def.bodyDefinitions().get(0),
4317                     def.bodyDefinitions().get(1),
4318                     def.bodyDefinitions().get(2));
4319         }
4320 
4321         EnhancedForOp(EnhancedForOp that, CodeContext cc, CodeTransformer ct) {
4322             super(that, cc);
4323 
4324             this.exprBody = that.exprBody.transform(cc, ct).build(this);
4325             this.initBody = that.initBody.transform(cc, ct).build(this);
4326             this.loopBody = that.loopBody.transform(cc, ct).build(this);
4327         }
4328 
4329         @Override
4330         public EnhancedForOp transform(CodeContext cc, CodeTransformer ct) {
4331             return new EnhancedForOp(this, cc, ct);
4332         }
4333 
4334         EnhancedForOp(Body.Builder expressionC, Body.Builder initC, Body.Builder bodyC) {
4335             super(List.of());
4336 
4337             this.exprBody = expressionC.build(this);
4338             if (exprBody.bodySignature().returnType().equals(VOID)) {
4339                 throw new IllegalArgumentException("Expression should return non-void value: " + exprBody.bodySignature());
4340             }
4341             if (!exprBody.bodySignature().parameterTypes().isEmpty()) {
4342                 throw new IllegalArgumentException("Expression should have zero parameters: " + exprBody.bodySignature());
4343             }
4344 
4345             this.initBody = initC.build(this);
4346             if (initBody.bodySignature().returnType().equals(VOID)) {
4347                 throw new IllegalArgumentException("Initialization should return non-void value: " + initBody.bodySignature());
4348             }
4349             if (initBody.bodySignature().parameterTypes().size() != 1) {
4350                 throw new IllegalArgumentException("Initialization should have one parameter: " + initBody.bodySignature());
4351             }
4352 
4353             this.loopBody = bodyC.build(this);
4354             if (!loopBody.bodySignature().returnType().equals(VOID)) {
4355                 throw new IllegalArgumentException("Body should return void: " + loopBody.bodySignature());
4356             }
4357             if (loopBody.bodySignature().parameterTypes().size() != 1) {
4358                 throw new IllegalArgumentException("Body should have one parameter: " + loopBody.bodySignature());
4359             }
4360         }
4361 
4362         @Override
4363         public List<Body> bodies() {
4364             return List.of(exprBody, initBody, loopBody);
4365         }
4366 
4367         /**
4368          * {@return the expression body}
4369          */
4370         public Body exprBody() {
4371             return exprBody;
4372         }
4373 
4374         /**
4375          * {@return the initialization body}
4376          */
4377         public Body initBody() {
4378             return initBody;
4379         }
4380 
4381         @Override
4382         public Body loopBody() {
4383             return loopBody;
4384         }
4385 
4386         static final MethodRef ITERABLE_ITERATOR = MethodRef.method(Iterable.class, "iterator", Iterator.class);
4387         static final MethodRef ITERATOR_HAS_NEXT = MethodRef.method(Iterator.class, "hasNext", boolean.class);
4388         static final MethodRef ITERATOR_NEXT = MethodRef.method(Iterator.class, "next", Object.class);
4389 
4390         @Override
4391         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
4392             JavaType elementType = (JavaType) initBody.entryBlock().parameters().get(0).type();
4393             boolean isArray = exprBody.bodySignature().returnType() instanceof ArrayType;
4394 
4395             Block.Builder preHeader = b.block(exprBody.bodySignature().returnType());
4396             Block.Builder header = b.block(isArray ? List.of(INT) : List.of());
4397             Block.Builder init = b.block();
4398             Block.Builder body = b.block();
4399             Block.Builder exit = b.block();
4400 
4401             b.transformBody(exprBody, List.of(), loweringTransformer(inherited, (block, op) -> {
4402                 if (op instanceof CoreOp.YieldOp yop) {
4403                     Value loopSource = block.context().getValue(yop.yieldValue());
4404                     block.add(branch(preHeader.reference(loopSource)));
4405                     return block;
4406                 } else {
4407                     return null;
4408                 }
4409             }));
4410 
4411             if (isArray) {
4412                 Value array = preHeader.parameters().get(0);
4413                 Value arrayLength = preHeader.add(arrayLength(array));
4414                 Value i = preHeader.add(constant(INT, 0));
4415                 preHeader.add(branch(header.reference(i)));
4416 
4417                 i = header.parameters().get(0);
4418                 Value p = header.add(lt(i, arrayLength));
4419                 header.add(conditionalBranch(p, init.reference(), exit.reference()));
4420 
4421                 Value e = init.add(arrayLoadOp(array, i));
4422                 List<Value> initValues = new ArrayList<>();
4423                 init.transformBody(this.initBody, List.of(e), loweringTransformer(inherited, (block, op) -> {
4424                     if (op instanceof CoreOp.YieldOp yop) {
4425                         initValues.addAll(block.context().getValues(yop.operands()));
4426                         block.add(branch(body.reference()));
4427                         return block;
4428                     } else {
4429                         return null;
4430                     }
4431                 }));
4432 
4433                 Block.Builder update = b.block();
4434                 BranchTarget.setBranchTarget(b.context(), this, exit, update);
4435 
4436                 body.transformBody(this.loopBody, initValues, loweringTransformer(inherited, (_, _) -> null));
4437 
4438                 i = update.add(add(i, update.add(constant(INT, 1))));
4439                 update.add(branch(header.reference(i)));
4440             } else {
4441                 JavaType iterable = parameterized(type(Iterator.class), elementType);
4442                 Value iterator = preHeader.add(invoke(iterable, ITERABLE_ITERATOR, preHeader.parameters().get(0)));
4443                 preHeader.add(branch(header.reference()));
4444 
4445                 Value p = header.add(invoke(ITERATOR_HAS_NEXT, iterator));
4446                 header.add(conditionalBranch(p, init.reference(), exit.reference()));
4447 
4448                 Value e = init.add(invoke(elementType, ITERATOR_NEXT, iterator));
4449                 List<Value> initValues = new ArrayList<>();
4450                 init.transformBody(this.initBody, List.of(e), loweringTransformer(inherited, (block, op) -> {
4451                     if (op instanceof CoreOp.YieldOp yop) {
4452                         initValues.addAll(block.context().getValues(yop.operands()));
4453                         block.add(branch(body.reference()));
4454                         return block;
4455                     } else {
4456                         return null;
4457                     }
4458                 }));
4459 
4460                 BranchTarget.setBranchTarget(b.context(), this, exit, header);
4461 
4462                 body.transformBody(this.loopBody, initValues, loweringTransformer(inherited, (_, _) -> null));
4463             }
4464 
4465             return exit;
4466         }
4467 
4468         @Override
4469         public CodeType resultType() {
4470             return VOID;
4471         }
4472     }
4473 
4474     /**
4475      * The while operation, that can model a Java language while statement.
4476      * <p>
4477      * While operations feature two bodies. The <em>predicate body</em> models the loop condition.
4478      * The <em>loop body</em> models the statements to execute.
4479      * <p>
4480      * The predicate body should accept no arguments and yield a {@link JavaType#BOOLEAN} value.
4481      * The loop body should accept no arguments, and yield {@linkplain JavaType#VOID no value}.
4482      * <p>
4483      * The result type of a while operation is {@link JavaType#VOID}.
4484      *
4485      * @jls 14.12 The while Statement
4486      */
4487     @OpDeclaration(WhileOp.NAME)
4488     public static final class WhileOp extends JavaOp
4489             implements Op.Loop, Op.Lowerable, JavaStatement {
4490 
4491         /**
4492          * Builder for the predicate body of a while operation.
4493          */
4494         public static class PredicateBuilder {
4495             final Body.Builder connectedAncestorBody;
4496 
4497             PredicateBuilder(Body.Builder connectedAncestorBody) {
4498                 this.connectedAncestorBody = connectedAncestorBody;
4499             }
4500 
4501             /**
4502              * Builds the predicate body of a while operation.
4503              *
4504              * @param c a consumer that populates the predicate body
4505              * @return a builder for specifying the loop body
4506              */
4507             public WhileOp.BodyBuilder predicate(Consumer<Block.Builder> c) {
4508                 Body.Builder body = Body.Builder.of(connectedAncestorBody, CoreType.functionType(BOOLEAN));
4509                 c.accept(body.entryBlock());
4510 
4511                 return new WhileOp.BodyBuilder(connectedAncestorBody, body);
4512             }
4513         }
4514 
4515         /**
4516          * Builder for the loop body of a while operation.
4517          */
4518         public static class BodyBuilder {
4519             final Body.Builder connectedAncestorBody;
4520             private final Body.Builder predicate;
4521 
4522             BodyBuilder(Body.Builder connectedAncestorBody, Body.Builder predicate) {
4523                 this.connectedAncestorBody = connectedAncestorBody;
4524                 this.predicate = predicate;
4525             }
4526 
4527             /**
4528              * Completes the while operation by adding the loop body.
4529              *
4530              * @param c a consumer that populates the loop body
4531              * @return the completed while operation
4532              */
4533             public WhileOp body(Consumer<Block.Builder> c) {
4534                 Body.Builder body = Body.Builder.of(connectedAncestorBody, CoreType.FUNCTION_TYPE_VOID);
4535                 c.accept(body.entryBlock());
4536 
4537                 return new WhileOp(List.of(predicate, body));
4538             }
4539         }
4540 
4541         private static final String NAME = "java.while";
4542 
4543         private final List<Body> bodies;
4544 
4545         WhileOp(ExternalizedOp def) {
4546             this(def.bodyDefinitions());
4547         }
4548 
4549         WhileOp(List<Body.Builder> bodyCs) {
4550             super(List.of());
4551 
4552             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
4553         }
4554 
4555         WhileOp(Body.Builder predicate, Body.Builder body) {
4556             super(List.of());
4557 
4558             Objects.requireNonNull(body);
4559 
4560             this.bodies = Stream.of(predicate, body).filter(Objects::nonNull)
4561                     .map(bc -> bc.build(this)).toList();
4562 
4563             // @@@ This will change with pattern bindings
4564             if (!bodies.get(0).bodySignature().equals(CoreType.functionType(BOOLEAN))) {
4565                 throw new IllegalArgumentException(
4566                         "Predicate body signature should be " + CoreType.functionType(BOOLEAN) +
4567                                 " but is " + bodies.get(0).bodySignature());
4568             }
4569             if (!bodies.get(1).bodySignature().equals(CoreType.FUNCTION_TYPE_VOID)) {
4570                 throw new IllegalArgumentException(
4571                         "Body type should be " + CoreType.functionType(VOID) +
4572                                 " but is " + bodies.get(1).bodySignature());
4573             }
4574         }
4575 
4576         WhileOp(WhileOp that, CodeContext cc, CodeTransformer ct) {
4577             super(that, cc);
4578 
4579             this.bodies = that.bodies.stream()
4580                     .map(b -> b.transform(cc, ct).build(this)).toList();
4581         }
4582 
4583         @Override
4584         public WhileOp transform(CodeContext cc, CodeTransformer ct) {
4585             return new WhileOp(this, cc, ct);
4586         }
4587 
4588         @Override
4589         public List<Body> bodies() {
4590             return bodies;
4591         }
4592 
4593         /**
4594          * {@return the loop condition body}
4595          */
4596         public Body predicateBody() {
4597             return bodies.get(0);
4598         }
4599 
4600         @Override
4601         public Body loopBody() {
4602             return bodies.get(1);
4603         }
4604 
4605         @Override
4606         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
4607             Block.Builder header = b.block();
4608             Block.Builder body = b.block();
4609             Block.Builder exit = b.block();
4610 
4611             b.add(branch(header.reference()));
4612 
4613             header.transformBody(predicateBody(), List.of(), loweringTransformer(inherited, (block, op) -> {
4614                 if (op instanceof CoreOp.YieldOp yo) {
4615                     block.add(conditionalBranch(block.context().getValue(yo.yieldValue()),
4616                             body.reference(), exit.reference()));
4617                     return block;
4618                 } else {
4619                     return null;
4620                 }
4621             }));
4622 
4623             BranchTarget.setBranchTarget(b.context(), this, exit, header);
4624 
4625             body.transformBody(loopBody(), List.of(), loweringTransformer(inherited, (_, _) -> null));
4626 
4627             return exit;
4628         }
4629 
4630         @Override
4631         public CodeType resultType() {
4632             return VOID;
4633         }
4634     }
4635 
4636     /**
4637      * The do-while operation, that can model a Java language do statement.
4638      * <p>
4639      * Do-while operations feature two bodies. The <em>loop body</em> models the statements to execute.
4640      * The <em>predicate body</em> models the loop condition.
4641      * <p>
4642      * The loop body should accept no arguments, and yield {@linkplain JavaType#VOID no value}. The predicate body
4643      * should accept no arguments, and yield a {@link JavaType#BOOLEAN} value.
4644      * <p>
4645      * The result type of a do-while operation is {@link JavaType#VOID}.
4646      *
4647      * @jls 14.13 The do Statement
4648      */
4649     // @@@ Unify JavaDoWhileOp and JavaWhileOp with common abstract superclass
4650     @OpDeclaration(DoWhileOp.NAME)
4651     public static final class DoWhileOp extends JavaOp
4652             implements Op.Loop, Op.Lowerable, JavaStatement {
4653 
4654         /**
4655          * Builder for the predicate body of a do-while operation.
4656          */
4657         public static class PredicateBuilder {
4658             final Body.Builder connectedAncestorBody;
4659             private final Body.Builder body;
4660 
4661             PredicateBuilder(Body.Builder connectedAncestorBody, Body.Builder body) {
4662                 this.connectedAncestorBody = connectedAncestorBody;
4663                 this.body = body;
4664             }
4665 
4666             /**
4667              * Completes the do-while operation by adding the predicate body.
4668              *
4669              * @param c a consumer that populates the predicate body
4670              * @return the completed do-while operation
4671              */
4672             public DoWhileOp predicate(Consumer<Block.Builder> c) {
4673                 Body.Builder predicate = Body.Builder.of(connectedAncestorBody, CoreType.functionType(BOOLEAN));
4674                 c.accept(predicate.entryBlock());
4675 
4676                 return new DoWhileOp(List.of(body, predicate));
4677             }
4678         }
4679 
4680         /**
4681          * Builder for the loop body of a do-while operation.
4682          */
4683         public static class BodyBuilder {
4684             final Body.Builder connectedAncestorBody;
4685 
4686             BodyBuilder(Body.Builder connectedAncestorBody) {
4687                 this.connectedAncestorBody = connectedAncestorBody;
4688             }
4689 
4690             /**
4691              * Builds the loop body of a do-while operation.
4692              *
4693              * @param c a consumer that populates the loop body
4694              * @return a builder for specifying the predicate body
4695              */
4696             public DoWhileOp.PredicateBuilder body(Consumer<Block.Builder> c) {
4697                 Body.Builder body = Body.Builder.of(connectedAncestorBody, CoreType.FUNCTION_TYPE_VOID);
4698                 c.accept(body.entryBlock());
4699 
4700                 return new DoWhileOp.PredicateBuilder(connectedAncestorBody, body);
4701             }
4702         }
4703 
4704         private static final String NAME = "java.do.while";
4705 
4706         private final List<Body> bodies;
4707 
4708         DoWhileOp(ExternalizedOp def) {
4709             this(def.bodyDefinitions());
4710         }
4711 
4712         DoWhileOp(List<Body.Builder> bodyCs) {
4713             super(List.of());
4714 
4715             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
4716         }
4717 
4718         DoWhileOp(Body.Builder body, Body.Builder predicate) {
4719             super(List.of());
4720 
4721             Objects.requireNonNull(body);
4722 
4723             this.bodies = Stream.of(body, predicate).filter(Objects::nonNull)
4724                     .map(bc -> bc.build(this)).toList();
4725 
4726             if (!bodies.get(0).bodySignature().equals(CoreType.FUNCTION_TYPE_VOID)) {
4727                 throw new IllegalArgumentException(
4728                         "Body type should be " + CoreType.functionType(VOID) +
4729                                 " but is " + bodies.get(1).bodySignature());
4730             }
4731             if (!bodies.get(1).bodySignature().equals(CoreType.functionType(BOOLEAN))) {
4732                 throw new IllegalArgumentException(
4733                         "Predicate body signature should be " + CoreType.functionType(BOOLEAN) +
4734                                 " but is " + bodies.get(0).bodySignature());
4735             }
4736         }
4737 
4738         DoWhileOp(DoWhileOp that, CodeContext cc, CodeTransformer ct) {
4739             super(that, cc);
4740 
4741             this.bodies = that.bodies.stream()
4742                     .map(b -> b.transform(cc, ct).build(this)).toList();
4743         }
4744 
4745         @Override
4746         public DoWhileOp transform(CodeContext cc, CodeTransformer ct) {
4747             return new DoWhileOp(this, cc, ct);
4748         }
4749 
4750         @Override
4751         public List<Body> bodies() {
4752             return bodies;
4753         }
4754 
4755         /**
4756          * {@return the predicate body for the do-while operation}
4757          */
4758         public Body predicateBody() {
4759             return bodies.get(1);
4760         }
4761 
4762         @Override
4763         public Body loopBody() {
4764             return bodies.get(0);
4765         }
4766 
4767         @Override
4768         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
4769             Block.Builder body = b.block();
4770             Block.Builder header = b.block();
4771             Block.Builder exit = b.block();
4772 
4773             b.add(branch(body.reference()));
4774 
4775             BranchTarget.setBranchTarget(b.context(), this, exit, header);
4776 
4777             body.transformBody(loopBody(), List.of(), loweringTransformer(inherited, (_, _) -> null));
4778 
4779             header.transformBody(predicateBody(), List.of(), loweringTransformer(inherited, (block, op) -> {
4780                 if (op instanceof CoreOp.YieldOp yo) {
4781                     block.add(conditionalBranch(block.context().getValue(yo.yieldValue()),
4782                             body.reference(), exit.reference()));
4783                     return block;
4784                 } else {
4785                     return null;
4786                 }
4787             }));
4788 
4789             return exit;
4790         }
4791 
4792         @Override
4793         public CodeType resultType() {
4794             return VOID;
4795         }
4796     }
4797 
4798     /**
4799      * The conditional operation, that can model Java language conditional-and and conditional-or expressions.
4800      * <p>
4801      * Conditional operations feature two or more predicate bodies, each yielding a {@link JavaType#BOOLEAN} value.
4802      *
4803      * @jls 15.23 Conditional-And Operator {@code &&}
4804      * @jls 15.24 Conditional-Or Operator {@code ||}
4805      */
4806     public sealed static abstract class JavaConditionalOp extends JavaOp
4807             implements Op.Nested, Op.Lowerable, JavaExpression {
4808         final List<Body> bodies;
4809 
4810         JavaConditionalOp(JavaConditionalOp that, CodeContext cc, CodeTransformer ct) {
4811             super(that, cc);
4812 
4813             // Copy body
4814             this.bodies = that.bodies.stream().map(b -> b.transform(cc, ct).build(this)).toList();
4815         }
4816 
4817         JavaConditionalOp(List<Body.Builder> bodyCs) {
4818             super(List.of());
4819 
4820             if (bodyCs.isEmpty()) {
4821                 throw new IllegalArgumentException();
4822             }
4823 
4824             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
4825             for (Body b : bodies) {
4826                 if (!b.bodySignature().equals(CoreType.functionType(BOOLEAN))) {
4827                     throw new IllegalArgumentException("Body conditional body signature: " + b.bodySignature());
4828                 }
4829             }
4830         }
4831 
4832         @Override
4833         public List<Body> bodies() {
4834             return bodies;
4835         }
4836 
4837         static Block.Builder lower(Block.Builder startBlock, BiFunction<Block.Builder, Op, Block.Builder> before, JavaConditionalOp cop) {
4838             List<Body> bodies = cop.bodies();
4839 
4840             Block.Builder exit = startBlock.block();
4841             CodeType oprType = cop.result().type();
4842             Block.Parameter arg = exit.parameter(oprType);
4843             startBlock.context().mapValue(cop.result(), arg);
4844 
4845             // Transform bodies in reverse order
4846             // This makes available the blocks to be referenced as successors in prior blocks
4847 
4848             Block.Builder pred = null;
4849             for (int i = bodies.size() - 1; i >= 0; i--) {
4850                 CodeTransformer bodyTransformer;
4851                 BiFunction<Block.Builder, Op, Block.Builder> lowering;
4852                 if (i == bodies.size() - 1) {
4853                     bodyTransformer = loweringTransformer(before, (block, op) -> {
4854                         if (op instanceof CoreOp.YieldOp yop) {
4855                             Value p = block.context().getValue(yop.yieldValue());
4856                             block.add(branch(exit.reference(p)));
4857                             return block;
4858                         } else {
4859                             return null;
4860                         }
4861                     });
4862                 } else {
4863                     Block.Builder nextPred = pred;
4864                     bodyTransformer = loweringTransformer(before, (block, op) -> {
4865                         if (op instanceof CoreOp.YieldOp yop) {
4866                             Value p = block.context().getValue(yop.yieldValue());
4867                             if (cop instanceof ConditionalAndOp) {
4868                                 block.add(conditionalBranch(p, nextPred.reference(), exit.reference(p)));
4869                             } else {
4870                                 block.add(conditionalBranch(p, exit.reference(p), nextPred.reference()));
4871                             }
4872                             return block;
4873                         } else {
4874                             return null;
4875                         }
4876                     });
4877                 }
4878 
4879                 Body fromPred = bodies.get(i);
4880                 if (i == 0) {
4881                     startBlock.transformBody(fromPred, List.of(), bodyTransformer);
4882                 } else {
4883                     pred = startBlock.block(fromPred.bodySignature().parameterTypes());
4884                     pred.transformBody(fromPred, pred.parameters(), bodyTransformer);
4885                 }
4886             }
4887 
4888             return exit;
4889         }
4890 
4891         @Override
4892         public CodeType resultType() {
4893             return BOOLEAN;
4894         }
4895     }
4896 
4897     /**
4898      * The conditional-and operation, that can model Java language conditional-and expressions.
4899      *
4900      * @jls 15.23 Conditional-And Operator {@code &&}
4901      */
4902     @OpDeclaration(ConditionalAndOp.NAME)
4903     public static final class ConditionalAndOp extends JavaConditionalOp {
4904 
4905         /**
4906          * Builder for conditional-and operations.
4907          */
4908         public static class Builder {
4909             final Body.Builder connectedAncestorBody;
4910             final List<Body.Builder> bodies;
4911 
4912             Builder(Body.Builder connectedAncestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
4913                 this.connectedAncestorBody = connectedAncestorBody;
4914                 this.bodies = new ArrayList<>();
4915                 and(lhs);
4916                 and(rhs);
4917             }
4918 
4919             /**
4920              * Adds a predicate body to this conditional-and operation.
4921              *
4922              * @param c a consumer that populates the predicate body
4923              * @return this builder
4924              */
4925             public Builder and(Consumer<Block.Builder> c) {
4926                 Body.Builder body = Body.Builder.of(connectedAncestorBody, CoreType.functionType(BOOLEAN));
4927                 c.accept(body.entryBlock());
4928                 bodies.add(body);
4929 
4930                 return this;
4931             }
4932 
4933             /**
4934              * {@return the completed conditional-and operation}
4935              */
4936             public ConditionalAndOp build() {
4937                 return new ConditionalAndOp(bodies);
4938             }
4939         }
4940 
4941         static final String NAME = "java.cand";
4942 
4943         ConditionalAndOp(ExternalizedOp def) {
4944             this(def.bodyDefinitions());
4945         }
4946 
4947         ConditionalAndOp(ConditionalAndOp that, CodeContext cc, CodeTransformer ct) {
4948             super(that, cc, ct);
4949         }
4950 
4951         @Override
4952         public ConditionalAndOp transform(CodeContext cc, CodeTransformer ct) {
4953             return new ConditionalAndOp(this, cc, ct);
4954         }
4955 
4956         ConditionalAndOp(List<Body.Builder> bodyCs) {
4957             super(bodyCs);
4958         }
4959 
4960         @Override
4961         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
4962             return lower(b, inherited, this);
4963         }
4964     }
4965 
4966     /**
4967      * The conditional-or operation, that can model Java language conditional-or expressions.
4968      *
4969      * @jls 15.24 Conditional-Or Operator {@code ||}
4970      */
4971     @OpDeclaration(ConditionalOrOp.NAME)
4972     public static final class ConditionalOrOp extends JavaConditionalOp {
4973 
4974         /**
4975          * Builder for conditional-or operations.
4976          */
4977         public static class Builder {
4978             final Body.Builder connectedAncestorBody;
4979             final List<Body.Builder> bodies;
4980 
4981             Builder(Body.Builder connectedAncestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
4982                 this.connectedAncestorBody = connectedAncestorBody;
4983                 this.bodies = new ArrayList<>();
4984                 or(lhs);
4985                 or(rhs);
4986             }
4987 
4988             /**
4989              * Adds a predicate body to this conditional-or operation.
4990              *
4991              * @param c a consumer that populates the predicate body
4992              * @return this builder
4993              */
4994             public Builder or(Consumer<Block.Builder> c) {
4995                 Body.Builder body = Body.Builder.of(connectedAncestorBody, CoreType.functionType(BOOLEAN));
4996                 c.accept(body.entryBlock());
4997                 bodies.add(body);
4998 
4999                 return this;
5000             }
5001 
5002             /**
5003              * {@return the completed conditional-or operation}
5004              */
5005             public ConditionalOrOp build() {
5006                 return new ConditionalOrOp(bodies);
5007             }
5008         }
5009 
5010         static final String NAME = "java.cor";
5011 
5012         ConditionalOrOp(ExternalizedOp def) {
5013             this(def.bodyDefinitions());
5014         }
5015 
5016         ConditionalOrOp(ConditionalOrOp that, CodeContext cc, CodeTransformer ct) {
5017             super(that, cc, ct);
5018         }
5019 
5020         @Override
5021         public ConditionalOrOp transform(CodeContext cc, CodeTransformer ct) {
5022             return new ConditionalOrOp(this, cc, ct);
5023         }
5024 
5025         ConditionalOrOp(List<Body.Builder> bodyCs) {
5026             super(bodyCs);
5027         }
5028 
5029         @Override
5030         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
5031             return lower(b, inherited, this);
5032         }
5033     }
5034 
5035     /**
5036      * The conditional operation, that can model Java language conditional operator {@code ?} expressions.
5037      * <p>
5038      * Conditional expression operations feature three bodies: the predicate body, the true body, and the false body.
5039      * <p>
5040      * The predicate body accepts no arguments and yields a {@link JavaType#BOOLEAN} value.
5041      * The true and false bodies accepts no arguments and yield a value.
5042      *
5043      * @jls 15.25 Conditional Operator {@code ? :}
5044      */
5045     @OpDeclaration(ConditionalExpressionOp.NAME)
5046     public static final class ConditionalExpressionOp extends JavaOp
5047             implements Op.Nested, Op.Lowerable, JavaExpression {
5048 
5049         static final String NAME = "java.cexpression";
5050 
5051         final CodeType resultType;
5052         // {cond, truepart, falsepart}
5053         final List<Body> bodies;
5054 
5055         ConditionalExpressionOp(ExternalizedOp def) {
5056             if (!def.operands().isEmpty()) {
5057                 throw new IllegalStateException("Operation must have no operands");
5058             }
5059 
5060             this(def.resultType(), def.bodyDefinitions());
5061         }
5062 
5063         ConditionalExpressionOp(ConditionalExpressionOp that, CodeContext cc, CodeTransformer ct) {
5064             super(that, cc);
5065 
5066             // Copy body
5067             this.bodies = that.bodies.stream()
5068                     .map(b -> b.transform(cc, ct).build(this)).toList();
5069             this.resultType = that.resultType;
5070         }
5071 
5072         @Override
5073         public ConditionalExpressionOp transform(CodeContext cc, CodeTransformer ct) {
5074             return new ConditionalExpressionOp(this, cc, ct);
5075         }
5076 
5077         ConditionalExpressionOp(CodeType expressionType, List<Body.Builder> bodyCs) {
5078             super(List.of());
5079 
5080             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
5081             // @@@ when expressionType is null, we assume truepart and falsepart have the same yieldType
5082             this.resultType = expressionType == null ? bodies.get(1).yieldType() : expressionType;
5083 
5084             if (bodies.size() < 3) {
5085                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
5086             }
5087 
5088             Body cond = bodies.get(0);
5089             if (!cond.bodySignature().equals(CoreType.functionType(BOOLEAN))) {
5090                 throw new IllegalArgumentException("Illegal cond body signature: " + cond.bodySignature());
5091             }
5092         }
5093 
5094         @Override
5095         public List<Body> bodies() {
5096             return bodies;
5097         }
5098 
5099         /**
5100          * {@return the predicate body}
5101          */
5102         public Body predicateBody() {
5103             return bodies.get(0);
5104         }
5105 
5106         /**
5107          * {@return the true body}
5108          */
5109         public Body trueBody() {
5110             return bodies.get(1);
5111         }
5112 
5113         /**
5114          * {@return the false body}
5115          */
5116         public Body falseBody() {
5117             return bodies.get(2);
5118         }
5119 
5120         @Override
5121         public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
5122             Block.Builder exit = b.block(resultType());
5123             exit.context().mapValue(result(), exit.parameters().get(0));
5124 
5125             BranchTarget.setBranchTarget(b.context(), this, exit, null);
5126 
5127             List<Block.Builder> builders = List.of(b.block(), b.block());
5128             b.transformBody(bodies.get(0), List.of(), loweringTransformer(inherited, (block, op) -> {
5129                 if (op instanceof CoreOp.YieldOp yo) {
5130                     block.add(conditionalBranch(block.context().getValue(yo.yieldValue()),
5131                             builders.get(0).reference(), builders.get(1).reference()));
5132                     return block;
5133                 } else {
5134                     return null;
5135                 }
5136             }));
5137 
5138             for (int i = 0; i < 2; i++) {
5139                 builders.get(i).transformBody(bodies.get(i + 1), List.of(), loweringTransformer(inherited, (block, op) -> {
5140                     if (op instanceof CoreOp.YieldOp yop) {
5141                         block.add(branch(exit.reference(block.context().getValue(yop.yieldValue()))));
5142                         return block;
5143                     } else {
5144                         return null;
5145                     }
5146                 }));
5147             }
5148 
5149             return exit;
5150         }
5151 
5152         @Override
5153         public CodeType resultType() {
5154             return resultType;
5155         }
5156     }
5157 
5158     /**
5159      * The try operation, that can model Java language try statements.
5160      * <p>
5161      * Try operations feature a <em>try body</em>, zero or more <em>catch bodies</em>, and an optional
5162      * <em>finally body</em>. Try operations may also feature zero or more <em>resources bodies</em>, modeling a
5163      * try-with-resources statement.
5164      * <p>
5165      * Each resource body yields a value. The first resource body accepts no arguments. A second resource body accepts
5166      * an argument whose type is the same as the yield type of the first resource body. A subsequent resource accepts,
5167      * in order, arguments whose types are the same as all the prior resource body yield types.
5168      * <p>
5169      * The try body yields {@linkplain JavaType#VOID no value}. If one or more resources bodies are present then
5170      * the try body accepts, in order, arguments whose types are the same as the resource bodies yield types.
5171      * <p>
5172      * Each catch body should accept an exception value and yield {@linkplain JavaType#VOID no value}. The
5173      * finally body, if present, should accept no arguments and yield {@linkplain JavaType#VOID no value}.
5174      * <p>
5175      * The result type of a try operation is {@link JavaType#VOID}.
5176      *
5177      * @jls 14.20 The try statement
5178      * @jls 14.20.3 try-with-resources
5179      */
5180     @OpDeclaration(TryOp.NAME)
5181     public static final class TryOp extends JavaOp
5182             implements Op.Nested, Op.Lowerable, JavaStatement {
5183 
5184         /**
5185          * Builder for the resource bodies and the try body of a try operation.
5186          */
5187         public static final class BodyBuilder {
5188             final Body.Builder connectedAncestorBody;
5189             final List<Body.Builder> resources;
5190 
5191             BodyBuilder(Body.Builder connectedAncestorBody) {
5192                 this.connectedAncestorBody = connectedAncestorBody;
5193                 this.resources = new ArrayList<>();
5194             }
5195 
5196             /**
5197              * Adds a resource body to a try-with-resources operation.
5198              *
5199              * @param yieldType the resource type for a resource expression, or the Var type for a resource declaration
5200              * @param c a consumer that populates the resource body
5201              * @return this builder
5202              */
5203             public BodyBuilder resource(CodeType yieldType, Consumer<Block.Builder> c) {
5204                 List<CodeType> paramTypes = resources.stream().map(r -> r.bodySignature().returnType()).toList();
5205                 Body.Builder resource = Body.Builder.of(connectedAncestorBody,
5206                         CoreType.functionType(yieldType, paramTypes));
5207                 c.accept(resource.entryBlock());
5208                 resources.add(resource);
5209                 return this;
5210             }
5211 
5212             /**
5213              * Builds the try body of the try operation.
5214              *
5215              * @param c a consumer that populates the try body
5216              * @return a builder for specifying catch bodies and an optional finalizer
5217              */
5218             public CatchBuilder body(Consumer<Block.Builder> c) {
5219                 Body.Builder body = Body.Builder.of(connectedAncestorBody,
5220                         CoreType.functionType(VOID, resources.stream().map(bb -> bb.bodySignature().returnType()).toList()));
5221                 c.accept(body.entryBlock());
5222 
5223                 return new CatchBuilder(connectedAncestorBody, resources, body);
5224             }
5225         }
5226 
5227         /**
5228          * Builder for specifying catch bodies and an optional finalizer body of a try operation.
5229          */
5230         public static final class CatchBuilder {
5231             final Body.Builder connectedAncestorBody;
5232             final List<Body.Builder> resources;
5233             final Body.Builder body;
5234             final List<Body.Builder> catchers;
5235 
5236             CatchBuilder(Body.Builder connectedAncestorBody, List<Body.Builder> resources, Body.Builder body) {
5237                 this.connectedAncestorBody = connectedAncestorBody;
5238                 this.resources = resources;
5239                 this.body = body;
5240                 this.catchers = new ArrayList<>();
5241             }
5242 
5243             // @@@ multi-catch
5244             /**
5245              * Adds a catch body for handling exceptions of a specific type.
5246              *
5247              * @param exceptionType the type of exception handled
5248              * @param c a consumer that populates the catch body
5249              * @return this builder
5250              */
5251             public CatchBuilder catch_(CodeType exceptionType, Consumer<Block.Builder> c) {
5252                 Body.Builder _catch = Body.Builder.of(connectedAncestorBody,
5253                         CoreType.functionType(VOID, exceptionType));
5254                 c.accept(_catch.entryBlock());
5255                 catchers.add(_catch);
5256 
5257                 return this;
5258             }
5259 
5260             /**
5261              * Completes the try operation by adding the finalizer body.
5262              *
5263              * @param c a consumer that populates the finalizer body
5264              * @return the completed try operation
5265              */
5266             public TryOp finally_(Consumer<Block.Builder> c) {
5267                 Body.Builder _finally = Body.Builder.of(connectedAncestorBody, CoreType.FUNCTION_TYPE_VOID);
5268                 c.accept(_finally.entryBlock());
5269 
5270                 return new TryOp(resources, body, catchers, _finally);
5271             }
5272 
5273             /**
5274              * Completes the try operation without a finalizer body.
5275              *
5276              * @return the completed try operation
5277              */
5278             public TryOp noFinalizer() {
5279                 return new TryOp(resources, body, catchers, null);
5280             }
5281         }
5282 
5283         static final String NAME = "java.try";
5284         static final MethodRef AUTO_CLOSEABLE_CLOSE_METHOD = MethodRef.method(AutoCloseable.class, "close", void.class);
5285         static final MethodRef THROWABLE_ADD_SUPPRESSED_METHOD = MethodRef.method(Throwable.class, "addSuppressed", void.class, Throwable.class);
5286 
5287         final List<Body> resourcesBodies;
5288         final Body body;
5289         final List<Body> catchBodies;
5290         final Body finallyBody;
5291 
5292         TryOp(ExternalizedOp def) {
5293             List<Body.Builder> bodies = def.bodyDefinitions();
5294             int bodyIndex = 0;
5295             while (!bodies.get(bodyIndex).bodySignature().returnType().equals(VOID)) {
5296                 bodyIndex++;
5297             }
5298             List<Body.Builder> resources = bodies.subList(0, bodyIndex);
5299             Body.Builder body = bodies.get(bodyIndex);
5300             Body.Builder last = bodies.getLast();
5301             Body.Builder finalizer;
5302             if (last != body && last.bodySignature().parameterTypes().isEmpty()) {
5303                 finalizer = last;
5304             } else {
5305                 finalizer = null;
5306             }
5307             List<Body.Builder> catchers = bodies.subList(
5308                     bodyIndex + 1,
5309                     bodies.size() - (finalizer == null ? 0 : 1));
5310 
5311             this(resources, body, catchers, finalizer);
5312         }
5313 
5314         TryOp(TryOp that, CodeContext cc, CodeTransformer ct) {
5315             super(that, cc);
5316 
5317             this.resourcesBodies = that.resourcesBodies.stream()
5318                     .map(b -> b.transform(cc, ct).build(this))
5319                     .toList();
5320             this.body = that.body.transform(cc, ct).build(this);
5321             this.catchBodies = that.catchBodies.stream()
5322                     .map(b -> b.transform(cc, ct).build(this))
5323                     .toList();
5324             if (that.finallyBody != null) {
5325                 this.finallyBody = that.finallyBody.transform(cc, ct).build(this);
5326             } else {
5327                 this.finallyBody = null;
5328             }
5329         }
5330 
5331         @Override
5332         public TryOp transform(CodeContext cc, CodeTransformer ct) {
5333             return new TryOp(this, cc, ct);
5334         }
5335 
5336         TryOp(List<Body.Builder> resourcesC,
5337               Body.Builder bodyC,
5338               List<Body.Builder> catchersC,
5339               Body.Builder finalizerC) {
5340             super(List.of());
5341 
5342             this.resourcesBodies = resourcesC.stream().map(r -> r.build(this)).toList();
5343             List<CodeType> resourceTypes = new ArrayList<>();
5344             for (Body _resource : resourcesBodies) {
5345                 if (_resource.bodySignature().returnType().equals(VOID)) {
5346                     throw new IllegalArgumentException("Resource should not return void: " + _resource.bodySignature());
5347                 }
5348                 if (!_resource.bodySignature().parameterTypes().equals(resourceTypes)) {
5349                     throw new IllegalArgumentException(
5350                             "Resource should have parameters matching preceding resource yields: "
5351                                     + _resource.bodySignature());
5352                 }
5353                 resourceTypes.add(_resource.bodySignature().returnType());
5354             }
5355 
5356             this.body = bodyC.build(this);
5357             if (!body.bodySignature().returnType().equals(VOID)) {
5358                 throw new IllegalArgumentException("Try should return void: " + body.bodySignature());
5359             }
5360             if (!body.bodySignature().parameterTypes().equals(resourceTypes)) {
5361                 throw new IllegalArgumentException("Try should have parameters matching resource yields: "
5362                         + body.bodySignature());
5363             }
5364 
5365             this.catchBodies = catchersC.stream().map(c -> c.build(this)).toList();
5366             for (Body _catch : catchBodies) {
5367                 if (!_catch.bodySignature().returnType().equals(VOID)) {
5368                     throw new IllegalArgumentException("Catch should return void: " + _catch.bodySignature());
5369                 }
5370                 if (_catch.bodySignature().parameterTypes().size() != 1) {
5371                     throw new IllegalArgumentException("Catch should have zero parameters: " + _catch.bodySignature());
5372                 }
5373             }
5374 
5375             if (finalizerC != null) {
5376                 this.finallyBody = finalizerC.build(this);
5377                 if (!finallyBody.bodySignature().returnType().equals(VOID)) {
5378                     throw new IllegalArgumentException("Finally should return void: " + finallyBody.bodySignature());
5379                 }
5380                 if (!finallyBody.bodySignature().parameterTypes().isEmpty()) {
5381                     throw new IllegalArgumentException("Finally should have zero parameters: " + finallyBody.bodySignature());
5382                 }
5383             } else {
5384                 this.finallyBody = null;
5385             }
5386         }
5387 
5388         @Override
5389         public List<Body> bodies() {
5390             ArrayList<Body> bodies = new ArrayList<>();
5391             bodies.addAll(resourcesBodies);
5392             bodies.add(body);
5393             bodies.addAll(catchBodies);
5394             if (finallyBody != null) {
5395                 bodies.add(finallyBody);
5396             }
5397             return bodies;
5398         }
5399 
5400         /**
5401          * {@return the resources bodies}
5402          */
5403         public List<Body> resourceBodies() {
5404             return resourcesBodies;
5405         }
5406 
5407         /**
5408          * {@return the body of the try operation}
5409          */
5410         public Body body() {
5411             return body;
5412         }
5413 
5414         /**
5415          * {@return the catch bodies}
5416          */
5417         public List<Body> catchBodies() {
5418             return catchBodies;
5419         }
5420 
5421         /**
5422          * {@return the finally body, or {@code null} if this try operation has no finally body}
5423          */
5424         public Body finallyBody() {
5425             return finallyBody;
5426         }
5427 
5428         @Override
5429         public Block.Builder lower(Block.Builder b, final BiFunction<Block.Builder, Op, Block.Builder> inherited) {
5430             Block.Builder exit = b.block();
5431             BranchTarget.setBranchTarget(b.context(), this, exit, null);
5432 
5433             // Lowering is staged by repeated dispatching of the intermediate models through
5434             // the lower method: extended try-with-resources -> basic try-with-resources ->
5435             // try-catch-finally -> lower-level try form.
5436             // There is no recursion here, each time it is structurally different TryOp.
5437             if (!resourcesBodies.isEmpty()) {
5438                 b.transformBody(resourcesBodies.size() == 1
5439                         && catchBodies.isEmpty()
5440                         && finallyBody == null
5441                                 ? lowerBasicTryWithResources()
5442                                 : normalizeTryWithResources(),
5443                         b.context().getValues(capturedValues()),
5444                         loweringTransformer(inherited, (block, op) -> {
5445                     if (op instanceof CoreOp.YieldOp) {
5446                         block.add(branch(exit.reference()));
5447                         return block;
5448                     } else {
5449                         return null;
5450                     }
5451                 }));
5452                 return exit;
5453             }
5454 
5455             // Simple case with no catch and finally bodies
5456             if (catchBodies.isEmpty() && finallyBody == null) {
5457                 b.transformBody(body, List.of(), loweringTransformer(inherited, (block, op) -> {
5458                     if (op instanceof CoreOp.YieldOp) {
5459                         block.add(branch(exit.reference()));
5460                         return block;
5461                     } else {
5462                         return null;
5463                     }
5464                 }));
5465                 return exit;
5466             }
5467 
5468             Block.Builder tryRegionEnter = b.block();
5469             Block.Builder tryRegionExit = b.block();
5470 
5471             // Construct the catcher block builders
5472             List<Block.Builder> catchers = catchBodies().stream()
5473                     .map(catcher -> b.block())
5474                     .toList();
5475             Block.Builder catcherFinally;
5476             if (finallyBody == null) {
5477                 catcherFinally = null;
5478             } else {
5479                 catcherFinally = b.block();
5480                 catchers = new ArrayList<>(catchers);
5481                 catchers.add(catcherFinally);
5482             }
5483 
5484             // Enter the try exception region
5485             List<Block.Reference> exitHandlers = catchers.stream()
5486                     .map(Block.Builder::reference)
5487                     .toList();
5488             Op.Result enter = b.add(exceptionRegionEnter(tryRegionEnter.reference(), exitHandlers.reversed()));
5489 
5490             BiFunction<Block.Builder, Op, Block.Builder> tryExitTransformer;
5491             if (finallyBody != null) {
5492                 tryExitTransformer = composeFirst(inherited, (block, op) -> {
5493                     if (op instanceof CoreOp.ReturnOp ||
5494                             (op instanceof StatementTargetOp lop && ifExitFromTry(lop))) {
5495                         return inlineFinalizer(block, enter, inherited);
5496                     } else {
5497                         return block;
5498                     }
5499                 });
5500             } else {
5501                 tryExitTransformer = composeFirst(inherited, (block, op) -> {
5502                     if (op instanceof CoreOp.ReturnOp ||
5503                             (op instanceof StatementTargetOp lop && ifExitFromTry(lop))) {
5504                         Block.Builder tryRegionReturnExit = block.block();
5505                         block.add(exceptionRegionExit(enter, tryRegionReturnExit.reference()));
5506                         return tryRegionReturnExit;
5507                     } else {
5508                         return block;
5509                     }
5510                 });
5511             }
5512             // Inline the try body
5513             AtomicBoolean hasTryRegionExit = new AtomicBoolean();
5514             tryRegionEnter.transformBody(body, List.of(), loweringTransformer(tryExitTransformer, (block, op) -> {
5515                 if (op instanceof CoreOp.YieldOp) {
5516                     hasTryRegionExit.set(true);
5517                     block.add(branch(tryRegionExit.reference()));
5518                     return block;
5519                 } else {
5520                     return null;
5521                 }
5522             }));
5523 
5524             Block.Builder finallyEnter = null;
5525             if (finallyBody != null) {
5526                 finallyEnter = b.block();
5527                 if (hasTryRegionExit.get()) {
5528                     // Exit the try exception region
5529                     tryRegionExit.add(exceptionRegionExit(enter, finallyEnter.reference()));
5530                 }
5531             } else if (hasTryRegionExit.get()) {
5532                 // Exit the try exception region
5533                 tryRegionExit.add(exceptionRegionExit(enter, exit.reference()));
5534             }
5535 
5536             // Inline the catch bodies
5537             for (int i = 0; i < this.catchBodies.size(); i++) {
5538                 Block.Builder catcher = catchers.get(i);
5539                 Body catcherBody = this.catchBodies.get(i);
5540                 // Create the throwable argument
5541                 Block.Parameter t = catcher.parameter(catcherBody.bodySignature().parameterTypes().get(0));
5542 
5543                 if (finallyBody != null) {
5544                     Block.Builder catchRegionEnter = b.block();
5545                     Block.Builder catchRegionExit = b.block();
5546 
5547                     // Enter the catch exception region
5548                     Result catchExceptionRegion = catcher.add(
5549                             exceptionRegionEnter(catchRegionEnter.reference(), catcherFinally.reference()));
5550 
5551                     BiFunction<Block.Builder, Op, Block.Builder> catchExitTransformer = composeFirst(inherited, (block, op) -> {
5552                         if (op instanceof CoreOp.ReturnOp) {
5553                             return inlineFinalizer(block, catchExceptionRegion, inherited);
5554                         } else if (op instanceof StatementTargetOp lop && ifExitFromTry(lop)) {
5555                             return inlineFinalizer(block, catchExceptionRegion, inherited);
5556                         } else {
5557                             return block;
5558                         }
5559                     });
5560                     // Inline the catch body
5561                     AtomicBoolean hasCatchRegionExit = new AtomicBoolean();
5562                     catchRegionEnter.transformBody(catcherBody, List.of(t), loweringTransformer(catchExitTransformer, (block, op) -> {
5563                         if (op instanceof CoreOp.YieldOp) {
5564                             hasCatchRegionExit.set(true);
5565                             block.add(branch(catchRegionExit.reference()));
5566                             return block;
5567                         } else {
5568                             return null;
5569                         }
5570                     }));
5571 
5572                     // Exit the catch exception region
5573                     if (hasCatchRegionExit.get()) {
5574                         hasTryRegionExit.set(true);
5575                         catchRegionExit.add(exceptionRegionExit(catchExceptionRegion, finallyEnter.reference()));
5576                     }
5577                 } else {
5578                     // Inline the catch body
5579                     catcher.transformBody(catcherBody, List.of(t), loweringTransformer(inherited, (block, op) -> {
5580                         if (op instanceof CoreOp.YieldOp) {
5581                             block.add(branch(exit.reference()));
5582                             return block;
5583                         } else {
5584                             return null;
5585                         }
5586                     }));
5587                 }
5588             }
5589 
5590             if (finallyBody != null && hasTryRegionExit.get()) {
5591                 // Inline the finally body
5592                 finallyEnter.transformBody(finallyBody, List.of(), loweringTransformer(inherited, (block, op) -> {
5593                     if (op instanceof CoreOp.YieldOp) {
5594                         block.add(branch(exit.reference()));
5595                         return block;
5596                     } else {
5597                         return null;
5598                     }
5599                 }));
5600             }
5601 
5602             // Inline the finally body as a catcher of Throwable and adjusting to throw
5603             if (finallyBody != null) {
5604                 // Create the throwable argument
5605                 Block.Parameter t = catcherFinally.parameter(type(Throwable.class));
5606 
5607                 catcherFinally.transformBody(finallyBody, List.of(), loweringTransformer(inherited, (block, op) -> {
5608                     if (op instanceof CoreOp.YieldOp) {
5609                         block.add(throw_(t));
5610                         return block;
5611                     } else {
5612                         return null;
5613                     }
5614                 }));
5615             }
5616             return exit;
5617         }
5618 
5619         /// Normalize try-with-resources in two stages.
5620         ///
5621         /// First normalize an extended form to nested basic forms, one resource per
5622         /// level, left to right.
5623         ///
5624         /// Then lower each basic form to `try / catch / finally` logic.
5625         ///
5626         /// Stage boundaries use standalone synthetic bodies, so the next step always
5627         /// starts from a complete model.
5628         ///
5629         /// ```
5630         /// try (r1; r2; ...; rn) { body } catch (...) { catches } finally { finalizer }
5631         ///
5632         /// =>
5633         ///
5634         /// try (r1) {
5635         ///     try (r2) {
5636         ///         ...
5637         ///             try (rn) { body }
5638         ///         ...
5639         ///     }
5640         /// } catch (...) {
5641         ///     catches
5642         /// } finally {
5643         ///     finalizer
5644         /// }
5645         ///
5646         /// =>
5647         ///
5648         /// try {
5649         ///     r1 = acquire1()
5650         ///     primary1 = null
5651         ///     try {
5652         ///         r2 = acquire2()
5653         ///         primary2 = null
5654         ///         try {
5655         ///             ...
5656         ///                 rn = acquireN()
5657         ///                 primaryN = null
5658         ///                 try {
5659         ///                     body
5660         ///                 } catch (eN) {
5661         ///                     primaryN = eN
5662         ///                     throw eN
5663         ///                 } finally {
5664         ///                     if (primaryN != null) {
5665         ///                         try { resourceN.close(); }
5666         ///                         catch (closeExcN) { primaryN.addSuppressed(closeExcN); }
5667         ///                     } else {
5668         ///                         resourceN.close();
5669         ///                     }
5670         ///                 }
5671         ///             ...
5672         ///         } catch (e2) {
5673         ///             primary2 = e2
5674         ///             throw e2
5675         ///         } finally {
5676         ///             if (primary2 != null) {
5677         ///                 try { resource2.close(); }
5678         ///                 catch (closeExc2) { primary2.addSuppressed(closeExc2); }
5679         ///             } else {
5680         ///                 resource2.close();
5681         ///             }
5682         ///         }
5683         ///     } catch (e1) {
5684         ///         primary1 = e1
5685         ///         throw e1
5686         ///     } finally {
5687         ///         if (primary1 != null) {
5688         ///             try { resource1.close(); }
5689         ///             catch (closeExc1) { primary1.addSuppressed(closeExc1); }
5690         ///         } else {
5691         ///             resource1.close();
5692         ///         }
5693         ///     }
5694         /// } catch (...) {
5695         ///     catches
5696         /// } finally {
5697         ///     finalizer
5698         /// }
5699         /// ```
5700         ///
5701         /// @jls 14.20.3 try-with-resources
5702         /// @jls 14.20.3.1 Basic try-with-resources
5703         /// @jls 14.20.3.2 Extended try-with-resources
5704         Body normalizeTryWithResources() {
5705             return syntheticBody(entryBlock -> {
5706                 Function<Block.Builder, TryOp> normalizedTry = block -> {
5707                     block.context().mapValues(capturedValues(), entryBlock.parameters());
5708                     return normalizeExtendedTryWithResources(block.parentBody(), block.context(), new ArrayList<>());
5709                 };
5710                 if (catchBodies.isEmpty() && finallyBody == null) {
5711                     entryBlock.add(normalizedTry.apply(entryBlock));
5712                 } else {
5713                     CatchBuilder catchBuilder = try_(entryBlock.parentBody(), tryB -> {
5714                         tryB.add(normalizedTry.apply(tryB));
5715                         tryB.add(core_yield());
5716                     });
5717                     for (Body catcher : catchBodies) {
5718                         catchBuilder.catch_(catcher.bodySignature().parameterTypes().getFirst(), catchB ->
5719                                 catchB.transformBody(catcher, catchB.parameters(), entryBlock.context(), CodeTransformer.COPYING_TRANSFORMER));
5720                     }
5721                     entryBlock.add(finallyBody == null
5722                             ? catchBuilder.noFinalizer()
5723                             : catchBuilder.finally_(finB ->
5724                                     finB.transformBody(finallyBody, List.of(), entryBlock.context(), CodeTransformer.COPYING_TRANSFORMER)));
5725                 }
5726                 entryBlock.add(core_yield());
5727             });
5728         }
5729 
5730         /// Lower basic try-with-resources to `try / catch / finally`.
5731         ///
5732         /// Keeps the primary exception from the try body and adds as suppressed an exception from resource close.
5733         ///
5734         /// Use standalone synthetic body, so the lowered model is complete and can be further transformed.
5735         ///
5736         /// ```
5737         /// resource = acquire()
5738         /// primary = null
5739         /// try {
5740         ///     body(resources)
5741         /// } catch (e) {
5742         ///     primary = e
5743         ///     throw t
5744         /// } finally {
5745         ///     if (resource != null) {
5746         ///         if (primary != null) {
5747         ///             try { resource.close(); }
5748         ///             catch (closeExc) { primary.addSuppressed(closeExc); }
5749         ///         } else {
5750         ///             resource.close();
5751         ///         }
5752         ///     }
5753         /// }
5754         /// ```
5755         ///
5756         /// @jls 14.20.3.1 Basic try-with-resources
5757         Body lowerBasicTryWithResources() {
5758             assert resourcesBodies.size() == 1;
5759             CodeType resourceType = resourcesBodies.getFirst().bodySignature().returnType();
5760             return syntheticBody(entryBlock -> {
5761                 Block.Builder afterAcquire = entryBlock.block(resourceType);
5762                 entryBlock.transformBody(resourcesBodies.getFirst(), List.of(), (block, op) -> {
5763                     if (op instanceof CoreOp.YieldOp yop) {
5764                         block.add(branch(afterAcquire.reference(block.context().getValue(yop.yieldValue()))));
5765                     } else {
5766                         block.add(op);
5767                     }
5768                     return block;
5769                 });
5770                 Value resource = afterAcquire.parameters().getFirst();
5771                 Value primaryExceptionVar = afterAcquire.add(var(afterAcquire.add(constant(type(Throwable.class), null))));
5772                 // @@@ following builder code may be refactored into a reflected template method transformation
5773                 afterAcquire.add(try_(entryBlock.parentBody(), tryEntry -> {
5774                     tryEntry.transformBody(body, List.of(resource), afterAcquire.context(), CodeTransformer.COPYING_TRANSFORMER);
5775                 }).catch_(type(Throwable.class), catchB -> {
5776                     Block.Parameter thrown = catchB.parameters().getFirst();
5777                     catchB.add(varStore(primaryExceptionVar, thrown));
5778                     catchB.add(throw_(thrown));
5779                 }).finally_(finB -> {
5780                     Value nullObj = finB.add(constant(J_L_OBJECT, null));
5781                     finB.add(if_(finB.parentBody()).if_(predB -> {
5782                                 predB.add(core_yield(predB.add(neq(resource, nullObj))));
5783                     }).then(closeB -> {
5784                         Value primaryException = closeB.add(varLoad(primaryExceptionVar));
5785                         closeB.add(if_(closeB.parentBody()).if_(predB -> {
5786                             predB.add(core_yield(predB.add(neq(primaryException, nullObj))));
5787                         }).then(suppB -> {
5788                             suppB.add(try_(suppB.parentBody(), tryB -> {
5789                                 tryB.add(invoke(AUTO_CLOSEABLE_CLOSE_METHOD, resource));
5790                                 tryB.add(core_yield());
5791                             }).catch_(type(Throwable.class), catchB -> {
5792                                 Block.Parameter closeException = catchB.parameters().getFirst();
5793                                 catchB.add(invoke(THROWABLE_ADD_SUPPRESSED_METHOD, primaryException, closeException));
5794                                 catchB.add(core_yield());
5795                             }).noFinalizer());
5796                             suppB.add(core_yield());
5797                         }).else_(normB -> {
5798                             normB.add(invoke(AUTO_CLOSEABLE_CLOSE_METHOD, resource));
5799                             normB.add(core_yield());
5800                         }));
5801                         closeB.add(core_yield());
5802                     }).else_());
5803                     finB.add(core_yield());
5804                 }));
5805                 afterAcquire.add(core_yield());
5806             });
5807         }
5808 
5809         /// Recursive step for extended try-with-resources.
5810         ///
5811         /// Resource `index` becomes the current outer basic try-with-resources.
5812         ///
5813         /// @jls 14.20.3.2 Extended try-with-resources
5814         TryOp normalizeExtendedTryWithResources(Body.Builder ancestorBody, CodeContext cc, List<Value> resourceValues) {
5815             Body resource = resourcesBodies.get(resourceValues.size());
5816             Body.Builder resourceBody = Body.Builder.of(ancestorBody, CoreType.functionType(resource.yieldType()), cc);
5817             resourceBody.entryBlock().transformBody(resource, resourceValues, cc, CodeTransformer.COPYING_TRANSFORMER);
5818             Body.Builder basicBody = Body.Builder.of(ancestorBody, CoreType.functionType(VOID, List.of(resource.yieldType())), cc);
5819             Block.Builder bodyB = basicBody.entryBlock();
5820             resourceValues.add(bodyB.parameters().getFirst());
5821             if (resourceValues.size() < resourcesBodies.size()) {
5822                 bodyB.add(normalizeExtendedTryWithResources(basicBody, cc, resourceValues));
5823                 bodyB.add(core_yield());
5824             } else {
5825                 bodyB.transformBody(body, resourceValues, cc, CodeTransformer.COPYING_TRANSFORMER);
5826             }
5827             return try_(List.of(resourceBody), basicBody, List.of(), null);
5828         }
5829 
5830         Body syntheticBody(Consumer<Block.Builder> c) {
5831             List<Value> captures = capturedValues();
5832             Body.Builder syntheticBody = Body.Builder.of(null, CoreType.functionType(VOID, captures.stream().map(Value::type).toList()));
5833             Block.Builder entryBlock = syntheticBody.entryBlock();
5834             entryBlock.context().mapValues(captures, entryBlock.parameters());
5835             c.accept(entryBlock);
5836             return syntheticBody.build(unreachable());
5837         }
5838 
5839         boolean ifExitFromTry(StatementTargetOp lop) {
5840             Op target = lop.target();
5841             return target == this || target.isAncestorOf(this);
5842         }
5843 
5844         Block.Builder inlineFinalizer(Block.Builder block1, Value enter, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
5845             Block.Builder finallyEnter = block1.block();
5846             Block.Builder finallyExit = block1.block();
5847 
5848             block1.add(exceptionRegionExit(enter, finallyEnter.reference()));
5849 
5850             // Inline the finally body
5851             finallyEnter.transformBody(finallyBody, List.of(), loweringTransformer(inherited, (block2, op2) -> {
5852                 if (op2 instanceof CoreOp.YieldOp) {
5853                     block2.add(branch(finallyExit.reference()));
5854                     return block2;
5855                 } else {
5856                     return null;
5857                 }
5858             }));
5859 
5860             return finallyExit;
5861         }
5862 
5863         @Override
5864         public CodeType resultType() {
5865             return VOID;
5866         }
5867     }
5868 
5869     //
5870     // Patterns
5871 
5872     // Reified pattern nodes
5873 
5874     /**
5875      * Synthetic pattern types
5876      * // @@@ Replace with types extending from CodeType
5877      */
5878     public sealed interface Pattern {
5879 
5880         /**
5881          * Synthetic type pattern type.
5882          *
5883          * @param <T> the type of values that are bound
5884          */
5885         final class Type<T> implements Pattern {
5886             Type() {
5887             }
5888         }
5889 
5890         /**
5891          * Synthetic record pattern type.
5892          *
5893          * @param <T> the type of records that are bound
5894          */
5895         final class Record<T> implements Pattern {
5896             Record() {
5897             }
5898         }
5899 
5900         /**
5901          * A synthetic match-all pattern type representing an unconditional pattern.
5902          */
5903         final class MatchAll implements Pattern {
5904             MatchAll() {
5905             }
5906         }
5907 
5908         // @@@ Pattern types
5909 
5910         /** The synthetic type of a type test pattern. */
5911         JavaType PATTERN_BINDING_TYPE = JavaType.type(Type.class);
5912 
5913         /** The synthetic type of a record pattern. */
5914         JavaType PATTERN_RECORD_TYPE = JavaType.type(Record.class);
5915 
5916         /** The synthetic type of an unconditional pattern. */
5917         JavaType PATTERN_MATCH_ALL_TYPE = JavaType.type(MatchAll.class);
5918 
5919         /**
5920          * {@return a synthetic type for a type test pattern with the provided type}
5921          * @param t the type of the type test pattern
5922          */
5923         static JavaType bindingType(CodeType t) {
5924             return parameterized(PATTERN_BINDING_TYPE, (JavaType) t);
5925         }
5926 
5927         /**
5928          * {@return a synthetic type for a record pattern with the provided record type}
5929          * @param t the record type
5930          */
5931         static JavaType recordType(CodeType t) {
5932             return parameterized(PATTERN_RECORD_TYPE, (JavaType) t);
5933         }
5934 
5935         /**
5936          * {@return a synthetic type for an unconditional pattern}
5937          */
5938         static JavaType matchAllType() {
5939             return PATTERN_MATCH_ALL_TYPE;
5940         }
5941 
5942         /**
5943          * {@return the type bound by a synthetic type test/record pattern}
5944          * @param t the synthetic pattern type
5945          */
5946         static CodeType targetType(CodeType t) {
5947             return ((ClassType) t).typeArguments().get(0);
5948         }
5949     }
5950 
5951     /**
5952      * Pattern operations.
5953      *
5954      * @jls 14.30 Patterns
5955      */
5956     public static final class PatternOps {
5957         PatternOps() {
5958         }
5959 
5960         /**
5961          * The pattern operation.
5962          * <p>
5963          * The result type of a pattern operation is a synthetic {@linkplain Pattern pattern type}.
5964          * Pattern operations are used in pattern bodies of {@link MatchOp} and as nested pattern operands of
5965          * {@link RecordPatternOp}.
5966          */
5967         public sealed static abstract class PatternOp extends JavaOp implements Op.Pure {
5968             PatternOp(PatternOp that, CodeContext cc) {
5969                 super(that, cc);
5970             }
5971 
5972             PatternOp(List<Value> operands) {
5973                 super(operands);
5974             }
5975         }
5976 
5977         /**
5978          * The type pattern operation, that can model Java language type test patterns.
5979          * <p>
5980          * Type pattern operations are associated with a target type (a {@link JavaType})
5981          * and an optional binding name.
5982          *
5983          * @jls 14.30.1 Kinds of Patterns
5984          * @jls 15.20.2 The instanceof Operator
5985          */
5986         @OpDeclaration(TypePatternOp.NAME)
5987         public static final class TypePatternOp extends PatternOp {
5988             static final String NAME = "pattern.type";
5989 
5990             /**
5991              * The externalized attribute key for a pattern binding name in a type pattern operation.
5992              */
5993         static final String ATTRIBUTE_BINDING_NAME = NAME + ".binding.name";
5994 
5995             final CodeType resultType;
5996             final String bindingName;
5997 
5998             TypePatternOp(ExternalizedOp def) {
5999                 super(List.of());
6000 
6001                 this.bindingName = def.extractAttributeValue(ATTRIBUTE_BINDING_NAME, true,
6002                         v -> switch (v) {
6003                             case String s -> s;
6004                             case null -> null;
6005                             default -> throw new UnsupportedOperationException("Unsupported pattern binding name value:" + v);
6006                         });
6007                 // @@@ Cannot use canonical constructor because it wraps the given type
6008                 this.resultType = def.resultType();
6009             }
6010 
6011             TypePatternOp(TypePatternOp that, CodeContext cc) {
6012                 super(that, cc);
6013 
6014                 this.bindingName = that.bindingName;
6015                 this.resultType = that.resultType;
6016             }
6017 
6018             @Override
6019             public TypePatternOp transform(CodeContext cc, CodeTransformer ct) {
6020                 return new TypePatternOp(this, cc);
6021             }
6022 
6023             TypePatternOp(CodeType targetType, String bindingName) {
6024                 super(List.of());
6025 
6026                 this.bindingName = bindingName;
6027                 this.resultType = Pattern.bindingType(targetType);
6028             }
6029 
6030             @Override
6031             public Map<String, Object> externalize() {
6032                 return bindingName == null ? Map.of() : Map.of("", bindingName);
6033             }
6034 
6035             /**
6036              * {@return the variable name bound by this type test pattern, or {@code null} if none}
6037              */
6038             public String bindingName() {
6039                 return bindingName;
6040             }
6041 
6042             /**
6043              * {@return the type matched by this type test pattern}
6044              */
6045             public CodeType targetType() {
6046                 return Pattern.targetType(resultType());
6047             }
6048 
6049             @Override
6050             public CodeType resultType() {
6051                 return resultType;
6052             }
6053         }
6054 
6055         /**
6056          * The record pattern operation, that can model Java language record patterns.
6057          * <p>
6058          * Record pattern operations are associated with a {@linkplain RecordTypeRef record reference}.
6059          * The operands are nested pattern values.
6060          *
6061          * @jls 14.30.1 Kinds of Patterns
6062          */
6063         @OpDeclaration(RecordPatternOp.NAME)
6064         public static final class RecordPatternOp extends PatternOp {
6065             static final String NAME = "pattern.record";
6066 
6067             /**
6068               * The externalized attribute key for a record reference in a record pattern operation.
6069               */
6070             static final String ATTRIBUTE_RECORD_REF = NAME + ".ref";
6071 
6072             final RecordTypeRef recordReference;
6073 
6074             RecordPatternOp(ExternalizedOp def) {
6075                 RecordTypeRef recordRef = def.extractAttributeValue(ATTRIBUTE_RECORD_REF, true,
6076                         v -> switch (v) {
6077                             case RecordTypeRef rtd -> rtd;
6078                             case null, default ->
6079                                     throw new UnsupportedOperationException("Unsupported record type reference value:" + v);
6080                         });
6081 
6082                 this(recordRef, def.operands());
6083             }
6084 
6085             RecordPatternOp(RecordPatternOp that, CodeContext cc) {
6086                 super(that, cc);
6087 
6088                 this.recordReference = that.recordReference;
6089             }
6090 
6091             @Override
6092             public RecordPatternOp transform(CodeContext cc, CodeTransformer ct) {
6093                 return new RecordPatternOp(this, cc);
6094             }
6095 
6096             RecordPatternOp(RecordTypeRef recordReference, List<Value> nestedPatterns) {
6097                 // The type of each value is a subtype of Pattern
6098                 // The number of values corresponds to the number of components of the record
6099                 super(List.copyOf(nestedPatterns));
6100 
6101                 this.recordReference = recordReference;
6102             }
6103 
6104             @Override
6105             public Map<String, Object> externalize() {
6106                 return Map.of("", recordReference());
6107             }
6108 
6109             /**
6110               * {@return the record reference associated with this record pattern}
6111               */
6112             public RecordTypeRef recordReference() {
6113                 return recordReference;
6114             }
6115 
6116             /**
6117              * {@return the type matched by this record pattern}
6118              */
6119             public CodeType targetType() {
6120                 return Pattern.targetType(resultType());
6121             }
6122 
6123             @Override
6124             public CodeType resultType() {
6125                 return Pattern.recordType(recordReference.recordType());
6126             }
6127         }
6128 
6129         /**
6130          * A pattern operation representing a match-all (unconditional) pattern.
6131          *
6132          * @jls 14.30.1 Kinds of Patterns
6133          */
6134         @OpDeclaration(MatchAllPatternOp.NAME)
6135         public static final class MatchAllPatternOp extends PatternOp {
6136 
6137             // @@@ we may need to add info about the type of the record component
6138             // this info can be used when lowering
6139 
6140             static final String NAME = "pattern.match.all";
6141 
6142             MatchAllPatternOp(ExternalizedOp def) {
6143                 this();
6144             }
6145 
6146             MatchAllPatternOp(MatchAllPatternOp that, CodeContext cc) {
6147                 super(that, cc);
6148             }
6149 
6150             MatchAllPatternOp() {
6151                 super(List.of());
6152             }
6153 
6154             @Override
6155             public Op transform(CodeContext cc, CodeTransformer ct) {
6156                 return new MatchAllPatternOp(this, cc);
6157             }
6158 
6159             @Override
6160             public CodeType resultType() {
6161                 return Pattern.matchAllType();
6162             }
6163         }
6164 
6165         /**
6166          * The match operation, that can model Java language pattern matching.
6167          * <p>
6168          * Match operations can be used to model instanceof expressions with a pattern match operator, or
6169          * case labels with case patterns in switch statements and switch expressions.
6170          * <p>
6171          * Match operations feature one operand, the target value being matched, and two bodies: the pattern body and
6172          * the match body.
6173          * <p>
6174          * The pattern body should accept no arguments and yield a pattern value.
6175          * The match body accepts the values bound by the pattern body and yields {@linkplain JavaType#VOID no value}.
6176          * The result type of a match operation is {@link JavaType#BOOLEAN}.
6177          *
6178          * @jls 14.30.2 Pattern Matching
6179          * @jls 14.11 The switch Statement
6180          * @jls 15.28 switch Expressions
6181          * @jls 15.20.2 The instanceof Operator
6182          */
6183         @OpDeclaration(MatchOp.NAME)
6184         public static final class MatchOp extends JavaOp implements Op.Isolated, Op.Lowerable {
6185             static final String NAME = "pattern.match";
6186 
6187             final Body patternBody;
6188             final Body matchBody;
6189 
6190             MatchOp(ExternalizedOp def) {
6191                 this(def.operands().get(0),
6192                         def.bodyDefinitions().get(0), def.bodyDefinitions().get(1));
6193             }
6194 
6195             MatchOp(MatchOp that, CodeContext cc, CodeTransformer ct) {
6196                 super(that, cc);
6197 
6198                 this.patternBody = that.patternBody.transform(cc, ct).build(this);
6199                 this.matchBody = that.matchBody.transform(cc, ct).build(this);
6200             }
6201 
6202             @Override
6203             public MatchOp transform(CodeContext cc, CodeTransformer ct) {
6204                 return new MatchOp(this, cc, ct);
6205             }
6206 
6207             MatchOp(Value target, Body.Builder patternC, Body.Builder matchC) {
6208                 super(List.of(target));
6209 
6210                 this.patternBody = patternC.build(this);
6211                 this.matchBody = matchC.build(this);
6212             }
6213 
6214             @Override
6215             public List<Body> bodies() {
6216                 return List.of(patternBody, matchBody);
6217             }
6218 
6219             /**
6220              * Returns the pattern body for this match operation.
6221              *
6222              * @return the pattern body
6223              */
6224             public Body patternBody() {
6225                 return patternBody;
6226             }
6227 
6228             /**
6229              * Returns the match body for this match operation.
6230              *
6231              * @return the match body
6232              */
6233             public Body matchBody() {
6234                 return matchBody;
6235             }
6236 
6237             /**
6238              * Returns the target value being matched in this match operation.
6239              *
6240              * @return the match target value
6241              */
6242             public Value targetOperand() {
6243                 return operands().get(0);
6244             }
6245 
6246             @Override
6247             public Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited) {
6248                 // No match block
6249                 Block.Builder endNoMatchBlock = b.block();
6250                 // Match block
6251                 Block.Builder endMatchBlock = b.block();
6252                 // End block
6253                 Block.Builder endBlock = b.block();
6254                 Block.Parameter matchResult = endBlock.parameter(resultType());
6255                 // Map match operation result
6256                 b.context().mapValue(result(), matchResult);
6257 
6258                 List<Value> patternValues = new ArrayList<>();
6259                 Op patternYieldOp = patternBody.entryBlock().terminatingOp();
6260                 Op.Result rootPatternValue = (Op.Result) patternYieldOp.operands().get(0);
6261                 Block.Builder currentBlock = lower(endNoMatchBlock, b,
6262                         patternValues,
6263                         rootPatternValue.op(),
6264                         b.context().getValue(targetOperand()));
6265                 currentBlock.add(branch(endMatchBlock.reference()));
6266 
6267                 // No match block
6268                 // Pass false
6269                 endNoMatchBlock.add(branch(endBlock.reference(
6270                         endNoMatchBlock.add(constant(BOOLEAN, false)))));
6271 
6272                 // Match block
6273                 // Lower match body and pass true
6274                 endMatchBlock.transformBody(matchBody, patternValues, loweringTransformer(inherited, (block, op) -> {
6275                     if (op instanceof CoreOp.YieldOp) {
6276                         block.add(branch(endBlock.reference(
6277                                 block.add(constant(BOOLEAN, true)))));
6278                         return block;
6279                     } else {
6280                         return null;
6281                     }
6282                 }));
6283 
6284                 return endBlock;
6285             }
6286 
6287             static Block.Builder lower(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
6288                                        List<Value> bindings,
6289                                        Op pattern, Value target) {
6290                 return switch (pattern) {
6291                     case RecordPatternOp rp -> lowerRecordPattern(endNoMatchBlock, currentBlock, bindings, rp, target);
6292                     case TypePatternOp tp -> lowerTypePattern(endNoMatchBlock, currentBlock, bindings, tp, target);
6293                     case MatchAllPatternOp map -> lowerMatchAllPattern(currentBlock);
6294                     case null, default -> throw new UnsupportedOperationException("Unknown pattern op: " + pattern);
6295                 };
6296             }
6297 
6298             static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
6299                                                     List<Value> bindings,
6300                                                     JavaOp.PatternOps.RecordPatternOp rpOp, Value target) {
6301                 CodeType targetType = rpOp.targetType();
6302 
6303                 Block.Builder nextBlock = currentBlock.block();
6304 
6305                 // Check if instance of target type
6306                 Op.Result isInstance = currentBlock.add(instanceOf(targetType, target));
6307                 currentBlock.add(conditionalBranch(isInstance, nextBlock.reference(), endNoMatchBlock.reference()));
6308 
6309                 currentBlock = nextBlock;
6310 
6311                 target = currentBlock.add(cast(targetType, target));
6312 
6313                 // Access component values of record and match on each as nested target
6314                 List<Value> dArgs = rpOp.operands();
6315                 for (int i = 0; i < dArgs.size(); i++) {
6316                     Op.Result nestedPattern = (Op.Result) dArgs.get(i);
6317                     // @@@ Handle exceptions?
6318             Value nestedTarget = currentBlock.add(invoke(rpOp.recordReference().methodForComponent(i), target));
6319 
6320                     currentBlock = lower(endNoMatchBlock, currentBlock, bindings, nestedPattern.op(), nestedTarget);
6321                 }
6322 
6323                 return currentBlock;
6324             }
6325 
6326             static Block.Builder lowerTypePattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
6327                                                   List<Value> bindings,
6328                                                   TypePatternOp tpOp, Value target) {
6329                 CodeType targetType = tpOp.targetType();
6330 
6331                 // Check if instance of target type
6332                 Op p; // op that perform type check
6333                 Op c; // op that perform conversion
6334                 CodeType s = target.type();
6335                 CodeType t = targetType;
6336                 if (t instanceof PrimitiveType pt) {
6337                     if (s instanceof ClassType cs) {
6338                         // unboxing conversions
6339                         ClassType box;
6340                         if (cs.unbox().isEmpty()) { // s not a boxed type
6341                             // e.g. Number -> int, narrowing + unboxing
6342                             box = pt.box().orElseThrow();
6343                             p = instanceOf(box, target);
6344                         } else {
6345                             // e.g. Float -> float, unboxing
6346                             // e.g. Integer -> long, unboxing + widening
6347                             box = cs;
6348                             p = null;
6349                         }
6350                         c = invoke(MethodRef.method(box, t + "Value", t), target);
6351                     } else {
6352                         // primitive to primitive conversion
6353                         PrimitiveType ps = ((PrimitiveType) s);
6354                         if (isNarrowingPrimitiveConv(ps, pt) || isWideningPrimitiveConvWithCheck(ps, pt)
6355                                 || isWideningAndNarrowingPrimitiveConv(ps, pt)) {
6356                             // e.g. int -> byte, narrowing
6357                             // e,g. int -> float, widening with check
6358                             // e.g. byte -> char, widening and narrowing
6359                             MethodRef mref = convMethodRef(s, t);
6360                             p = invoke(mref, target);
6361                         } else {
6362                             p = null;
6363                         }
6364                         c = conv(targetType, target);
6365                     }
6366                 } else if (s instanceof PrimitiveType ps) {
6367                     // boxing conversions
6368                     // e.g. int -> Number, boxing + widening
6369                     // e.g. byte -> Byte, boxing
6370                     p = null;
6371                     ClassType box = ps.box().orElseThrow();
6372                     c = invoke(MethodRef.method(box, "valueOf", box, ps), target);
6373                 } else if (!s.equals(t)) {
6374                     // reference to reference, but not identity
6375                     // e.g. Number -> Double, narrowing
6376                     // e.g. Short -> Object, widening
6377                     p = instanceOf(targetType, target);
6378                     c = cast(targetType, target);
6379                 } else {
6380                     // identity reference
6381                     // e.g. Character -> Character
6382                     p = null;
6383                     c = null;
6384                 }
6385 
6386                 if (c != null) {
6387                     if (p != null) {
6388                         // p != null, we need to perform type check at runtime
6389                         Block.Builder nextBlock = currentBlock.block();
6390                         currentBlock.add(conditionalBranch(currentBlock.add(p), nextBlock.reference(), endNoMatchBlock.reference()));
6391                         currentBlock = nextBlock;
6392                     }
6393                     target = currentBlock.add(c);
6394                 }
6395 
6396                 bindings.add(target);
6397 
6398                 return currentBlock;
6399             }
6400 
6401             private static boolean isWideningAndNarrowingPrimitiveConv(PrimitiveType s, PrimitiveType t) {
6402                 return BYTE.equals(s) && CHAR.equals(t);
6403             }
6404 
6405             private static boolean isWideningPrimitiveConvWithCheck(PrimitiveType s, PrimitiveType t) {
6406                 return (INT.equals(s) && FLOAT.equals(t))
6407                         || (LONG.equals(s) && FLOAT.equals(t))
6408                         || (LONG.equals(s) && DOUBLE.equals(t));
6409             }
6410 
6411             // s -> t is narrowing if order(t) <= order(s)
6412             private final static Map<PrimitiveType, Integer> narrowingOrder = Map.of(
6413                     BYTE, 1,
6414                     SHORT, 2,
6415                     CHAR, 2,
6416                     INT, 3,
6417                     LONG, 4,
6418                     FLOAT, 5,
6419                     DOUBLE, 6
6420             );
6421             private static boolean isNarrowingPrimitiveConv(PrimitiveType s, PrimitiveType t) {
6422                 return narrowingOrder.get(t) <= narrowingOrder.get(s) && !s.equals(t); // need to be strict, to not consider int -> int as narrowing
6423             }
6424 
6425             private static MethodRef convMethodRef(CodeType s, CodeType t) {
6426                 if (BYTE.equals(s) || SHORT.equals(s) || CHAR.equals(s)) {
6427                     s = INT;
6428                 }
6429                 String sn = capitalize(s.toString());
6430                 String tn = capitalize(t.toString());
6431                 String mn = "is%sTo%sExact".formatted(sn, tn);
6432                 JavaType exactConversionSupport = JavaType.type(ClassDesc.of("java.lang.runtime.ExactConversionsSupport"));
6433                 return MethodRef.method(exactConversionSupport, mn, BOOLEAN, s);
6434             }
6435 
6436             private static String capitalize(String s) {
6437                 return s.substring(0, 1).toUpperCase() + s.substring(1);
6438             }
6439 
6440             static Block.Builder lowerMatchAllPattern(Block.Builder currentBlock) {
6441                 return currentBlock;
6442             }
6443 
6444             @Override
6445             public CodeType resultType() {
6446                 return BOOLEAN;
6447             }
6448         }
6449     }
6450 
6451     /**
6452      * Returns a composed function that composes {@code g} into the first argument of {@code f}.
6453      * <p>
6454      * if {@code f} is {@code null} then this method returns {@code g}.
6455      *
6456      * @param f the outer function
6457      * @param g the inner function
6458      * @return the composed
6459      */
6460     static <T, U> BiFunction<T, U, T> composeFirst(
6461             BiFunction<T, U, T> f,
6462             BiFunction<T, U, T> g) {
6463         Objects.requireNonNull(g);
6464         return f == null
6465                 ? g
6466                 : (builder, op) -> f.apply(g.apply(builder, op), op);
6467     }
6468 
6469     static Op createOp(ExternalizedOp def) {
6470         Op op = switch (def.name()) {
6471             case "add" -> new AddOp(def);
6472             case "and" -> new AndOp(def);
6473             case "array.length" -> new ArrayLengthOp(def);
6474             case "array.load" -> new ArrayAccessOp.ArrayLoadOp(def);
6475             case "array.store" -> new ArrayAccessOp.ArrayStoreOp(def);
6476             case "ashr" -> new AshrOp(def);
6477             case "assert" -> new AssertOp(def);
6478             case "cast" -> new CastOp(def);
6479             case "compl" -> new ComplOp(def);
6480             case "concat" -> new ConcatOp(def);
6481             case "conv" -> new ConvOp(def);
6482             case "div" -> new DivOp(def);
6483             case "eq" -> new EqOp(def);
6484             case "exception.region.enter" -> new ExceptionRegionEnter(def);
6485             case "exception.region.exit" -> new ExceptionRegionExit(def);
6486             case "field.load" -> new FieldAccessOp.FieldLoadOp(def);
6487             case "field.store" -> new FieldAccessOp.FieldStoreOp(def);
6488             case "ge" -> new GeOp(def);
6489             case "gt" -> new GtOp(def);
6490             case "instanceof" -> new InstanceOfOp(def);
6491             case "invoke" -> new InvokeOp(def);
6492             case "java.block" -> new BlockOp(def);
6493             case "java.break" -> new BreakOp(def);
6494             case "java.cand" -> new ConditionalAndOp(def);
6495             case "java.cexpression" -> new ConditionalExpressionOp(def);
6496             case "java.continue" -> new ContinueOp(def);
6497             case "java.cor" -> new ConditionalOrOp(def);
6498             case "java.do.while" -> new DoWhileOp(def);
6499             case "java.enhancedFor" -> new EnhancedForOp(def);
6500             case "java.for" -> new ForOp(def);
6501             case "java.if" -> new IfOp(def);
6502             case "java.labeled" -> new LabeledOp(def);
6503             case "java.switch.expression" -> new SwitchExpressionOp(def);
6504             case "java.switch.fallthrough" -> new SwitchFallthroughOp(def);
6505             case "java.switch.statement" -> new SwitchStatementOp(def);
6506             case "java.synchronized" -> new SynchronizedOp(def);
6507             case "java.try" -> new TryOp(def);
6508             case "java.while" -> new WhileOp(def);
6509             case "java.yield" -> new YieldOp(def);
6510             case "lambda" -> new LambdaOp(def);
6511             case "le" -> new LeOp(def);
6512             case "lshl" -> new LshlOp(def);
6513             case "lshr" -> new LshrOp(def);
6514             case "lt" -> new LtOp(def);
6515             case "mod" -> new ModOp(def);
6516             case "monitor.enter" -> new MonitorOp.MonitorEnterOp(def);
6517             case "monitor.exit" -> new MonitorOp.MonitorExitOp(def);
6518             case "mul" -> new MulOp(def);
6519             case "neg" -> new NegOp(def);
6520             case "neq" -> new NeqOp(def);
6521             case "new" -> new NewOp(def);
6522             case "not" -> new NotOp(def);
6523             case "or" -> new OrOp(def);
6524             case "pattern.match" -> new PatternOps.MatchOp(def);
6525             case "pattern.match.all" -> new PatternOps.MatchAllPatternOp(def);
6526             case "pattern.record" -> new PatternOps.RecordPatternOp(def);
6527             case "pattern.type" -> new PatternOps.TypePatternOp(def);
6528             case "sub" -> new SubOp(def);
6529             case "throw" -> new ThrowOp(def);
6530             case "xor" -> new XorOp(def);
6531             default -> null;
6532         };
6533         if (op != null) {
6534             op.setLocation(def.location());
6535         }
6536         return op;
6537     }
6538 
6539     /**
6540      * An operation factory for core operations composed with Java operations.
6541      */
6542     public static final OpFactory JAVA_OP_FACTORY = CoreOp.CORE_OP_FACTORY.andThen(JavaOp::createOp);
6543 
6544     /**
6545      * A Java dialect factory, for constructing core and Java operations and constructing
6546      * core types and Java types, where the core types can refer to Java
6547      * types.
6548      */
6549     public static final DialectFactory JAVA_DIALECT_FACTORY = new DialectFactory(
6550             JAVA_OP_FACTORY,
6551             JAVA_TYPE_FACTORY);
6552 
6553     /**
6554      * Creates a lambda operation.
6555      *
6556      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
6557      *                              connected, or {@code null} if they are isolated
6558      * @param signature             the lambda operation's signature, represented as a function type
6559      * @param functionalInterface   the lambda operation's functional interface type
6560      * @return the lambda operation
6561      */
6562     public static LambdaOp.Builder lambda(Body.Builder connectedAncestorBody,
6563                                           FunctionType signature, CodeType functionalInterface) {
6564         return new LambdaOp.Builder(connectedAncestorBody, signature, functionalInterface);
6565     }
6566 
6567     /**
6568      * Creates a lambda operation.
6569      *
6570      * @param functionalInterface the lambda operation's functional interface type
6571      * @param body                the body of the lambda operation
6572      * @return the lambda operation
6573      */
6574     public static LambdaOp lambda(CodeType functionalInterface, Body.Builder body) {
6575         return new LambdaOp(functionalInterface, body, false);
6576     }
6577 
6578     /**
6579      * Creates a lambda operation.
6580      *
6581      * @param functionalInterface the lambda operation's functional interface type
6582      * @param body                the body of the lambda operation
6583      * @param isReflectable       true if the lambda is reflectable
6584      * @return the lambda operation
6585      */
6586     public static LambdaOp lambda(CodeType functionalInterface, Body.Builder body, boolean isReflectable) {
6587         return new LambdaOp(functionalInterface, body, isReflectable);
6588     }
6589 
6590     /**
6591      * Creates an exception region enter operation
6592      *
6593      * @param start    the reference to the block that enters the exception region
6594      * @param catchers the references to blocks handling exceptions thrown by blocks within the exception region
6595      * @return the exception region enter operation
6596      */
6597     public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, Block.Reference... catchers) {
6598         return exceptionRegionEnter(start, List.of(catchers));
6599     }
6600 
6601     /**
6602      * Creates an exception region enter operation
6603      *
6604      * @param start    the reference to the block that enters the exception region
6605      * @param catchers the references to blocks handling exceptions thrown by blocks within the exception region
6606      * @return the exception region enter operation
6607      */
6608     public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, List<Block.Reference> catchers) {
6609         List<Block.Reference> s = new ArrayList<>();
6610         s.add(start);
6611         s.addAll(catchers);
6612         return new ExceptionRegionEnter(s);
6613     }
6614 
6615     /**
6616      * Creates an exception region exit operation
6617      *
6618      * @param enter the result of the dominant {@link ExceptionRegionEnter}
6619      * @param end   the reference to the block reached after exiting the exception region
6620      * @return the exception region exit operation
6621      */
6622     public static ExceptionRegionExit exceptionRegionExit(Value enter, Block.Reference end) {
6623         return new ExceptionRegionExit(enter, end);
6624     }
6625 
6626     /**
6627      * Creates a throw operation.
6628      *
6629      * @param exceptionValue the thrown value
6630      * @return the throw operation
6631      */
6632     public static ThrowOp throw_(Value exceptionValue) {
6633         return new ThrowOp(exceptionValue);
6634     }
6635 
6636     /**
6637      * Creates an assert operation.
6638      *
6639      * @param bodies the nested bodies
6640      * @return the assert operation
6641      */
6642     public static AssertOp assert_(List<Body.Builder> bodies) {
6643         return new AssertOp(bodies);
6644     }
6645 
6646     /**
6647      * Creates a monitor enter operation.
6648      * @param monitor the monitor value
6649      * @return the monitor enter operation
6650      */
6651     public static MonitorOp.MonitorEnterOp monitorEnter(Value monitor) {
6652         return new MonitorOp.MonitorEnterOp(monitor);
6653     }
6654 
6655     /**
6656      * Creates a monitor exit operation.
6657      * @param monitor the monitor value
6658      * @return the monitor exit operation
6659      */
6660     public static MonitorOp.MonitorExitOp monitorExit(Value monitor) {
6661         return new MonitorOp.MonitorExitOp(monitor);
6662     }
6663 
6664     /**
6665      * Creates an invoke operation modeling an invocation to an
6666      * instance or static (class) method with no variable arguments.
6667      * <p>
6668      * The invoke kind of the invoke operation is determined by
6669      * comparing the argument count with the method reference's
6670      * parameter count. If they are equal then the invoke kind is
6671      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
6672      * plus one is equal to the argument count then the invoke kind
6673      * is {@link InvokeOp.InvokeKind#INSTANCE instance}.
6674      * <p>
6675      * The result type of the invoke operation is the method reference's return type.
6676      *
6677      * @param invokeRef        the method reference
6678      * @param args             the invoke arguments
6679      * @return the invoke operation
6680      */
6681     public static InvokeOp invoke(MethodRef invokeRef, Value... args) {
6682         return invoke(invokeRef, List.of(args));
6683     }
6684 
6685     /**
6686      * Creates an invoke operation modeling an invocation to an
6687      * instance or static (class) method with no variable arguments.
6688      * <p>
6689      * The invoke kind of the invoke operation is determined by
6690      * comparing the argument count with the method reference's
6691      * parameter count. If they are equal then the invoke kind is
6692      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
6693      * plus one is equal to the argument count then the invoke kind
6694      * is {@link InvokeOp.InvokeKind#INSTANCE instance}.
6695      * <p>
6696      * The result type of the invoke operation is the method reference's return type.
6697      *
6698      * @param invokeRef        the method reference
6699      * @param args             the invoke arguments
6700      * @return the invoke operation
6701      */
6702     public static InvokeOp invoke(MethodRef invokeRef, List<Value> args) {
6703         return invoke(invokeRef.signature().returnType(), invokeRef, args);
6704     }
6705 
6706     /**
6707      * Creates an invoke operation modeling an invocation to an
6708      * instance or static (class) method with no variable arguments.
6709      * <p>
6710      * The invoke kind of the invoke operation is determined by
6711      * comparing the argument count with the method reference's
6712      * parameter count. If they are equal then the invoke kind is
6713      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
6714      * plus one is equal to the argument count then the invoke kind
6715      * is {@link InvokeOp.InvokeKind#INSTANCE instance}.
6716      *
6717      * @param returnType       the result type of the invoke operation
6718      * @param invokeRef        the method reference
6719      * @param args             the invoke arguments
6720      * @return the invoke operation
6721      */
6722     public static InvokeOp invoke(CodeType returnType, MethodRef invokeRef, Value... args) {
6723         return invoke(returnType, invokeRef, List.of(args));
6724     }
6725 
6726     /**
6727      * Creates an invoke operation modeling an invocation to an
6728      * instance or static (class) method with no variable arguments.
6729      * <p>
6730      * The invoke kind of the invoke operation is determined by
6731      * comparing the argument count with the method reference's
6732      * parameter count. If they are equal then the invoke kind is
6733      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
6734      * plus one is equal to the argument count then the invoke kind
6735      * is {@link InvokeOp.InvokeKind#INSTANCE instance}.
6736      *
6737      * @param returnType       the result type of the invoke operation
6738      * @param invokeRef        the method reference
6739      * @param args             the invoke arguments
6740      * @return the invoke super operation
6741      */
6742     public static InvokeOp invoke(CodeType returnType, MethodRef invokeRef, List<Value> args) {
6743         int paramCount = invokeRef.signature().parameterTypes().size();
6744         int argCount = args.size();
6745         InvokeOp.InvokeKind ik = (argCount == paramCount + 1)
6746                 ? InvokeOp.InvokeKind.INSTANCE
6747                 : InvokeOp.InvokeKind.STATIC;
6748         return new InvokeOp(ik, false, returnType, invokeRef, args);
6749     }
6750 
6751     /**
6752      * Creates an invoke operation modeling an invocation to a method.
6753      *
6754      * @param invokeKind       the invoke kind
6755      * @param isVarArgs        true if an invocation to a variable argument method
6756      * @param returnType       the result type of the invoke operation
6757      * @param invokeRef        the method reference
6758      * @param args             the invoke arguments
6759      * @return the invoke operation
6760      * @throws IllegalArgumentException if there is a mismatch between the argument count
6761      *                                  and the method reference's parameter count.
6762      */
6763     public static InvokeOp invoke(InvokeOp.InvokeKind invokeKind, boolean isVarArgs,
6764                                   CodeType returnType, MethodRef invokeRef, Value... args) {
6765         return new InvokeOp(invokeKind, isVarArgs, returnType, invokeRef, List.of(args));
6766     }
6767 
6768     /**
6769      * Creates an invoke operation modeling an invocation to a method.
6770      *
6771      * @param invokeKind       the invoke kind
6772      * @param isVarArgs        true if an invocation to a variable argument method
6773      * @param returnType       the result type of the invoke operation
6774      * @param invokeRef        the method reference
6775      * @param args             the invoke arguments
6776      * @return the invoke operation
6777      * @throws IllegalArgumentException if there is a mismatch between the argument count
6778      *                                  and the method reference's parameter count.
6779      */
6780     public static InvokeOp invoke(InvokeOp.InvokeKind invokeKind, boolean isVarArgs,
6781                                   CodeType returnType, MethodRef invokeRef, List<Value> args) {
6782         return new InvokeOp(invokeKind, isVarArgs, returnType, invokeRef, args);
6783     }
6784 
6785     /**
6786      * Creates a conversion operation.
6787      *
6788      * @param to   the conversion target type
6789      * @param from the value to be converted
6790      * @return the conversion operation
6791      */
6792     public static ConvOp conv(CodeType to, Value from) {
6793         return new ConvOp(to, from);
6794     }
6795 
6796     /**
6797      * Creates an instance creation operation.
6798      *
6799      * @param constructorRef  the constructor reference
6800      * @param args            the constructor arguments
6801      * @return the instance creation operation
6802      */
6803     public static NewOp new_(MethodRef constructorRef, Value... args) {
6804         return new_(constructorRef, List.of(args));
6805     }
6806 
6807     /**
6808      * Creates an instance creation operation.
6809      *
6810      * @param constructorRef  the constructor reference
6811      * @param args            the constructor arguments
6812      * @return the instance creation operation
6813      */
6814     public static NewOp new_(MethodRef constructorRef, List<Value> args) {
6815         return new NewOp(false, constructorRef.refType(), constructorRef, args);
6816     }
6817 
6818     /**
6819      * Creates an instance creation operation.
6820      *
6821      * @param returnType      the result type of the instance creation operation
6822      * @param constructorRef  the constructor reference
6823      * @param args            the constructor arguments
6824      * @return the instance creation operation
6825      */
6826     public static NewOp new_(CodeType returnType, MethodRef constructorRef,
6827                              Value... args) {
6828         return new_(returnType, constructorRef, List.of(args));
6829     }
6830 
6831     /**
6832      * Creates an instance creation operation.
6833      *
6834      * @param returnType      the result type of the instance creation operation
6835      * @param constructorRef  the constructor reference
6836      * @param args            the constructor arguments
6837      * @return the instance creation operation
6838      */
6839     public static NewOp new_(CodeType returnType, MethodRef constructorRef,
6840                              List<Value> args) {
6841         return new NewOp(false, returnType, constructorRef, args);
6842     }
6843 
6844     /**
6845      * Creates an instance creation operation.
6846      *
6847      * @param isVarargs {@code true} if calling a varargs constructor
6848      * @param returnType      the result type of the instance creation operation
6849      * @param constructorRef  the constructor reference
6850      * @param args            the constructor arguments
6851      * @return the instance creation operation
6852      */
6853     public static NewOp new_(boolean isVarargs, CodeType returnType, MethodRef constructorRef,
6854                              List<Value> args) {
6855         return new NewOp(isVarargs, returnType, constructorRef, args);
6856     }
6857 
6858     /**
6859      * Creates an array creation operation.
6860      *
6861      * @param arrayType the array type
6862      * @param length    the array size
6863      * @return the array creation operation
6864      */
6865     public static NewOp newArray(CodeType arrayType, Value length) {
6866         MethodRef constructorRef = MethodRef.constructor(arrayType, INT);
6867         return new_(constructorRef, length);
6868     }
6869 
6870     /**
6871      * Creates a field load operation to a non-static field.
6872      *
6873      * @param fieldRef   the field reference
6874      * @param receiver   the receiver value
6875      * @return the field load operation
6876      */
6877     public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef fieldRef, Value receiver) {
6878         return new FieldAccessOp.FieldLoadOp(fieldRef.type(), fieldRef, receiver);
6879     }
6880 
6881     /**
6882      * Creates a field load operation to a non-static field.
6883      *
6884      * @param resultType the result type of the operation
6885      * @param fieldRef   the field reference
6886      * @param receiver   the receiver value
6887      * @return the field load operation
6888      */
6889     public static FieldAccessOp.FieldLoadOp fieldLoad(CodeType resultType, FieldRef fieldRef, Value receiver) {
6890         return new FieldAccessOp.FieldLoadOp(resultType, fieldRef, receiver);
6891     }
6892 
6893     /**
6894      * Creates a field load operation to a static field.
6895      *
6896      * @param fieldRef the field reference
6897      * @return the field load operation
6898      */
6899     public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef fieldRef) {
6900         return new FieldAccessOp.FieldLoadOp(fieldRef.type(), fieldRef);
6901     }
6902 
6903     /**
6904      * Creates a field load operation to a static field.
6905      *
6906      * @param resultType the result type of the operation
6907      * @param fieldRef the field reference
6908      * @return the field load operation
6909      */
6910     public static FieldAccessOp.FieldLoadOp fieldLoad(CodeType resultType, FieldRef fieldRef) {
6911         return new FieldAccessOp.FieldLoadOp(resultType, fieldRef);
6912     }
6913 
6914     /**
6915      * Creates a field store operation to a non-static field.
6916      *
6917      * @param fieldRef   the field reference
6918      * @param receiver   the receiver value
6919      * @param v          the value to store
6920      * @return the field store operation
6921      */
6922     public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef fieldRef, Value receiver, Value v) {
6923         return new FieldAccessOp.FieldStoreOp(fieldRef, receiver, v);
6924     }
6925 
6926     /**
6927      * Creates a field load operation to a static field.
6928      *
6929      * @param fieldRef   the field reference
6930      * @param v          the value to store
6931      * @return the field store operation
6932      */
6933     public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef fieldRef, Value v) {
6934         return new FieldAccessOp.FieldStoreOp(fieldRef, v);
6935     }
6936 
6937     /**
6938      * Creates an array length operation.
6939      *
6940      * @param array the array value
6941      * @return the array length operation
6942      */
6943     public static ArrayLengthOp arrayLength(Value array) {
6944         return new ArrayLengthOp(array);
6945     }
6946 
6947     /**
6948      * Creates an array load operation.
6949      *
6950      * @param array the array value
6951      * @param index the index value
6952      * @return the array load operation
6953      */
6954     public static ArrayAccessOp.ArrayLoadOp arrayLoadOp(Value array, Value index) {
6955         return new ArrayAccessOp.ArrayLoadOp(array, index);
6956     }
6957 
6958     /**
6959      * Creates an array load operation.
6960      *
6961      * @param array the array value
6962      * @param index the index value
6963      * @param componentType the type of the array component
6964      * @return the array load operation
6965      */
6966     public static ArrayAccessOp.ArrayLoadOp arrayLoadOp(Value array, Value index, CodeType componentType) {
6967         return new ArrayAccessOp.ArrayLoadOp(array, index, componentType);
6968     }
6969 
6970     /**
6971      * Creates an array store operation.
6972      *
6973      * @param array the array value
6974      * @param index the index value
6975      * @param v     the value to store
6976      * @return the array store operation
6977      */
6978     public static ArrayAccessOp.ArrayStoreOp arrayStoreOp(Value array, Value index, Value v) {
6979         return new ArrayAccessOp.ArrayStoreOp(array, index, v);
6980     }
6981 
6982     /**
6983      * Creates an instanceof operation.
6984      *
6985      * @param t the type to test against
6986      * @param v the value to test
6987      * @return the instanceof operation
6988      */
6989     public static InstanceOfOp instanceOf(CodeType t, Value v) {
6990         return new InstanceOfOp(t, v);
6991     }
6992 
6993     /**
6994      * Creates a cast operation.
6995      *
6996      * @param resultType the result type of the operation
6997      * @param v          the value to cast
6998      * @return the cast operation
6999      */
7000     public static CastOp cast(CodeType resultType, Value v) {
7001         return new CastOp(resultType, resultType, v);
7002     }
7003 
7004     /**
7005      * Creates a cast operation.
7006      *
7007      * @param resultType the result type of the operation
7008      * @param t          the type to cast to
7009      * @param v          the value to cast
7010      * @return the cast operation
7011      */
7012     public static CastOp cast(CodeType resultType, JavaType t, Value v) {
7013         return new CastOp(resultType, t, v);
7014     }
7015 
7016     /**
7017      * Creates an add operation.
7018      *
7019      * @param lhs the first operand
7020      * @param rhs the second operand
7021      * @return the add operation
7022      */
7023     public static AddOp add(Value lhs, Value rhs) {
7024         return new AddOp(lhs, rhs);
7025     }
7026 
7027     /**
7028      * Creates a sub operation.
7029      *
7030      * @param lhs the first operand
7031      * @param rhs the second operand
7032      * @return the sub operation
7033      */
7034     public static SubOp sub(Value lhs, Value rhs) {
7035         return new SubOp(lhs, rhs);
7036     }
7037 
7038     /**
7039      * Creates a mul operation.
7040      *
7041      * @param lhs the first operand
7042      * @param rhs the second operand
7043      * @return the mul operation
7044      */
7045     public static MulOp mul(Value lhs, Value rhs) {
7046         return new MulOp(lhs, rhs);
7047     }
7048 
7049     /**
7050      * Creates a div operation.
7051      *
7052      * @param lhs the first operand
7053      * @param rhs the second operand
7054      * @return the div operation
7055      */
7056     public static DivOp div(Value lhs, Value rhs) {
7057         return new DivOp(lhs, rhs);
7058     }
7059 
7060     /**
7061      * Creates a mod operation.
7062      *
7063      * @param lhs the first operand
7064      * @param rhs the second operand
7065      * @return the mod operation
7066      */
7067     public static ModOp mod(Value lhs, Value rhs) {
7068         return new ModOp(lhs, rhs);
7069     }
7070 
7071     /**
7072      * Creates a bitwise/logical or operation.
7073      *
7074      * @param lhs the first operand
7075      * @param rhs the second operand
7076      * @return the or operation
7077      */
7078     public static OrOp or(Value lhs, Value rhs) {
7079         return new OrOp(lhs, rhs);
7080     }
7081 
7082     /**
7083      * Creates a bitwise/logical and operation.
7084      *
7085      * @param lhs the first operand
7086      * @param rhs the second operand
7087      * @return the and operation
7088      */
7089     public static AndOp and(Value lhs, Value rhs) {
7090         return new AndOp(lhs, rhs);
7091     }
7092 
7093     /**
7094      * Creates a bitwise/logical xor operation.
7095      *
7096      * @param lhs the first operand
7097      * @param rhs the second operand
7098      * @return the xor operation
7099      */
7100     public static XorOp xor(Value lhs, Value rhs) {
7101         return new XorOp(lhs, rhs);
7102     }
7103 
7104     /**
7105      * Creates a left shift operation.
7106      *
7107      * @param lhs the first operand
7108      * @param rhs the second operand
7109      * @return the left shift operation
7110      */
7111     public static LshlOp lshl(Value lhs, Value rhs) {
7112         return new LshlOp(lhs, rhs);
7113     }
7114 
7115     /**
7116      * Creates a right shift operation.
7117      *
7118      * @param lhs the first operand
7119      * @param rhs the second operand
7120      * @return the right shift operation
7121      */
7122     public static AshrOp ashr(Value lhs, Value rhs) {
7123         return new AshrOp(lhs, rhs);
7124     }
7125 
7126     /**
7127      * Creates an unsigned right shift operation.
7128      *
7129      * @param lhs the first operand
7130      * @param rhs the second operand
7131      * @return the unsigned right shift operation
7132      */
7133     public static LshrOp lshr(Value lhs, Value rhs) {
7134         return new LshrOp(lhs, rhs);
7135     }
7136 
7137     /**
7138      * Creates a neg operation.
7139      *
7140      * @param v the operand
7141      * @return the neg operation
7142      */
7143     public static NegOp neg(Value v) {
7144         return new NegOp(v);
7145     }
7146 
7147     /**
7148      * Creates a bitwise complement operation.
7149      *
7150      * @param v the operand
7151      * @return the bitwise complement operation
7152      */
7153     public static ComplOp compl(Value v) {
7154         return new ComplOp(v);
7155     }
7156 
7157     /**
7158      * Creates a not operation.
7159      *
7160      * @param v the operand
7161      * @return the not operation
7162      */
7163     public static NotOp not(Value v) {
7164         return new NotOp(v);
7165     }
7166 
7167     /**
7168      * Creates an equals comparison operation.
7169      *
7170      * @param lhs the first operand
7171      * @param rhs the second operand
7172      * @return the equals comparison operation
7173      */
7174     public static EqOp eq(Value lhs, Value rhs) {
7175         return new EqOp(lhs, rhs);
7176     }
7177 
7178     /**
7179      * Creates a not equals comparison operation.
7180      *
7181      * @param lhs the first operand
7182      * @param rhs the second operand
7183      * @return the not equals comparison operation
7184      */
7185     public static NeqOp neq(Value lhs, Value rhs) {
7186         return new NeqOp(lhs, rhs);
7187     }
7188 
7189     /**
7190      * Creates a greater than comparison operation.
7191      *
7192      * @param lhs the first operand
7193      * @param rhs the second operand
7194      * @return the greater than comparison operation
7195      */
7196     public static GtOp gt(Value lhs, Value rhs) {
7197         return new GtOp(lhs, rhs);
7198     }
7199 
7200     /**
7201      * Creates a greater than or equals to comparison operation.
7202      *
7203      * @param lhs the first operand
7204      * @param rhs the second operand
7205      * @return the greater than or equals to comparison operation
7206      */
7207     public static GeOp ge(Value lhs, Value rhs) {
7208         return new GeOp(lhs, rhs);
7209     }
7210 
7211     /**
7212      * Creates a less than comparison operation.
7213      *
7214      * @param lhs the first operand
7215      * @param rhs the second operand
7216      * @return the less than comparison operation
7217      */
7218     public static LtOp lt(Value lhs, Value rhs) {
7219         return new LtOp(lhs, rhs);
7220     }
7221 
7222     /**
7223      * Creates a less than or equals to comparison operation.
7224      *
7225      * @param lhs the first operand
7226      * @param rhs the second operand
7227      * @return the less than or equals to comparison operation
7228      */
7229     public static LeOp le(Value lhs, Value rhs) {
7230         return new LeOp(lhs, rhs);
7231     }
7232 
7233     /**
7234      * Creates a string concatenation operation.
7235      *
7236      * @param lhs the first operand
7237      * @param rhs the second operand
7238      * @return the string concatenation operation
7239      */
7240     public static ConcatOp concat(Value lhs, Value rhs) {
7241         return new ConcatOp(lhs, rhs);
7242     }
7243 
7244     /**
7245      * Creates a continue operation.
7246      *
7247      * @return the continue operation
7248      */
7249     public static ContinueOp continue_() {
7250         return continue_(null);
7251     }
7252 
7253     /**
7254      * Creates a continue operation.
7255      *
7256      * @param label the value associated with where to continue from
7257      * @return the continue operation
7258      */
7259     public static ContinueOp continue_(Value label) {
7260         return new ContinueOp(label);
7261     }
7262 
7263     /**
7264      * Creates a break operation.
7265      *
7266      * @return the break operation
7267      */
7268     public static BreakOp break_() {
7269         return break_(null);
7270     }
7271 
7272     /**
7273      * Creates a break operation.
7274      *
7275      * @param label the label identifier
7276      * @return the break operation
7277      */
7278     public static BreakOp break_(Value label) {
7279         return new BreakOp(label);
7280     }
7281 
7282     /**
7283      * Creates a yield operation.
7284      *
7285      * @param operand the value to yield
7286      * @return the yield operation
7287      */
7288     public static YieldOp java_yield(Value operand) {
7289         return new YieldOp(operand);
7290     }
7291 
7292     /**
7293      * Creates a block operation.
7294      *
7295      * @param body the statements body builder
7296      * @return the block operation
7297      */
7298     public static BlockOp block(Body.Builder body) {
7299         return new BlockOp(body);
7300     }
7301 
7302     /**
7303      * Creates a synchronized operation.
7304      *
7305      * @param expr the expression body builder
7306      * @param blockBody the block body builder
7307      * @return the synchronized operation
7308      */
7309     public static SynchronizedOp synchronized_(Body.Builder expr, Body.Builder blockBody) {
7310         return new SynchronizedOp(expr, blockBody);
7311     }
7312 
7313     /**
7314      * Creates a labeled operation.
7315      *
7316      * @param body the labeled body builder
7317      * @return the labeled operation
7318      */
7319     public static LabeledOp labeled(Body.Builder body) {
7320         return new LabeledOp(body);
7321     }
7322 
7323     /**
7324      * Creates an if operation builder.
7325      *
7326      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7327      *                              connected, or {@code null} if they are isolated
7328      * @return the if operation builder
7329      */
7330     public static IfOp.IfBuilder if_(Body.Builder connectedAncestorBody) {
7331         return new IfOp.IfBuilder(connectedAncestorBody);
7332     }
7333 
7334     // Pairs of
7335     //   predicate ()boolean, body ()void
7336     // And one optional body ()void at the end
7337 
7338     /**
7339      * Creates an if operation.
7340      *
7341      * @param bodies the body builders for the predicate and action bodies
7342      * @return the if operation
7343      */
7344     public static IfOp if_(List<Body.Builder> bodies) {
7345         return new IfOp(bodies);
7346     }
7347 
7348     /**
7349      * Creates a switch expression operation.
7350      * <p>
7351      * The result type of the operation will be derived from the yield type of the second body
7352      *
7353      * @param target the switch target value
7354      * @param bodies the body builders for the predicate and action bodies
7355      * @return the switch expression operation
7356      */
7357     public static SwitchExpressionOp switchExpression(Value target, List<Body.Builder> bodies) {
7358         return new SwitchExpressionOp(null, target, bodies);
7359     }
7360 
7361     /**
7362      * Creates a switch expression operation.
7363      *
7364      * @param resultType the result type of the expression
7365      * @param target     the switch target value
7366      * @param bodies     the body builders for the predicate and action bodies
7367      * @return the switch expression operation
7368      */
7369     public static SwitchExpressionOp switchExpression(CodeType resultType, Value target,
7370                                                       List<Body.Builder> bodies) {
7371         Objects.requireNonNull(resultType);
7372         return new SwitchExpressionOp(resultType, target, bodies);
7373     }
7374 
7375     /**
7376      * Creates a switch statement operation.
7377      * <p>
7378      * Case bodies are provided as pairs of bodies, where the first body of each pair is the predicate body and the
7379      * second is the corresponding action body.
7380      *
7381      * @param target the switch target value
7382      * @param bodies the body builders for the predicate and action bodies
7383      * @return the switch statement operation
7384      */
7385     public static SwitchStatementOp switchStatement(Value target, List<Body.Builder> bodies) {
7386         return new SwitchStatementOp(target, bodies);
7387     }
7388 
7389     /**
7390      * Creates a switch fallthrough operation.
7391      *
7392      * @return the switch fallthrough operation
7393      */
7394     public static SwitchFallthroughOp switchFallthroughOp() {
7395         return new SwitchFallthroughOp();
7396     }
7397 
7398     /**
7399      * Creates a for operation builder.
7400      *
7401      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7402      *                              connected, or {@code null} if they are isolated
7403      * @param initTypes             the types of initialized variables
7404      * @return the for operation builder
7405      */
7406     public static ForOp.InitBuilder for_(Body.Builder connectedAncestorBody, CodeType... initTypes) {
7407         return for_(connectedAncestorBody, List.of(initTypes));
7408     }
7409 
7410     /**
7411      * Creates a for operation builder.
7412      *
7413      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7414      *                              connected, or {@code null} if they are isolated
7415      * @param initTypes             the types of initialized variables
7416      * @return the for operation builder
7417      */
7418     public static ForOp.InitBuilder for_(Body.Builder connectedAncestorBody, List<? extends CodeType> initTypes) {
7419         return new ForOp.InitBuilder(connectedAncestorBody, initTypes);
7420     }
7421 
7422 
7423     /**
7424      * Creates a for operation.
7425      *
7426      * @param initBody   the initialization body builder
7427      * @param condBody   the predicate body builder
7428      * @param updateBody the update body builder
7429      * @param loopBody   the loop body builder
7430      * @return the for operation
7431      */
7432     // initBody ()Tuple<Var<T1>, Var<T2>, ..., Var<TN>>, or initBody ()Var<T1>, or initBody ()void
7433     // condBody (Var<T1>, Var<T2>, ..., Var<TN>)boolean
7434     // updateBody (Var<T1>, Var<T2>, ..., Var<TN>)void
7435     // loopBody (Var<T1>, Var<T2>, ..., Var<TN>)void
7436     public static ForOp for_(Body.Builder initBody,
7437                              Body.Builder condBody,
7438                              Body.Builder updateBody,
7439                              Body.Builder loopBody) {
7440         return new ForOp(initBody, condBody, updateBody, loopBody);
7441     }
7442 
7443     /**
7444      * Creates an enhanced for operation builder.
7445      *
7446      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7447      *                              connected, or {@code null} if they are isolated
7448      * @param iterableType          the iterable type
7449      * @param elementType           the element type
7450      * @return the enhanced for operation builder
7451      */
7452     public static EnhancedForOp.ExpressionBuilder enhancedFor(Body.Builder connectedAncestorBody,
7453                                                               CodeType iterableType, CodeType elementType) {
7454         return new EnhancedForOp.ExpressionBuilder(connectedAncestorBody, iterableType, elementType);
7455     }
7456 
7457     /**
7458      * Creates an enhanced for operation.
7459      *
7460      * @param exprBody the expression body builder
7461      * @param initBody the initialization body builder
7462      * @param loopBody the loop body builder
7463      * @return the enhanced for operation
7464      */
7465     // expression ()I<E>
7466     // init (E )Var<T>
7467     // body (Var<T> )void
7468     public static EnhancedForOp enhancedFor(Body.Builder exprBody,
7469                                             Body.Builder initBody,
7470                                             Body.Builder loopBody) {
7471         return new EnhancedForOp(exprBody, initBody, loopBody);
7472     }
7473 
7474     /**
7475      * Creates a while operation builder.
7476      *
7477      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7478      *                              connected, or {@code null} if they are isolated
7479      * @return the while operation builder
7480      */
7481     public static WhileOp.PredicateBuilder while_(Body.Builder connectedAncestorBody) {
7482         return new WhileOp.PredicateBuilder(connectedAncestorBody);
7483     }
7484 
7485     /**
7486      * Creates a while operation.
7487      *
7488      * @param predicateBody the predicate body builder
7489      * @param loopBody      the loop body builder
7490      * @return the while operation
7491      */
7492     // predicateBody, ()boolean, may be null for predicateBody returning true
7493     // loopBody, ()void
7494     public static WhileOp while_(Body.Builder predicateBody, Body.Builder loopBody) {
7495         return new WhileOp(predicateBody, loopBody);
7496     }
7497 
7498     /**
7499      * Creates a do operation builder.
7500      *
7501      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7502      *                              connected, or {@code null} if they are isolated
7503      * @return the do operation builder
7504      */
7505     public static DoWhileOp.BodyBuilder doWhile(Body.Builder connectedAncestorBody) {
7506         return new DoWhileOp.BodyBuilder(connectedAncestorBody);
7507     }
7508 
7509     /**
7510      * Creates a do operation.
7511      *
7512      * @param loopBody      the loop body builder
7513      * @param predicateBody the predicate body builder
7514      * @return the do operation
7515      */
7516     public static DoWhileOp doWhile(Body.Builder loopBody, Body.Builder predicateBody) {
7517         return new DoWhileOp(loopBody, predicateBody);
7518     }
7519 
7520     /**
7521      * Creates a conditional-and operation builder.
7522      *
7523      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7524      *                              connected, or {@code null} if they are isolated
7525      * @param lhs                   a consumer that populates the first predicate body
7526      * @param rhs                   a consumer that populates the second predicate body
7527      * @return the conditional-and operation builder
7528      */
7529     public static ConditionalAndOp.Builder conditionalAnd(Body.Builder connectedAncestorBody,
7530                                                           Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
7531         return new ConditionalAndOp.Builder(connectedAncestorBody, lhs, rhs);
7532     }
7533 
7534     /**
7535      * Creates a conditional-or operation builder.
7536      *
7537      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7538      *                              connected, or {@code null} if they are isolated
7539      * @param lhs                   a consumer that populates the first predicate body
7540      * @param rhs                   a consumer that populates the second predicate body
7541      * @return the conditional-or operation builder
7542      */
7543     public static ConditionalOrOp.Builder conditionalOr(Body.Builder connectedAncestorBody,
7544                                                         Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
7545         return new ConditionalOrOp.Builder(connectedAncestorBody, lhs, rhs);
7546     }
7547 
7548     /**
7549      * Creates a conditional-and operation
7550      *
7551      * @param bodies the body builders for the predicate bodies
7552      * @return the conditional-and operation
7553      */
7554     // predicates, ()boolean
7555     public static ConditionalAndOp conditionalAnd(List<Body.Builder> bodies) {
7556         return new ConditionalAndOp(bodies);
7557     }
7558 
7559     /**
7560      * Creates a conditional-or operation
7561      *
7562      * @param bodies the body builders for the predicate bodies
7563      * @return the conditional-or operation
7564      */
7565     // predicates, ()boolean
7566     public static ConditionalOrOp conditionalOr(List<Body.Builder> bodies) {
7567         return new ConditionalOrOp(bodies);
7568     }
7569 
7570     /**
7571      * Creates a conditional operation
7572      *
7573      * @param expressionType the result type of the expression
7574      * @param bodies         the body builders for the predicate, true, and false bodies
7575      * @return the conditional operation
7576      */
7577     public static ConditionalExpressionOp conditionalExpression(CodeType expressionType,
7578                                                                 List<Body.Builder> bodies) {
7579         Objects.requireNonNull(expressionType);
7580         return new ConditionalExpressionOp(expressionType, bodies);
7581     }
7582 
7583     /**
7584      * Creates a conditional operation
7585      * <p>
7586      * The result type of the operation will be derived from the yield type of the true body.
7587      *
7588      * @param bodies the body builders for the predicate, true, and false bodies
7589      * @return the conditional operation
7590      */
7591     public static ConditionalExpressionOp conditionalExpression(List<Body.Builder> bodies) {
7592         return new ConditionalExpressionOp(null, bodies);
7593     }
7594 
7595     /**
7596      * Creates try operation builder.
7597      *
7598      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7599      *                              connected, or {@code null} if they are isolated
7600      * @param c                     a consumer that populates the try body
7601      * @return the try operation builder
7602      */
7603     public static TryOp.CatchBuilder try_(Body.Builder connectedAncestorBody, Consumer<Block.Builder> c) {
7604         Body.Builder _try = Body.Builder.of(connectedAncestorBody, CoreType.FUNCTION_TYPE_VOID);
7605         c.accept(_try.entryBlock());
7606         return new TryOp.CatchBuilder(connectedAncestorBody, List.of(), _try);
7607     }
7608 
7609     /**
7610      * Creates try-with-resources operation builder.
7611      *
7612      * @param connectedAncestorBody the nearest ancestor body builder to which body builders for this operation are
7613      *                              connected, or {@code null} if they are isolated
7614      * @return the try-with-resources operation builder
7615      */
7616     public static TryOp.BodyBuilder tryWithResources(Body.Builder connectedAncestorBody) {
7617         return new TryOp.BodyBuilder(connectedAncestorBody);
7618     }
7619 
7620     // resources: ()T1, (T1)T2, ..., (T1, T2, ..., T{N-1})TN, or empty
7621     // Ti is Ri for a resource expression, or Var<Ri> for a resource declaration
7622     // try (T1, T2, ..., TN)void, or try ()void
7623     // catch (E )void, where E <: Throwable
7624     // finally ()void, or null
7625 
7626     /**
7627      * Creates a try or try-with-resources operation.
7628      *
7629      * @param resourceBodies the resources body builders
7630      * @param body           the try body builder
7631      * @param catchBodies    the catch body builders
7632      * @param finallyBody    the finalizer body builder, may be {@code null}
7633      * @return the try or try-with-resources operation
7634      */
7635     public static TryOp try_(List<Body.Builder> resourceBodies,
7636                              Body.Builder body,
7637                              List<Body.Builder> catchBodies,
7638                              Body.Builder finallyBody) {
7639         return new TryOp(resourceBodies, body, catchBodies, finallyBody);
7640     }
7641 
7642     //
7643     // Patterns
7644 
7645     /**
7646      * Creates a pattern match operation.
7647      *
7648      * @param target      the target value
7649      * @param patternBody the pattern body builder
7650      * @param matchBody   the match body builder
7651      * @return the pattern match operation
7652      */
7653     public static PatternOps.MatchOp match(Value target,
7654                                            Body.Builder patternBody, Body.Builder matchBody) {
7655         return new PatternOps.MatchOp(target, patternBody, matchBody);
7656     }
7657 
7658     /**
7659      * Creates a pattern binding operation.
7660      *
7661      * @param type        the type of value to be bound
7662      * @param bindingName the binding name
7663      * @return the pattern binding operation
7664      */
7665     public static PatternOps.TypePatternOp typePattern(CodeType type, String bindingName) {
7666         return new PatternOps.TypePatternOp(type, bindingName);
7667     }
7668 
7669     /**
7670      * Creates a record pattern operation.
7671      *
7672      * @param recordRef the record reference
7673      * @param nestedPatterns   the nested pattern values
7674      * @return the record pattern operation
7675      */
7676     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordRef, Value... nestedPatterns) {
7677         return recordPattern(recordRef, List.of(nestedPatterns));
7678     }
7679 
7680     /**
7681      * Creates a record pattern operation.
7682      *
7683      * @param recordRef the record reference
7684      * @param nestedPatterns   the nested pattern values
7685      * @return the record pattern operation
7686      */
7687     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordRef, List<Value> nestedPatterns) {
7688         return new PatternOps.RecordPatternOp(recordRef, nestedPatterns);
7689     }
7690 
7691     /**
7692      * Creates a match-all pattern operation.
7693      *
7694      * @return a match-all pattern
7695      */
7696     public static PatternOps.MatchAllPatternOp matchAllPattern() {
7697         return new PatternOps.MatchAllPatternOp();
7698     }
7699 }