Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8284529

Compiler implementation for Record Patterns

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Approved
    • Icon: P3 P3
    • 19
    • tools
    • None
    • source
    • minimal
    • Java API, Language construct
    • SE

      Summary

      Enhance the Java programming language with record patterns. Record patterns significantly enhance the expressiveness and utility of pattern matching, allowing instances of record classes to be smoothly deconstructed. Notably, for the first time in Java, record patterns allow patterns to be nested.

      Problem

      Record classes (JEP 395) are transparent carriers for data. Code that receives an instance of a record class will typically extract the data, known as the components. For example, we can use a type pattern (JEP 394) to test whether a value is an instance of the record class Point and, if so, extract the x and y components from the instance:

      record Point(int x, int y) {}
      
      void printSum(Object o) {
          if (o instanceof Point p) {
              int x = p.x();
              int y = p.y();
              System.out.println(x+y);
          }
      }

      We enhance pattern matching by supporting record patterns that capture the test-and-extract behavior above. For example, the record pattern Point(int x, int y) first tests whether a value is an instance of Point, and if so, extracts the x and y components from the instance by invoking their accessor methods. The code would be improved, as follows:

      record Point(int x, int y) {}
      
      void printSum(Object o) {
          if (o instanceof Point(int x, int y)) {
              System.out.println(x+y);
          }
      }

      The real power of record patterns becomes evident when a record pattern is nested inside another record pattern. A type pattern can also be nested inside a record pattern. For example, in this code:

      if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
           System.out.println(c);
      }

      both the record pattern

      ColoredPoint(Point p, Color c)

      and the type pattern

      ColoredPoint lr

      are nested inside the record pattern

      Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)

      After a value r matches, the pattern variables p, c, and lr are initialized with the result of invoking the corresponding accessor method.

      Besides enhancing pattern matching in instanceof to support record patterns, we also add support for record patterns in switch expressions and switch statements.

      Solution

      The Java language is enhanced as follows:

      • Allow a record pattern as the operand of the instanceof operator.
      • Allow a record pattern as a case label of a switch expression or a switch statement.
      • Allow a record pattern to contain type patterns, such as the int x and int y in Point(int x, int y).
      • Allow a record pattern to contain record patterns, such as the Point(int x, int y) in ColoredPoint(Point(int x, int y), Color c).
      • Allow a record pattern to introduce an identifier for the record instance, such as the p in Point(int x, int y) p.
      • Inside a record pattern, allow a type pattern to use var. (This means that type patterns are strictly more powerful inside record patterns than outside.)
      • Allow parenthesized patterns.

      Pattern matching, which previously matched type patterns only, is enhanced to match record patterns: (this applies uniformly to instanceof and switch)

      • Allow pattern matching to match a non-null value with a record pattern. A value that is null (whether the first operand of instanceof or the selector expression of switch) does not match any record pattern.
      • Allow pattern matching to use the accessor method of a record component in the record component pattern list to access the value and further pattern match on the corresponding component.
      • Allow pattern matching to introduce the pattern variables of a matched record pattern in a well-scoped manner.
      • A new unchecked exception java.lang.MatchException is thrown in problematic scenarios involving record patterns:
        • Separate compilation anomalies involving record pattern type hierarchies.
        • Null targets and nested record patterns, e.g., R(S(String s)) will not match new R(null) or new R(S(null)).
        • When an accessor method throws an exception during pattern matching (pattern matching may cause methods such as record component accessors to be implicitly invoked in order to extract pattern bindings), pattern matching completes abruptly with MatchException. The original exception will be set as the cause of the MatchException. No suppressed exceptions will be recorded.

      The rules for switch expressions and switch statements are enhanced to support pattern matching of record patterns: (other rule changes are described in the CSR for the third preview of Pattern Matching for switch)

      1. Record patterns as labels — The rules for compatibility between switch labels and the type of the selector expression are extended, so that a record pattern with type R and component pattern list L -- case R(L) ... -- can be applicable at the type of the selector expression. There are no changes to the types allowed for the selector expression of a switch expression/statement, which remain as: integral primitive types except long, the corresponding boxed types, String, enum types, and any other reference type.

      2. Dominance of pattern labelsDominance is a relation between two patterns, checked at compile time. An error occurs if a switch label in a switch block is dominated by an earlier switch label. For example, it is an error if the switch label case 42 appears after the switch label case Integer i, because the type pattern Integer i dominates the constant 42. Dominance is extended to support record patterns. Notably, a switch label with a record pattern R(...) dominates a switch label with a record pattern S(...) if the erasure of S is a subtype of the erasure of R and every pattern component of R dominates every corresponding pattern component of S.

      3. Exhaustiveness of switch expressions — A switch expression requires that all possible values of the selector expression are handled in the switch block; in other words, it is exhaustive. This maintains the property that successful evaluation of a switch expression will always yield a value. Record patterns affect the exhaustiveness check of a switch expression in two ways:

        • In the first and second previews of Pattern Matching for switch (JEP 406, JEP 420), a switch block can be considered exhaustive if it contains a pattern that is unconditional at the selector type T, i.e., the pattern matches all values of T. Record patterns are not unconditional at any type because the null reference does not match any record pattern.

        • A new rule extends the exhaustiveness calculation over record patterns as a whole, including their component list, while respecting how record classes participate in sealed hierarchies. This means that even though a record pattern is not unconditional, if it is part of a sealed hierarchy we can statically see whether the pattern matches all potential values.

      4. Scope of pattern variable declarations — Record patterns introduce pattern variables, and thanks to nesting, their scope can be complex. New scoping rules apply to instanceof, switch expressions, and switch statements.

      5. Dealing with null — The null value does not match any record pattern in switch, and there is no automatic NPE from the switch. This "silent treatment" for null aligns record patterns in switch with type patterns in switch, and with all patterns in instanceof. Given a nested pattern R(P), and assuming we match the record class R, we assume the following equivalence: x instanceof R(P) === x instanceof R(var a) && a instanceof P. That way we ensure that no pattern (type or record) ever actually encounters a null.

      Specification

      The updated JLS draft for pattern matching for switch is attached as jep427+405-20220517.zip. Please note the JLS draft also includes changes from prerequisite CSR JDK-8284528.

      The proposed API enhancements are attached as specdiff.00.zip.

      Please note the API changes exclude changes from the prerequisite CSR JDK-8284528.

      The changes to the specification and API are a subject of change until the CSR is finalized.

        1. resolvinginstanceof.diff
          2 kB
          Gavin Bierman

            abimpoudis Angelos Bimpoudis
            gbierman Gavin Bierman
            Alex Buckley
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: