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

Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview)

    XMLWordPrintable

Details

    • CSR
    • Resolution: Approved
    • P4
    • 23
    • tools
    • None
    • source
    • minimal
    • Java API, Language construct
    • SE

    Description

      Summary

      Enhance pattern matching by allowing primitive type patterns to be used in all pattern contexts, align the semantics of primitive type patterns with instanceof, and extend switch to allow primitive constants as case labels. This is a preview language feature.

      Problem

      Nested primitive type patterns are limited

      Disaggregation with type patterns that involve primitive types is currently invariant. For example, assume a JSON model can be encoded with a sealed hierarchy according to its specification as follows:

      sealed interface JsonValue { 
        record JsonString(String s) implements JsonValue { }
        record JsonNumber(double d) implements JsonValue { } 
        record JsonNull() implements JsonValue { }
        record JsonBoolean(boolean b) implements JsonValue { }
        record JsonArray(List<JsonValue> values) implements JsonValue { }
        record JsonObject(Map<String, JsonValue> pairs) implements JsonValue { }
      }

      With respect to numbers JSON does not distinguish integers from non-integers, so in JsonNumber we represent all numbers with double values as recommended by the specification.

      Given a JSON payload of

      { "name" : "John", "age" : 30 }

      we can construct a corresponding JsonValue via

      var json = new JsonObject(Map.of("name", new JsonString("John")
                                       "age", new JsonNumber(30)));

      For each key in the map, this code instantiates an appropriate record for the corresponding value. For the first, the value "John" has the same type as the record's component, namely String. For the second, however, the Java compiler applies a widening primitive conversion to convert the int value, 30, to a double.

      What we would really like to do is use int directly in the JsonNumber pattern such that the pattern matches only when the double value inside the JsonNumber object can be converted to an int without loss of information, and when it does match it automatically narrows the double value to an int:

      if (json instanceof JsonObject(var map)
          && map.get("name") instanceof JsonString(String name)
          && map.get("age") instanceof JsonNumber(int age))
      {
          return new Customer(name, age);
      }

      Primitive type patterns are not permitted in top-level context of instanceof

      Primitive types patterns cannot be used at top-level contexts of instanceof at all; only type patterns of reference types are allowed in top-level contexts.

      Since the pattern matching instanceof operator safeguards cast conversions about reference types, lifting restrictions to primitive type patterns, means that instanceof can now be able to safeguard any cast conversion supported by Java (JLS 5.5), at top-level too. In the following example, instanceof with a primitive type pattern byte b implies that instanceof safeguards whether i can be safely cast to byte without loss of information. If instanceof returns true, it means that (byte) i will not lead to loss of information about e.g., magnitude and sign:

      int i = ...
      if (i instanceof byte b) {
          ... b ...
      }

      In this particular case of these two types, checking whether int can be safely cast to byte necessitates a run-time test of exactness. There are other pairs that do not need a run-time check and a conversion is guaranteed only by inspecting the two types involved. For example an int to long is always safe. A conversion between these two is unconditionally exact.

      Primitive types are not permitted in type comparisons

      Extending instanceof as the pattern matching operator means that the semantics for the type comparison operator instanceof, can be enhanced, symmetrically.

      int i = ...
      if (i instanceof byte) {
        ...
      }

      Primitive type patterns are not permitted in top-level context of switch

      At present, primitive type patterns are not allowed at a top-level context of a switch either. For example, with a top-level primitive type pattern Java users could rewrite the switch expression (JEP 361)

      switch (x.getStatus()) {
          case 0 -> "okay";
          case 1 -> "warning";
          case 2 -> "error";
          default -> "unknown status: " + x.getStatus();
      }

      more clearly as:

      switch (x.getStatus()) {
          case 0 -> "okay";
          case 1 -> "warning";
          case 2 -> "error";
          case int i -> "unknown status: " + i;
      }

      Here, the case int i label matches any status value not previously matched, making the switch expression exhaustive so that no default label is required.

      switch does not support all primitive types

      Prior to this JEP, switch expressions and switch statements could switch on some primitive types — but not boolean, float, double, or long. For example a switch involving longs:

      long v = ...;
      switch (v) {
          case 0x01            -> ...;
          case 0x02            -> ...;
          case 10_000_000_000L -> ...;
          case 20_000_000_000L -> ...;
          case long l          -> ... l ...;
      }

      With float values in case labels users will be able to switch on floating-point match candidates:

      float f = ...;
      switch (f) {
          case Float.NaN    -> ...;
          case Float.POSITIVE_INFINITY -> ...;
          case Float.NEGATIVE_INFINITY -> ...;
          case float g -> ... g ...;
      }

      With this proposal a switch-boolean will also be possible:

      boolean u = user.isLoggedIn();
      switch (u) {
          case true  -> user.id();
          case false -> { log("Unrecognized user"); yield -1; }
      }

      Solution

      The Java language is enhanced as follows:

      • Allow primitive type patterns to be used in nested positions, even if they do not spell-out the same type of the corresponding record component:
        • Derive the semantics of primitive type patterns (and reference type patterns on targets of primitive type) from casting conversions (A primitive type pattern T is applicable to a type U if there is a casting conversion).
      • Allow switch to support primitive types:
        • Add support for constant expressions of long, float, double, boolean, and their boxes.
        • Allow boolean switches to be exhaustive when both true and false cases are listed.
        • Incorporate primitive patterns in the exhaustiveness check: a) when a boolean type pattern is used and b) when a primitive type pattern is unconditional to the underlying type of a match candidate which has a boxed type.
      • Allow instanceof to support primitive types:
        • Update the grammar for instanceof as a type comparison operator to accept any type as its RHS.
        • Add support for instanceof according to all supported pairs of conversions under a casting context. instanceof is the precondition test for safe casting in general (of an exact conversion).
        • Define exactness of conversions:
          • Define unconditionally exact conversions for some pair of types (no run-time action needed).
          • Define the run-time actions to decide exactness for the rest.
      • Translate instanceof in TransPatterns to invoke the exactness tests when needed.
      • Allow the switch translation in SwitchBootstraps to call the exactness tests when needed.

      Specification

      The updated JLS draft for primitive types in patterns, instanceof, and switch is linked as https://cr.openjdk.org/~abimpoudis/instanceof/latest/ and attached as JLS-instanceof.pdf.

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

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

      Attachments

        1. 8304487.specdiff.01.zip
          3.55 MB
        2. 8304487.specdiff.02.zip
          3.57 MB
        3. 8304487.specdiff.03.zip
          3.85 MB
        4. JLS-instanceof.pdf
          1011 kB
        5. JLS-instanceof-1.pdf
          1.38 MB
        6. JLS-instanceof-2.pdf
          1.37 MB
        7. JLS-instanceof-3.pdf
          1.40 MB
        8. JLS-instanceof-4.pdf
          1.41 MB
        9. JLS-instanceof-5.pdf
          1.48 MB
        10. JLS-instanceof-6.pdf
          1.45 MB
        11. JLS-instanceof-7.pdf
          1.45 MB

        Issue Links

          Activity

            People

              abimpoudis Angelos Bimpoudis
              abimpoudis Angelos Bimpoudis
              Jan Lahoda
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: