JDK-8284529 : Compiler implementation for Record Patterns
  • Type: CSR
  • Component: tools
  • Sub-Component: javac
  • Priority: P3
  • Status: Finalized
  • Resolution: Unresolved
  • Fix Versions: 19
  • Submitted: 2022-04-07
  • Updated: 2022-05-12
Related Reports
CSR :  
Description
## 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](https://openjdk.java.net/jeps/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](https://openjdk.java.net/jeps/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](https://bugs.openjdk.java.net/browse/JDK-8284528))

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 labels_ — _Dominance_ 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](https://bugs.openjdk.java.net/browse/JDK-8213076), [JEP 420](https://bugs.openjdk.java.net/browse/JDK-8273326)), 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-20220510.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.

Comments
[~darcy]Added new text about MatchException recording the cause. New JLS attached
10-05-2022

MatchException details updated!
05-05-2022

JLS changes attached!
02-05-2022

Moving to Provisional, not Approved. Note that the JLS changes referenced are neither linked to nor attached. A stand-alone version of those changes will be necessary before the CSR is Finalized. For MatchException, please add some discussion of setting the cause of suppressed exceptions in the case of say, record component accessors.
02-05-2022