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