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