Pattern Matching for switch (Fourth Preview) and Record Patterns (Second Preview)

Changes to the Java® Language Specification • Version 20.0.2+9-78

This document describes changes to the Java Language Specification to support Pattern Matching for switch and Record Patterns, which are both preview features of Java SE 20. See JEP 433 and JEP 432 respectively for overviews of the features.

Changes are described with respect to existing sections of the JLS. New text is indicated like this and deleted text is indicated like this. Explanation and discussion, as needed, is set aside in grey boxes.

Changelog:

2022-11-15:

2022-10-28: Added details of inference of type arguments for record patterns.

2022-10-18: First draft released. Main changes from the third preview specification, in addition to various bug-fixes, are:

Chapter 3: Lexical Structure

3.9 Keywords

51 character sequences, formed from ASCII characters, are reserved for use as keywords and cannot be used as identifiers (3.8). Another 16 character sequences, also formed from ASCII characters, may be interpreted as keywords or as other tokens, depending on the context in which they appear.

Keyword:
ReservedKeyword
ContextualKeyword
ReservedKeyword:
(one of)
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
_ (underscore)
ContextualKeyword:
(one of)
exports permits to with
module provides transitive yield
non-sealed record uses
open requires var
opens sealed when

The keywords const and goto are reserved, even though they are not currently used. This may allow a Java compiler to produce better error messages if these C++ keywords incorrectly appear in programs.

The keyword strictfp is obsolete and should not be used in new code.

The keyword _ (underscore) is reserved for possible future use in parameter declarations.

true and false are not keywords, but rather boolean literals (3.10.3).

null is not a keyword, but rather the null literal (3.10.8).

During the reduction of input characters to input elements (3.5), a sequence of input characters that notionally matches a contextual keyword is reduced to a contextual keyword if and only if both of the following conditions hold:

  1. The sequence is recognized as a terminal specified in a suitable context of the syntactic grammar (2.3), as follows:

    • For module and open, when recognized as a terminal in a ModuleDeclaration (7.7).

    • For exports, opens, provides, requires, to, uses, and with, when recognized as a terminal in a ModuleDirective.

    • For transitive, when recognized as a terminal in a RequiresModifier.

      For example, recognizing the sequence requires transitive ; does not make use of RequiresModifier, so the term transitive is reduced here to an identifier and not a contextual keyword.

    • For var, when recognized as a terminal in a LocalVariableType (14.4) or a LambdaParameterType (15.27.1).

      In other contexts, attempting to use var as an identifier will cause an error, because var is not a TypeIdentifier (3.8).

    • For yield, when recognized as a terminal in a YieldStatement (14.21).

      In other contexts, attempting to use the yield as an identifier will cause an error, because yield is neither a TypeIdentifier nor a UnqualifiedMethodIdentifier.

    • For record, when recognized as a terminal in a RecordDeclaration (8.10).

    • For non-sealed, permits, and sealed, when recognized as a terminal in a NormalClassDeclaration (8.1) or a NormalInterfaceDeclaration (9.1).

    • For when, when recognized as a terminal in a Guard (14.11.1).

  2. The sequence is not immediately preceded or immediately followed by an input character that matches JavaLetterOrDigit.

In general, accidentally omitting white space in source code will cause a sequence of input characters to be tokenized as an identifier, due to the "longest possible translation" rule (3.2). For example, the sequence of twelve input characters p u b l i c s t a t i c is always tokenized as the identifier publicstatic, rather than as the reserved keywords public and static. If two tokens are intended, they must be separated by white space or a comment.

The rule above works in tandem with the "longest possible translation" rule to produce an intuitive result in contexts where contextual keywords may appear. For example, the sequence of eleven input characters v a r f i l e n a m e is usually tokenized as the identifier varfilename, but in a local variable declaration, the first three input characters are tentatively recognized as the contextual keyword var by the first condition of the rule above. However, it would be confusing to overlook the lack of white space in the sequence by recognizing the next eight input characters as the identifier filename. (This would mean that the sequence undergoes different tokenization in different contexts: an identifier in most contexts, but a contextual keyword and an identifier in local variable declarations.) Accordingly, the second condition prevents recognition of the contextual keyword var on the grounds that the immediately following input character f is a JavaLetterOrDigit. The sequence v a r f i l e n a m e is therefore tokenized as the identifier varfilename in a local variable declaration.

As another example of the careful recognition of contextual keywords, consider the sequence of 15 input characters n o n - s e a l e d c l a s s. This sequence is usually translated to three tokens - the identifier non, the operator -, and the identifier sealedclass - but in a normal class declaration, where the first condition holds, the first ten input characters are tentatively recognized as the contextual keyword non-sealed. To avoid translating the sequence to two keyword tokens (non-sealed and class) rather than three non-keyword tokens, and to avoid rewarding the programmer for omitting white space before class, the second condition prevents recognition of the contextual keyword. The sequence n o n - s e a l e d c l a s s is therefore tokenized as three tokens in a class declaration.

In the rule above, the first condition depends on details of the syntactic grammar, but a compiler for the Java programming language can implement the rule without fully parsing the input program. For example, a heuristic could be used to track the contextual state of the tokenizer, as long as the heuristic guarantees that valid uses of contextual keywords are tokenized as keywords, and valid uses of identifiers are tokenized as identifiers. Alternatively, a compiler could always tokenize a contextual keyword as an identifier, leaving it to a later phase to recognize special uses of these identifiers.

Chapter 5: Conversions and Contexts

5.5 Casting Contexts

Casting contexts allow the operand of a cast expression (15.16) to be converted to the type explicitly named by the cast operator. Compared to assignment contexts and invocation contexts, casting contexts allow the use of more of the conversions defined in 5.1, and allow more combinations of those conversions.

If the expression is of a primitive type, then a casting context allows the use of one of the following:

If the expression is of a reference type, then a casting context allows the use of one of the following:

If the expression has the null type, then the expression may be cast to any reference type.

If a casting context makes use of a narrowing reference conversion that is checked or partially unchecked (5.1.6.2, 5.1.6.3), then a run time check will be performed on the class of the expression's value, possibly causing a ClassCastException. Otherwise, no run time check is performed.

If an expression can be converted to a reference type by a casting conversion other than a narrowing reference conversion which is unchecked, we say the expression (or its value) is downcast compatible with the reference type.

If an expression of reference type S is downcast compatible with another reference type T, we say that the type S is downcast convertible to type T.

The following tables enumerate which conversions are used in certain casting contexts. Each conversion is signified by a symbol:

In the tables, a comma between symbols indicates that a casting context uses one conversion followed by another. The type Object means any reference type other than the eight wrapper classes Boolean, Byte, Short, Character, Integer, Long, Float, Double.

Table 5.5-A. Casting to primitive types

To
From
byte short char int long float double boolean
byte ω ωη ω ω ω ω -
short η η ω ω ω ω -
char η η ω ω ω ω -
int η η η ω ω ω -
long η η η η ω ω -
float η η η η η ω -
double η η η η η η -
boolean - - - - - - -
Byte ,ω - ,ω ,ω ,ω ,ω -
Short - - ,ω ,ω ,ω ,ω -
Character - - ,ω ,ω ,ω ,ω -
Integer - - - ,ω ,ω ,ω -
Long - - - - ,ω ,ω -
Float - - - - - ,ω -
Double - - - - - - -
Boolean - - - - - - -
Object , , , , , , , ,

Table 5.5-B. Casting to reference types

To
From
Byte Short Character Integer Long Float Double Boolean Object
byte - - - - - - - ,
short - - - - - - - ,
char - - - - - - - ,
int - - - - - - - ,
long - - - - - - - ,
float - - - - - - - ,
double - - - - - - - ,
boolean - - - - - - - ,
Byte - - - - - - -
Short - - - - - - -
Character - - - - - - -
Integer - - - - - - -
Long - - - - - - -
Float - - - - - - -
Double - - - - - - -
Boolean - - - - - - -
Object

Example 5.5-1. Casting for Reference Types

class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}

class Test {
    public static void main(String[] args) {
        Point p = new Point();
        ColoredPoint cp = new ColoredPoint();
        Colorable c;
        // The following may cause errors at run time because
        // we cannot be sure they will succeed; this possibility
        // is suggested by the casts:
        cp = (ColoredPoint)p;  // p might not reference an
                               // object which is a ColoredPoint
                               // or a subclass of ColoredPoint
        c = (Colorable)p;      // p might not be Colorable
        // The following are incorrect at compile time because
        // they can never succeed as explained in the text:
        Long l = (Long)p;            // compile-time error #1
        EndPoint e = new EndPoint();
        c = (Colorable)e;            // compile-time error #2
    }
}

Here, the first compile-time error occurs because the class types Long and Point are unrelated (that is, they are not the same, and neither is a subclass of the other), so a cast between them will always fail.

The second compile-time error occurs because a variable of type EndPoint can never reference a value that implements the interface Colorable. This is because EndPoint is a final type, and a variable of a final type always holds a value of the same run-time type as its compile-time type. Therefore, the run-time type of variable e must be exactly the type EndPoint, and type EndPoint does not implement Colorable.

Example 5.5-2. Casting for Array Types

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
    public String toString() { return "("+x+","+y+")"; }
}
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    ColoredPoint(int x, int y, int color) {
        super(x, y); setColor(color);
    }
    public void setColor(int color) { this.color = color; }
    public String toString() {
        return super.toString() + "@" + color;
    }
}

class Test {
    public static void main(String[] args) {
        Point[] pa = new ColoredPoint[4];
        pa[0] = new ColoredPoint(2, 2, 12);
        pa[1] = new ColoredPoint(4, 5, 24);
        ColoredPoint[] cpa = (ColoredPoint[])pa;
        System.out.print("cpa: {");
        for (int i = 0; i < cpa.length; i++)
            System.out.print((i == 0 ? " " : ", ") + cpa[i]);
        System.out.println(" }");
    }
}

This program compiles without errors and produces the output:

cpa: { (2,2)@12, (4,5)@24, null, null }

Example 5.5-3. Casting Incompatible Types at Run Time

class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}

class Test {
    public static void main(String[] args) {
        Point[] pa = new Point[100];

        // The following line will throw a ClassCastException:
        ColoredPoint[] cpa = (ColoredPoint[])pa;
        System.out.println(cpa[0]);
        int[] shortvec = new int[2];
        Object o = shortvec;

        // The following line will throw a ClassCastException:
        Colorable c = (Colorable)o;
        c.setColor(0);
    }
}

This program uses casts to compile, but it throws exceptions at run time, because the types are incompatible.

Chapter 6: Names

6.3 Scope of a Declaration

6.3.1 Scope for Pattern Variables in Expressions

6.3.1.6 switch Expressions

The following rule applies rules apply to a switch expression (15.28) with a switch block consisting of switch rules (14.11.1):

The following rules apply to a switch expression with a switch block consisting of switch labeled statement groups (14.11.1):

6.3.2 Scope for Pattern Variables in Statements

6.3.2.5 for Statements

The following rules apply to a basic for statement (14.14.1):

An enhanced for statement (14.14.2) is defined by translation to a basic for statement, so no special rules need to be provided for it.

A property of the translation of an enhanced for statement for (p : e) S, where p is a record pattern, is that any pattern variable introduced by the pattern p is definitely matched in the statement S.

6.3.2.6 switch Statements

The following rule applies rules apply to a switch statement (14.11) with a switch block consisting of switch rules (14.11.1):

The following rules apply to a switch expression with a switch block consisting of switch labeled statement groups (14.11.1):

6.3.3 Scope for Pattern Variables in Patterns

6.3.3.1 Record Patterns

The following rule applies to a record pattern p:

6.3.4 Scope for Pattern Variables in Switch Labels

Pattern variables can be introduced by case labels with a case pattern, either by the pattern itself or by an associated when expression, and are in scope for the relevant parts of the associated switch expression (6.3.1.6) or switch statement (6.3.2.6).

The following rules apply to case labels:

Chapter 13: Binary Compatibility

13.4 Evolution of Classes

13.4.2 sealed, non-sealed, and final Classes

