Skip to content

inconsistency between path-dependent and projection type matching #16728

Closed
@ftucky

Description

@ftucky

Compiler version

3.2.2

Minimized code

class A:
  class X:
      def outer : A.this.type = A.this

class B extends A
class C extends A

@main def run() : Unit =
  val b0 = new B
  val b1 = b0
  val b2 = new B

  val c0 = new C
  val c1 = c0
  val c2 = new C

  val b0x : A#X = new b0.X

  val pathTypeMatch = b0x match
    case _ : c2.X => "c2.X"
    case _ : c1.X => "c1.x"
    case _ : c0.X => "c0.X"
    case _ : b2.X => "b2.X"
    case _ : b1.X => "b1.X"
    case _ : b0.X => "b0.X"
    case _        => "ELSE"

  println(pathTypeMatch)

  val projectionTypeMatch = b0x match
    case _ : C#X => "C#X"
    case _ : B#X => "B#X"
    case _ : A#X => "A#X"
    case _       => "ELSE"

  println(projectionTypeMatch)

  val failingTypeMatch = b0x match
    case cx : C#X =>
      val c : C = cx.outer
      c

  println(failingTypeMatch)

Output

b1.X
C#X
java.lang.ClassCastException: class B cannot be cast to class C (B and C are in unnamed module of loader java.net.URLClassLoader @55e91e61)

Expectation

Path-dependent type matching behaves as expected. b0x is indeed a b1.X.

Projection type matching is inconsistent. b0x is not a C#X, but a B#X.
Once in the branch, the legitimate type-ascription of (cx:C#X).outer to type C throws the exception.

Apparently, B#X and C#X have a type erasure in A#X. In the byte code, the condition of all 3 branches are strictly identical (isinstance A$X). Thus, branches B#X and A#X are actually unreachable.

The paragraph below suggests the intention to take the prefix into account for the match.
https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#type-patterns

A type pattern T is of one of the following forms: A reference to a class C, p.C, or T#C. This type pattern matches any non-null instance of the given class. Note that the prefix of the class, if it exists, is relevant for determining class instances.

Besides, https://scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure states:

The erasure of a type projection T#x is |T|#x.

Here, C is concrete, so the erasure of C#X is expected to be C#X, not A#X.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions