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