13.4.2.1 sealed Classes

If a class that was freely extensible (8.1.1.2) is changed to be declared sealed, then an IncompatibleClassChangeError is thrown if a binary of a pre-existing subclass of this class is loaded and is not a permitted direct subclass of this class (8.1.6); such a change is not recommended for widely distributed classes.

Changing a class that was declared final to be declared sealed does not break compatibility with pre-existing binaries.

Adding a class to the set of permitted direct subclasses of a sealed class will not break compatibility with pre-existing binaries.

Note that evolving a sealed class by adding a permitted direct subclass is considered a binary compatible change because pre-existing binaries that previously linked without error (e.g., a class file that contains an exhaustive switch (14.11.1)) will continue to link without error. A class file that contains an exhaustive switch will not fail to link if the sealed class that it switches over is expanded by the hierarchy's owner to have a new permitted direct subclass. The JVM is not required to perform exhaustiveness checks when linking a class file that contains an exhaustive switch.

The execution of an exhaustive switch can fail with an error (a MatchException is thrown) if it encounters an instance of a permitted direct subclass that was not known at compile time (14.11.3, 15.28.2). Strictly speaking, the error is not flagging a binary incompatible change of the sealed class, but more accurately a migration incompatible change of the sealed class.

If a class is removed from the set of permitted direct subclasses of a sealed class, then an IncompatibleClassChangeError is thrown if the pre-existing binary of the removed class is loaded.

Deleting the sealed modifier from a class that does not have a sealed direct superclass or a sealed direct superinterface does not break compatibility with pre-existing binaries.

If a sealed class C did have a sealed direct superclass or a sealed direct superinterface, then deleting the sealed modifier would prevent C from being recompiled, as every class with a sealed direct superclass or a sealed direct superinterface must be either final, sealed, or non-sealed.

13.4.26 Evolution of Enum Classes

Adding or reordering enum constants in an enum class will not break compatibility with pre-existing binaries.

As with sealed classes (13.4.2.1), although adding an enum constant to an enum class is considered a binary compatible change, it may cause the execution of an exhaustive switch (14.11.1) to fail if the switch encounters the new enum constant that was not known at compile time (14.11.3, 15.28.2).

Deleting an enum constant from an enum class will delete the public field that corresponds to the enum constant (8.9.3). The consequences are specified in 13.4.8. Such a change is not recommended for widely distributed enum classes.

In all other respects, the binary compatibility rules for enum classes are identical to those for normal classes.

13.5 Evolution of Interfaces

13.5.2 sealed and non-sealed Interfaces

If an interface that was freely extensible (9.1.1.4) is changed to be declared sealed, then an IncompatibleClassChangeError is thrown if a binary of a pre-existing subclass or subinterface of this interface is loaded and is not a permitted direct subclass or subinterface of this interface (9.1.4); such a change is not recommended for widely distributed classes.

Adding a class or interface to the set of permitted direct subclasses or subinterfaces, respectively, of a sealed interface will not break compatibility with pre-existing binaries.

As with sealed classes (13.4.2.1), whilst adding a permitted direct subclass or subinterface of a sealed interface is considered a binary compatible change, it may cause the execution of an exhaustive switch (14.11.1) to fail with an error (a MatchException may be thrown) if the switch encounters an instance of the new permitted direct subclass or subinterface that was not known at compile time (14.11.3, 15.28.2).

If a class or interface is removed from the set of permitted direct subclasses or subinterfaces of a sealed interface, then an IncompatibleClassChangeError is thrown if the pre-existing binary of the removed class or interface is loaded.

Changing an interface that was declared sealed to be declared non-sealed does not break compatibility with pre-existing binaries.

A non-sealed interface I must have a sealed direct superinterface. Deleting the non-sealed modifier would prevent I from being recompiled, as every interface with a sealed direct superinterface must be sealed or non-sealed.

Deleting the sealed modifier from an interface that does not have a sealed direct superinterface does not break compatibility with pre-existing binaries.

If a sealed interface I did have a sealed direct superinterface, then deleting the sealed modifier would prevent I from being recompiled, as every interface with a sealed direct superinterface must be sealed or non-sealed.

Chapter 14: Blocks, Statements, and Patterns

14.11 The switch Statement

The switch statement transfers control to one of several statements or expressions, depending on the value of an expression.

SwitchStatement:
switch ( Expression ) SwitchBlock

The Expression is called the selector expression. The type of the selector expression must be char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type (8.9), or a compile-time error occurs.

These restrictions on the type of the selector expression are now included in the notion of a switch block being compatible with a selector expression, defined in the following section.

14.11.1 Switch Blocks

The body of both a switch statement and a switch expression (15.28) is called a switch block. This subsection presents general rules which apply to all switch blocks, whether they appear in switch statements or switch expressions. Other subsections present additional rules which apply either to switch blocks in switch statements (14.11.2) or to switch blocks in switch expressions (15.28.1).

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchBlockStatementGroup} {SwitchLabel :} }
SwitchRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchBlockStatementGroup:
SwitchLabel : { SwitchLabel :} BlockStatements
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern [ Guard ]
Guard:
when Expression

A switch block can consist of either:

Every switch rule and switch labeled statement group starts with a switch label, which is either a case label or a default label. Multiple switch labels are permitted for a switch labeled statement group.

A case label has one or more case constants. Every case constant must be either a constant expression (15.29) or the name of an enum constant (8.9.1), or a compile-time error occurs.

Switch labels and their case constants are said to be associated with the switch block. No two of the case constants associated with a switch block may have the same value, or a compile-time error occurs.

A case label consists of either a list of case constants or a single case pattern.

Every case constant must be either (1) the null literal, (2) a constant expression (15.29), or (3) the name of an enum constant (8.9.1); otherwise a compile-time error occurs. A single null case constant may also be paired with the default keyword.

A case pattern may have an optional when expression, which represents a further test on values that match the pattern. A case pattern is said to be unguarded if either (i) it has no when expression, or (ii) it has a when expression that is a constant expression (15.29) with value true; and guarded otherwise.

Switch labels and their case constants and case patterns are said to be associated with the switch block.

