3

This question is derived from:

Scala: Abstract types vs generics

In Scala 3, a path-dependent type is a type/bound that binds term(s)/object(s) with a distinct compile-time path signature. As a result, once it is defined (with its upper bound == lowerbound) for a trait, it is considered final and cannot be overridden in any implementing objects:

  object Case1 {

    trait Sub {
      type EE
    }

    trait S1 extends Sub { type EE = Product }
    trait S2 extends Sub { type EE = Tuple }

    trait A1 extends S1

    trait A2 extends A1 with S2
  }

---

: error overriding type EE in trait S1, which equals Product;
  type EE in trait S2, which equals Tuple trait A2 inherits conflicting members:
  type EE in trait S1, which equals Product  and
  type EE in trait S2, which equals Tuple
(Note: this can be resolved by declaring an override in trait A2.)

But there is one exception to this rule: this.type can easily bypass it:

  object Case2 {

    trait Supe {

      type E

      trait Sub {

        type EE = Supe.this.E
      }
    }

    object S1 extends Supe {
      type E = Product
    }
    object S2 extends Supe {
      type E = Tuple
    }

    trait A1 extends S1.Sub

    trait A2 extends A1 with S2.Sub
  }

// No compilation error

Why is it special as a path-dependent type? If it is not, what is it? I cannot find any part of the DOT calculus that propose an exclusive rule for it.

tribbloid
  • 4,026
  • 14
  • 64
  • 103
  • 1
    It's interesting that in Scala 2.13 Case 2 doesn't compile either https://scastie.scala-lang.org/DmytroMitin/mAa5gJOaQei1fRizsGhdgw – Dmytro Mitin Apr 26 '23 at 16:23
  • "not found: type Tuple" is distraction, how about this? https://scastie.scala-lang.org/WcVbqiI8Q42xyRs0O9TTpQ – tribbloid Apr 26 '23 at 16:38
  • 1
    Oh, yeah, thanks. I had `scala3-library_3` on my 2.13 classpath locally, so I had `Tuple` and my error was `illegal inheritance`. I didn't notice that the error at Scastie is different. – Dmytro Mitin Apr 26 '23 at 16:45
  • 1
    You still can't create an instance of `Case2.A2`. – Jasper-M Apr 28 '23 at 13:57
  • 1
    Override checking doesn't really come into play here, because `type EE` is already fully defined in and inherited from the common super type `Supe#Sub`. And Scala 3 delays bounds checking until you try to create a concrete class or instance. – Jasper-M Apr 28 '23 at 14:02
  • 1
    https://contributors.scala-lang.org/t/is-this-type-a-path-dependent-type-what-makes-it-special/6177/1 – Dmytro Mitin Apr 28 '23 at 18:40
  • @Jasper-M Technically this question is more about logic, so bound checking is not in the picture here. You are right A2 cannot be instantiated :) – tribbloid Apr 28 '23 at 20:22
  • https://twitter.com/tribbloid/status/1652412025690116097 – Dmytro Mitin May 01 '23 at 03:04

1 Answers1

2

type EE = Supe.this.E is just type EE = E.

There's no much difference between Case 1 and Case 2. Both are illegal inheritance. Illegal both in Scala 2 and Scala 3. The difference between Scala 2 and Scala 3 is just when you get an error. In Scala 2 you get an error immediately, in Scala 3 you get an error a little later, when you implement a trait with a class.

// Scala 2

object Case1 {
  trait Sub {
    type EE
  }

  trait S1 extends Sub {
    type EE = Product
  }

  trait S2 extends Sub {
    type EE = Tuple
  }

  trait A1 extends S1
  trait A2 extends A1 with S2 //trait A2 inherits conflicting members: type EE = Product (defined in trait S1) and type EE = Tuple (defined in trait S2)
}

object Case2 {
  trait Supe {
    type E

    trait Sub {
      type EE = Supe.this.E
    }
  }

  object S1 extends Supe {
    type E = Product
  }

  object S2 extends Supe {
    type E = Tuple
  }

