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