For a given switch block both of the following must be true, otherwise a compile-time error occurs:

Any when expression associated with a switch block must have type boolean or Boolean. Any variable that is used but not declared in a when expression must be either final or effectively final (4.12.4). It is a compile-time error if a when expression is a constant expression (15.29) with the value false.

The switch block of a switch statement or a switch expression is compatible with the type of the selector expression, T, if both of the following are true:

The switch block of a switch statement or a switch expression is switch compatible with the type of the selector expression, T, if all of the following are true:

No case label supports a selector expression of type boolean, long, float, or double. Switch blocks are not designed to work with these types.

The switch block of a switch statement or a switch expression must be switch compatible with the type of the selector expression, or a compile-time error occurs.

A switch label is said to dominate another switch label if the former applies to every value that the latter applies to. It is a compile-time error if a switch label in a switch block dominates any switch label that follows it. The rules for determining dominance are as follows:

It is a compile-time error if, in a switch block that consists of switch labeled statement groups, a statement is labeled with a case pattern that declares one or more pattern variables, and either:

The first condition prevents a statement group from "falling through" to another statement group without initializing pattern variables. For example, were a statement labeled by case Integer i reachable from the preceding statement group, the pattern variable i would not have been initialized:

Object o = "Hello";
switch (o) {
    case String s:
        System.out.println("String: " + s );  // No break!
    case Integer i:
        System.out.println(i + 1);            // Error! Can be reached
                                              // without matching the
                                              // pattern `Integer i`
    default:
}

Switch blocks consisting of switch label statement groups allow multiple labels to apply to a statement group. The second condition prevents a statement group from being executed based on one label without initializing the pattern variables of another label. For example:

Object o = "Hello World";
switch (o) {
    case String s:
    case Integer i:
        System.out.println(i + 1);  // Error! Can be reached
                                    // without matching the
                                    // pattern `Integer i`
    default:
}

Object obj = null;
switch (obj) {
    case null:
    case String s:
        System.out.println(s);      // Error! Can be reached
                                    // without matching the
                                    // pattern `String s`
    default:
}

Both of these conditions apply only when the case pattern declares pattern variables. The following examples, in contrast, are unproblematic:

record R() {}
record S() {}

Object o = "Hello World";
switch (o) {
    case String s:
        System.out.println(s);        // No break!
    case R():
        System.out.println("It's either an R or a string");
        break;
    default:
}

Object ob = new R();
switch (ob) {
    case R():
    case S():                         // Multiple case labels!
        System.out.println("Either R or an S");
        break;
    default:
}

Object obj = null;
switch (obj) {
    case null:
    case R():                         // Multiple case labels!
        System.out.println("Either null or an R");
        break;
    default:
}
14.11.1.1 Exhaustive Switch Blocks

The switch block of a switch expression or switch statement is exhaustive for a selector expression of type T if either (i) there is a default label associated with the switch block, or (ii) there is a case label with a default associated with the switch block, or (iii) the set containing all the case constants and unguarded case patterns (collectively known as case elements) associated with the switch block is non-empty and exhausts T, which is specified as follows:

A switch statement or expression is exhaustive if its switch block exhausts the type of the selector expression.

14.11.1.2 Executable Switch Blocks and Determining which Switch Label Applies at Run-Time

As the meaning of some patterns is determined by the type of the expression that is being matched against, patterns must be resolved before pattern matching can be performed (14.30.2). Consequently, any case patterns associated with a switch block of a switch expression or switch statement must also be resolved.

An executable switch expression or switch statement is one where every case pattern associated with the switch block has been resolved with respect to the type of the selector expression, T, as follows:

Both the execution of a switch statement (14.11.3) and the evaluation of a switch expression (15.28.2) need to determine if a switch label matches the value of the selector expression. To determine whether a switch label in a switch block matches a given value, the value is compared with the case constants associated with the switch block. Then:

Both the execution of an executable switch statement (14.11.3) and the evaluation of an executable switch expression (15.28.2) need to determine if a switch label associated with the switch block applies to the value of the selector expression, as follows:

  1. If the value is the null reference, then a case label with a null case constant applies.

  2. If the value is not the null reference, then we determine the first (if any) case label in the switch block that applies to the value as follows:

    • A case label with a non-null case constant c applies to a value of type Character, Byte, Short, or Integer, if the value is first subjected to unboxing conversion (5.1.8) and the constant c is equal to the unboxed value.

      Any unboxing conversion must complete normally as the value being unboxed is guaranteed not to be the null reference.

      Equality is defined in terms of the == operator (15.21).

    • A case label with a non-null case constant c applies to a value that is not of type Character, Byte, Short, or Integer, if the constant c is equal to the value.

      Equality is defined in terms of the == operator unless the value is a String, in which case equality is defined in terms of the equals method of class String.

    • Determining that a case label with a case pattern p applies to a value proceeds first by checking if the value matches the pattern p (14.30.2).

      If pattern matching completes abruptly then the process of determining which switch label applies completes abruptly for the same reason.

      If pattern matching succeeds and the case pattern is unguarded then this case label applies.

      If pattern matching succeeds and the case pattern is guarded, then the when expression is evaluated. If the result is of type Boolean, it is subjected to unboxing conversion (5.1.8).

      If evaluation of the when expression or the subsequent unboxing conversion (if any) completes abruptly for some reason, the process of determining which switch label applies completes abruptly for the same reason.

      Otherwise, if the resulting value is true then the case label applies.

    • A case null, default label applies to every value

  3. If the value is not the null reference, and no case label applies according to the rules of step 2, then if a default label is associated with the switch block, that label applies.

A single case label can contain several case constants. The label matches applies to the value of the selector expression if any one of its constants matches is equal to the value of the selector expression. For example, in the following code, the case label matches if the enum variable day is either one of the enum constants shown:

switch (day) {
    ...
    case SATURDAY, SUNDAY :
        System.out.println("It's the weekend!");
        break;
    ...
}

If a switch label that supports a pattern applies, then this is because the process of pattern matching the value against the pattern has succeeded (14.30.2). If a value successfully matches a pattern then the process of pattern matching initializes any pattern variables declared by the pattern.

