scaladependent-typescala-3path-dependent-typedotty

In Scala 3 with DOT calculus, is `this.type` a path-dependent type? What makes it special?


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.


Solution

  • 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.