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