For historical reasons, a default label is only considered after all case labels have failed to match, even if some of those labels appear after the default label. However, subsequent labels may only make use of non-null case constants (14.11.1), and as a matter of style, programmers are encouraged to place their default labels last.

null cannot be used as a case constant because it is not a constant expression. Even if case null was allowed, it would be undesirable because the code in that case can never be executed. Namely, if the selector expression is of a reference type (that is, String or a boxed primitive type or an enum type), then an exception will occur if the selector expression evaluates to null at run time. In the judgment of the designers of the Java programming language, propagating the exception is a better outcome than either having no case label match, or having the default label match.

In C and C++ the body of a switch statement can be a statement and statements with case labels do not have to be immediately contained by that statement. Consider the simple loop:

for (i = 0; i < n; ++i) foo();

where n is known to be positive. A trick known as Duff's device can be used in C or C++ to unroll the loop, but this is not valid code in the Java programming language:

int q = (n+7)/8;
switch (n%8) {
    case 0: do { foo();    // Great C hack, Tom,
    case 7:      foo();    // but it's not valid here.
    case 6:      foo();
    case 5:      foo();
    case 4:      foo();
    case 3:      foo();
    case 2:      foo();
    case 1:      foo();
            } while (--q > 0);
}

Fortunately, this trick does not seem to be widely known or used. Moreover, it is less needed nowadays; this sort of code transformation is properly in the province of state-of-the-art optimizing compilers.

14.11.2 The Switch Block of a switch Statement

In addition to the general rules for switch blocks (14.11.1), there are further rules for switch blocks in switch statements.

An enhanced switch statement is one where either (i) the type of the selector expression is not char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type, or (ii) there is a case pattern or null case constant associated with the switch block.

Namely, all All of the following must be true for the switch block of a switch statement, or a compile-time error occurs:

Prior to Java SE 20, switch statements (and switch expressions) were limited in two ways: (i) the type of the selector expression was restricted to either an integral type (excluding long), an enum type, or String; and (ii) only non-null case constants were supported. Moreover, unlike switch expressions, switch statements did not have to be exhaustive. This is often the cause of difficult-to-detect bugs, where no switch label applies and the switch statement will silently do nothing. For example:

enum E { A, B, C }

E e = ...;
switch (e) {
   case A -> System.out.println("A");
   case B -> System.out.println("B");
   // No case for C!
}

With Java SE 20, switch statements have been enhanced in the sense that along with supporting case patterns, the two limitations listed above have also been lifted. The designers of the Java programming language decided that enhanced switch statements should align with switch expressions and be required to be exhaustive. This is often achieved with the addition of a trivial default label. For example, the following enhanced switch statement is not exhaustive:

Object o = ...;
switch (o) {    // Error - non-exhaustive switch!
    case String s -> System.out.println("A string!");
}

but it can easily be made exhaustive:

Object o = ...;
switch (o) {
    case String s -> System.out.println("A string!");
    default -> {}
}

For compatibility reasons, switch statements that are not enhanced switch statements are not required to be exhaustive.

14.11.3 Execution of a switch Statement

An executable A switch statement (14.11.1.2) is executed by first evaluating the selector expression. Then: If evaluation of the selector expression completes abruptly, then the entire switch statement completes abruptly for the same reason.

If evaluation of the selector expression completes normally and the result is non-null, and the subsequent unboxing conversion (if any) completes normally, then execution of the switch statement continues by determining if a switch label associated with the resolved switch block matches applies to the value of the selector expression (14.11.1.2). Then:

If execution of any statement or expression in the switch block completes abruptly, it is handled as follows:

Example 14.11.3-1. Fall-Through in the switch Statement

When a selector expression matches a switch label switch label applies, and that switch label is for a switch rule, the switch rule expression or statement introduced by the switch label is executed, and nothing else. In the case of a switch label for a statement group, all the block statements in the switch block that follow the switch label are executed, including those that appear after subsequent switch labels. The effect is that, as in C and C++, execution of statements can "fall through labels."

For example, the program:

class TooMany {
    static void howMany(int k) {
        switch (k) {
            case 1: System.out.print("one ");
            case 2: System.out.print("too ");
            case 3: System.out.println("many");
        }
    }
    public static void main(String[] args) {
        howMany(3);
        howMany(2);
        howMany(1);
    }
}

contains a switch block in which the code for each case falls through into the code for the next case. As a result, the program prints:

many
too many
one too many

Fall through can be the cause of subtle bugs. If code is not to fall through case to case in this manner, then break statements can be used to indicate when control should be transferred, or switch rules can be used, as in the program:

class TwoMany {
    static void howMany(int k) {
        switch (k) {
            case 1: System.out.println("one");
                    break;  // exit the switch
            case 2: System.out.println("two");
                    break;  // exit the switch
            case 3: System.out.println("many");
                    break;  // not needed, but good style
        }
    }
    static void howManyAgain(int k) {
        switch (k) {
            case 1 -> System.out.println("one");
            case 2 -> System.out.println("two");
            case 3 -> System.out.println("many");
        }
    }
    public static void main(String[] args) {
        howMany(1);
        howMany(2);
        howMany(3);
        howManyAgain(1);
        howManyAgain(2);
        howManyAgain(3);
    }
}

This program prints:

one
two
many
one
two
many

14.14 The for Statement

14.14.2 The enhanced for statement

The enhanced for statement has the form:

EnhancedForStatement:
for ( LocalVariableDeclaration EnhancedForDeclaration : Expression )
Statement
EnhancedForStatementNoShortIf:
for ( LocalVariableDeclaration EnhancedForDeclaration : Expression )
StatementNoShortIf
EnhancedForDeclaration:
LocalVariableDeclaration
RecordPattern

The following productions from 4.3, 8.3, 8.4.1, and 14.4, and 14.30 are shown here for convenience:

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
Dims:
{Annotation} [ ] {{Annotation} [ ]}
RecordPattern:
ReferenceType ( [ PatternList ] )
PatternList:
Pattern { , Pattern }

The type of the Expression must be an array type (10.1) or a subtype of the raw type Iterable, or a compile-time error occurs.

The header of the enhanced for statement EnhancedForDeclaration is either (i) a local variable declaration that declares a local variable whose name is the identifier given by VariableDeclaratorId, or (ii) a record pattern (14.30.1). When the enhanced for statement is executed, then either the local variable is initialized, on each iteration of the loop, to successive elements of the Iterable or the array produced by the expression, or on each iteration of the loop, successive elements of the Iterable or the array produced by the expression are pattern matched against the record pattern.

The iteration type of the Expression is defined as follows:

The rules for a local variable declared in the header of an enhanced for statement are specified in 14.4, disregarding any rules in that section which apply when the LocalVariableType is var.

In addition, all of the following must be true, or a compile-time error occurs:

If the EnhancedForDeclaration of an enhanced for statement is a LocalVariableDeclaration, then both of the following must be true, or a compile-time error occurs:

For the purposes of the compile-time rules given in 14.4, if the EnhancedForDeclaration of an enhanced for statement is a LocalVariableDeclaration then it is treated as if it had an initializer whose type is the for statement's iteration type.

These changes shift responsibility for the detailed treatment of the local variable declaration—in particular the treatment of var—to 14.4. There is no substantive change in how an enhanced for with a local variable declaration is interpreted.

If the EnhancedForDeclaration of an enhanced for statement is a record pattern, then both of the following must be true, or a compile-time error occurs:

The scope and shadowing of a any local variable declared in the header EnhancedForDeclaration of an enhanced for statement is specified in 6.3 and 6.4.

References to the any such local variable from a nested class or interface, or a lambda expression, are restricted, as specified in 6.5.6.1.

The type T of the local variable declared in the header of the enhanced for statement is determined as follows:

These rules are subsumed by the definition of "iteration type" and the assertion that the local variable declaration is treated as if it had an initializer of the iteration type.

The precise meaning of the enhanced for statement is given by translation into a basic for statement, as follows:

For example, this code:

List<? extends Integer> l = ...
for (float i : l) ...

will be translated to:

for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
    float #i0 = (Integer)#i.next();
    ...
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
    float #i0 = #i.next();
    ...
}

Example 14.14-1. Enhanced for And Arrays

The following program, which calculates the sum of an integer array, shows how enhanced for works for arrays:

int sum(int[] a) {
    int sum = 0;
    for (int i : a) sum += i;
    return sum;
}

Example 14.14-2. Enhanced for And Unboxing Conversion

The following program combines the enhanced for statement with auto-unboxing to translate a histogram into a frequency table:

Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
    total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
    System.out.println(e.getKey() + " " + e.getValue() / total);
}

14.30 Patterns

A pattern describes a test that can be performed on a value. Patterns appear as operands of statements and expressions, which provide the values to be tested. Patterns may declare local variables, known as pattern variables.

The process of testing a value against a pattern is known as pattern matching. If a value successfully matches a pattern, then the process of pattern matching initializes the pattern variable variables, if any, declared by the pattern.

Pattern variables are only in scope (6.3) where pattern matching succeeds and thus the pattern variables will have been initialized. It is not possible to use a pattern variable that has not been initialized.

14.30.1 Kinds of Patterns

A type pattern is used to test whether a value is an instance of the type appearing in the pattern. A record pattern is used to test whether a value is an instance of a record class type and, if it is, to recursively perform pattern matching on the record component values. A pattern may be parenthesized to assist in readability.

Pattern:
TypePattern
RecordPattern
ParenthesizedPattern
TypePattern:
LocalVariableDeclaration
RecordPattern:
ReferenceType ( [ PatternList ] )
PatternList :
Pattern { , Pattern }
ParenthesizedPattern:
( Pattern )

The following productions from 4.3, 8.3, 8.4.1, and 14.4 are shown here for convenience:

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
Dims:
{Annotation} [ ] {{Annotation} [ ]}

See 8.3 for UnannType.

A type pattern declares one local variable, known as a pattern variable. The Identifier in the local variable declaration specifies the name of the pattern variable.

A type pattern that does not appear as an element in the nested pattern list of a record pattern is called a top-level type pattern.

The rules for a local variable declared in a type pattern are specified in 14.4. In addition, all of the following must be true, or a compile-time error occurs:

The type of a pattern variable declared in a top-level type pattern is the reference type denoted by LocalVariableType.

The type of a pattern variable declared in a type pattern that is not a top-level type pattern is determined as follows:

The type of a type pattern is the type of its pattern variable.

A record pattern consists of a ReferenceType and a nested pattern list. If ReferenceType is not a record class type (8.10) then a compile-time error occurs.

If the ReferenceType is a raw type, then the type of the record pattern is inferred, as described in 18.5.5. It is a compile-time error if no type can be inferred for the record pattern.

Otherwise, the type of the record pattern is ReferenceType.

The length of the record pattern's nested pattern list must be the same as the length of the record component list in the declaration of the record class named by ReferenceType; otherwise a compile-time error occurs.

Currently, there is no support for variable arity record patterns. This may be supported in future versions of the Java Programming Language.

A record pattern declares the local variables, if any, that are declared by the patterns in the nested pattern list.

A parenthesized pattern declares the local variables that are declared by the contained pattern.

There is also a special any pattern, which is a pattern that arises from the process of resolving a pattern (14.30.2).

Currently, no syntax exists for any patterns so they can not be used as a pattern in a pattern instanceof expression, or as a pattern in a switch label of a switch expression or switch statement. It is possible that future versions of the Java programming language may relax this restriction.

An any pattern declares one local variable, known as a pattern variable.

The pattern variable declared by an any pattern has a type, which is a reference type.

An expression e is compatible with a pattern of type T if e is downcast compatible with T (5.5).

Compatibility of an expression with a pattern is used by the instanceof pattern match operator (15.20.2).

14.30.2 Pattern Matching

Pattern matching is the process of testing a value against a pattern at run time. Pattern matching is distinct from statement execution (14.1) and expression evaluation (15.1). If a value successfully matches a pattern, then the process of pattern matching will initialize all the pattern variables declared by the pattern, if any.