  trait A1 extends S1.Sub
  trait A2 extends A1 with S2.Sub //illegal inheritance; trait A2 inherits different type instances of trait Sub: Case2.S2.Sub and Case2.S1.Sub
}
// Scala 3

object Case1 {
  trait Sub {
    type EE
  }

  trait S1 extends Sub {
    type EE = Product
  }

  trait S2 extends Sub {
    type EE = Tuple
  }

  trait A1 extends S1
  trait A2 extends A1 with S2 //error overriding type EE in trait S1, which equals Product
}

object Case2 {
  trait Supe {
    type E

    trait Sub {
      type EE = Supe.this.E
    }
  }

  object S1 extends Supe {
    type E = Product
  }

  object S2 extends Supe {
    type E = Tuple
  }

  trait A1 extends S1.Sub
  trait A2 extends A1 with S2.Sub

  // added
  class A3 extends A2 // class A3 cannot be instantiated since it has conflicting base types Case2.S1.Sub and Case2.S2.Sub
  val a2 = new A2 {} // anonymous class Object with Case2.A2 {...} cannot be instantiated since it has conflicting base types Case2.S1.Sub and Case2.S2.Sub
}

I guess I've seen this specifics of Scala 3 discussed at the bug tracker. If I find the link I'll add it.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Good point, but even if the compiler switch to V2 behaviour, it is still unclear if `this.type` is a dependent type or bound. – tribbloid May 01 '23 at 18:12
  • @tribbloid Not sure I understand the question. The spec about types is here https://scala-lang.org/files/archive/spec/2.13/03-types.html Which specific part is not clear? – Dmytro Mitin May 01 '23 at 20:18
  • last time I remembered Scala 2.13 doesn't abide to DOT calculus. In addition it should give answer to a simple question: is `this.type` a dependent type or something else? – tribbloid May 01 '23 at 20:38
  • @tribbloid Why should it give such answer? Can you see that the term "path-dependent type" is absent in the spec? – Dmytro Mitin May 02 '23 at 09:00
  • 1
    @tribbloid In the spec there is notion of **path** and **type**. `x.y.z` and `x.y.C.this` (for a class `x.y.C`) are paths, `x.y.z.T` (aka `x.y.z.type#T`), `x.y.C#T`, `x.y.C.this.T` (aka `x.y.C.this.type#T`) are types. You can call them path-dependent types. "Path-dependent type" is a term from popular explanation https://docs.scala-lang.org/scala3/book/types-dependent-function.html , not from the spec. – Dmytro Mitin May 02 '23 at 11:21
  • 1
    @tribbloid From implementation point of view, `this.type` is `ThisType`, `1` and `"a"` are `ConstantType`, `x.type` is `SingleType`, `x.T` (aka `x.type#T`) and `A#T` are `TypeRef` https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/internal/Types.scala https://github.com/scala/scala/blob/2.13.x/src/reflect/scala/reflect/api/Types.scala https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/dotc/core/Types.scala https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala – Dmytro Mitin May 02 '23 at 11:21
  • @tribbloid Not all questions make sense. When you ask whether `this.type` is a path-dependent type try to formulate better what you're actually asking. – Dmytro Mitin May 02 '23 at 11:22
  • 1
    IMHO path-dependent type is a logic concept fully formalized in "M. Rapoport and O. Lhoták, “A Path To DOT: Formalizing Fully Path-Dependent Types,” Apr. 2019, Accessed: Jun. 26, 2021. [Online]. Available: https://arxiv.org/abs/1904.07298" – tribbloid May 02 '23 at 21:41
  • The preceding paper by Amin. et al. first proposed an incomplete definition in the definition of system D – tribbloid May 02 '23 at 21:43
  • 1
    @tribbloid Rapoport and Lhoták's pDOT miss a rule that if a path `p` is well-typed then we have `p : p.type`. We've fixed this in our cDOT paper (https://dl.acm.org/doi/abs/10.1145/3563342). Note that `p` can be essentially `this`. – Lionel Parreaux May 09 '23 at 15:59