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