Before pattern matching is performed, all patterns are first resolved with respect to the type of the expression that they are to be matched against (either the selector expression of a switch statement or switch statement, or the RelationalExpression of an instanceof expression), resulting in a possibly amended pattern.

Resolving a pattern at type U is specified as follows:

This process of resolving a pattern is to capture the correct semantics of record patterns. Consider, for example:

class Super {}
class Sub extends Super {}
record R(Super s) {}

We expect all non-null values of type R to match the pattern R(Super s), including the value resulting from evaluating the expression new R(null). (Even though the null value does not match the pattern Super s.) However, we would not expect this value to match the pattern R(Sub s) as the null value for the record component does not match the pattern Sub s.

The meaning of a pattern occurring in a nested pattern list is then determined with respect to the record declaration. Resolution replaces any type patterns appearing in a nested pattern list that should match all values including null with instances of the special any pattern. In our example above, the pattern R(Sub s) is resolved to the pattern R(Sub s), whereas the pattern R(Super s) is resolved to a record pattern with type R and a nested pattern list containing an any pattern.

The process of pattern matching may involve expression evaluation or statement execution. Accordingly, pattern matching is said to complete abruptly if evaluation of a expression or execution of a statement completes abruptly. An abrupt completion always has an associated reason, which is always a throw with a given value. Pattern matching is said to complete normally if it does not complete abruptly.

The rules for determining whether a value matches a resolved pattern, and for initializing pattern variables, are as follows:

There is no rule to cover a value that is the null reference. This is because the solitary construct that performs pattern matching, the instanceof pattern match operator (15.20.2), only does so when a value is not the null reference. It is possible that future versions of the Java programming language will allow pattern matching in other expressions and statements.

14.30.3 Properties of Patterns

A pattern p is said to be applicable at a type T if one of the following rules apply:

A pattern p is said to be unconditional at a type T if every value of type T will match p (after p has been resolved at type T (14.30.2)), and is defined as follows:

Note that record patterns are not unconditional at any type because the null reference does not match any record pattern.

A pattern p is said to dominate another pattern q if every value that matches q also matches p, and is defined as follows:

Chapter 15: Expressions

15.20 Relational Operators

15.20.2 The instanceof Operator

An instanceof expression may perform either type comparison or pattern matching.

InstanceofExpression:
RelationalExpression instanceof ReferenceType
RelationalExpression instanceof Pattern

If the operand to the right of the instanceof keyword is a ReferenceType, then the instanceof keyword is the type comparison operator.

If the operand to the right of the instanceof keyword is a Pattern, then the instanceof keyword is the pattern match operator.

The following rules apply when instanceof is the type comparison operator:

The following rules apply when instanceof is the pattern match operator:

Example 15.20.2-1. The Type Comparison Operator

class Point   { int x, y; }
class Element { int atomicNumber; }
class Test {
    public static void main(String[] args) {
        Point   p = new Point();
        Element e = new Element();
        if (e instanceof Point) {  // compile-time error
            System.out.println("I get your point!");
            p = (Point)e;  // compile-time error
        }
    }
}

This program results in two compile-time errors. The cast (Point)e is incorrect because no instance of Element or any of its possible subclasses (none are shown here) could possibly be an instance of any subclass of Point. The instanceof expression is incorrect for exactly the same reason. If, on the other hand, the class Point were a subclass of Element (an admittedly strange notion in this example):

class Point extends Element { int x, y; }

then the cast would be possible, though it would require a run-time check, and the instanceof expression would then be sensible and valid. The cast (Point)e would never raise an exception because it would not be executed if the value of e could not correctly be cast to type Point.

Prior to Java SE 16, the ReferenceType operand of a type comparison operator was required to be reifiable (4.7). This prevented the use of a parameterized type unless all its type arguments were wildcards. The requirement was lifted in Java SE 16 to allow more parameterized types to be used. For example, in the following program, it is legal to test whether the method parameter x, with static type List<Integer>, has a more "refined" parameterized type ArrayList<Integer> at run time:

import java.util.ArrayList;
import java.util.List;

class Test2 {
    public static void main(String[] args) {
        List<Integer> x = new ArrayList<Integer>();

        if (x instanceof ArrayList<Integer>) {  // OK
            System.out.println("ArrayList of Integers");
        }
        if (x instanceof ArrayList<String>) {  // error
            System.out.println("ArrayList of Strings");
        }
        if (x instanceof ArrayList<Object>) {  // error
            System.out.println("ArrayList of Objects");
        }
    }
}

The first instanceof expression is legal because there is a casting conversion from List<Integer> to ArrayList<Integer>. However, the second and third instanceof expressions both cause a compile-time error because there is no casting conversion from List<Integer> to ArrayList<String> or ArrayList<Object>.

15.28 switch Expressions

A switch expression transfers control to one of several statements or expressions, depending on the value of an expression; all possible values of that expression must be handled, and all of the several statements and expressions must produce a value for the result of the switch expression.

SwitchExpression:
switch ( Expression ) SwitchBlock

The Expression is called the selector expression. The type of the selector expression must be char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type (8.9), or a compile-time error occurs.

The body of both a switch expression and a switch statement (14.11) is called a switch block. General rules which apply to all switch blocks, whether they appear in switch expressions or switch statements, are given in 14.11.1. The following productions from 14.11.1 are shown here for convenience:

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchBlockStatementGroup} {SwitchLabel :} }
SwitchRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchBlockStatementGroup:
SwitchLabel : { SwitchLabel :} BlockStatements
SwitchLabel:
case CaseConstant {, CaseConstant}
default
SwitchLabel:
case CaseConstant {, CaseConstant }
case CasePattern
case null [, default]
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern [ Guard ]
Guard:
when Expression

15.28.1 The Switch Block of a switch Expression

In addition to the general rules for switch blocks (14.11.1), there are further rules for switch blocks in switch expressions.

Namely, all of the following must be true for the switch block of a switch expression, or a compile-time error occurs:

If It is a compile-time error if the switch block of a switch expression consists of switch rules, then any switch rule block cannot but one or more switch rule blocks can complete normally (14.22).

