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