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