If It is a compile-time error if the switch block of a switch expression consists of switch labeled statement groups, then but the last statement in the switch block cannot can complete normally, and or the switch block does not have any has one or more switch labels after the last switch labeled statement group.

It is a compile-time error if a switch expression is not exhaustive (14.11.1.1).

switch expressions cannot have empty switch blocks, unlike switch statements.

This is covered by the result expression requirement, below.

Furthermore, switch expressions differ from switch statements in terms of which expressions may appear to the right of an arrow (->) in the switch block, that is, which expressions may be used as switch rule expressions. In a switch expression, any expression may be used as a switch rule expression, but in a switch statement, only a statement expression may be used (14.11.1).

The result expressions of a switch expression are determined as follows:

It is a compile-time error if a switch expression has no result expressions.

A switch expression is a poly expression if it appears in an assignment context or an invocation context (5.2, 5.3). Otherwise, it is a standalone expression.

Where a poly switch expression appears in a context of a particular kind with target type T, its result expressions similarly appear in a context of the same kind with target type T.

A poly switch expression is compatible with a target type T if each of its result expressions is compatible with T.

The type of a poly switch expression is the same as its target type.

The type of a standalone switch expression is determined as follows:

15.28.2 Run-Time Evaluation of switch Expressions

An executable A switch expression (14.11.1.2) is evaluated by first evaluating the selector expression. Then: If evaluation of the selector expression completes abruptly, then evaluation of the switch expression completes abruptly for the same reason.

If evaluation of the selector expression completes normally and the result is non-null, and the subsequent unboxing conversion (if any) completes normally, then evaluation of the switch expression continues by determining if a switch label associated with the resolved switch block matches applies to the value of the selector expression (14.11.1.2). Then:

If execution of any statement or expression in the switch block completes abruptly, it is handled as follows:

Chapter 16: Definite Assignment

16.2 Definite Assignment and Statements

16.2.9 switch Statements

Chapter 18: Type Inference

18.5 Uses of Inference

18.5.5 Record Pattern Type Inference

When a record pattern (14.30.1) for a generic record class R appears in a context in which values of a type T will be matched against it, and the pattern does not provide type arguments for R, the type arguments are inferred, as described below.

  1. If T is not downcast convertible (5.5) to the raw type R, inference fails.

  2. Otherwise, where P1, ..., Pn (n 1) are the type parameters of R, let α1, ..., αn be inference variables. An initial bound set, B0, is generated from the declared bounds of P1, ..., Pn, as described in 18.1.3.

  3. A type T' is derived from T, as follows:

    • If T is a wildcard-parameterized type, let β1, ..., βk (k 1) be inference variables, where k is the number of wildcard type arguments in T. T' is the result of replacing each wildcard type argument in T with βi (1 ≤ ik).

      Additional bounds for β1, ..., βk are incorporated into B0 to form a bound set B1, as follows:

      • If βi (1 ≤ ik) replaced a wildcard in T with upper bound U, then the bound βi <: U appears in the bound set

      • If βi (1 ≤ ik) replaced a wildcard in T with lower bound L, then the bound L <: βi appears in the bound set

      • Let Q1, ..., Qm (m 1) be the type parameters of the class or interface named by T', and A1, ..., Am be the type arguments of T'.

        For each βi (1 ≤ ik), and for each type U delimited by & in the TypeBound of the type parameter corresponding to βi (1 ≤ im), the bound βi <: U[Q1:=A1, ..., Qm:=Am] appears in the bound set. If there is no TypeBound for the type parameter corresponding to βi, or if no proper upper bounds are derived from the TypeBound (only dependencies), then the bound βi <: Object appears in the set.

    • If T is any other class or interface type, then T' is the same as T, and B1 is the same as B0.

    • If T is a type variable or an intersection type, then for each upper bound of the type variable or element of the intersection type, this step and step 4 are repeated recursively. All bounds produced in steps 3 and 4 are incorporated into a single bound set.

  4. If T' is a parameterization of a generic class G, and there exists a supertype of R<α1, ..., αn> that is also a parameterization of G, let R' be that supertype. The constraint formula ‹T' = R'› is reduced (18.2) and the resulting bounds are incorporated into B1 to produce a new bound set, B2.

    Otherwise, B2 is the same as B1.

    If B2 contains the bound false, inference fails.

  5. Otherwise, the inference variables α1, ..., αn are resolved in B2 (18.4). Unlike normal resolution, in this case resolution skips the step that attempts to produce an instantiation for an inference variable from its proper lower bounds or proper upper bounds; instead, any new instantiations are created by skipping directly to the step that introduces fresh type variables.

    If resolution fails, then inference fails.

  6. Otherwise, let A1, ..., An be the resolved instantiations for α1, ..., αn, and let Y1, ..., Yp (p ≥ 0) be any fresh type variables introduced by resolution.

    The type of the record pattern is the upward projection of R<A1, ..., An> with respect to Y1, ..., Yp (4.10.5).

Example 18.5.5-1. Record Pattern Type Inference

The following program infers a parameterization for a record class:

record Mapper<T>(T in, T out) implements UnaryOperator<T> {
    public T apply(T arg) { return in.equals(arg) ? out : null; }
}

void test(UnaryOperator<? extends CharSequence> op) {
    if (op instanceof Mapper(var in, var out)) {
        boolean shorter = out.length() < in.length();
    }
}

In this case, R is the record class Mapper, and T is the type UnaryOperator<? extends CharSequence>. T is downcast convertible to raw Mapper, so we'll infer an instantiation for α in Mapper<α>. T' is the type UnaryOperator<β>, where β has upper bound CharSequence.

Mapper<α> has the supertype UnaryOperator<α>, so we'll reduce the constraint formula ‹UnaryOperator<β> = UnaryOperator<α>›. This leads to the bound α = β. Incorporation further infers that α <: CharSequence.

Now we resolve α, yielding α = Y, a fresh type variable with upper bound CharSequence. Finally, we find the upward projection of Mapper<Y> with respect to Y, inferring that the type of the record pattern is Mapper<? extends CharSequence>.

Once we know the type of the record pattern, we can find its component types, which are matched against the nested patterns. Pattern variables in and out both have type CharSequence.