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