Issues related to possible `HasThisType` implementation in Scala/Dotty

Also in previous posts (1, 2) where highlighted possible need of some ThisLikeN[T1,...TN]-types (HasThisLikeTypeN traits). Actually approach#1 could not be used here (constraint this.type <: this.ThisLike[T1,...Tn] could not be achieved with regular abstract types, only self-types could provide that in this case).
Meanwhile approach#2 and approach#3 could be easily adapted to this use case.

So starting from this basic skeletons

    trait HasThisType1[PThisLike1[_], T1] {
      type ThisLike1[P1] = PThisLike1[P1]
    }
    trait HasThisTypeHolder {
      type PThisLike1[_]
      trait HasThisType1[T1] {
        type ThisLike1[P1] = PThisLike1[P1]
      }
    }

final implementations may look like described below.


Approach#2

applied to HasThisLikeN / ThisLike[T1, ... ,TN] could look like this

    // covariant version
    trait HasThisType1[PThisLike1[P1] <: HasThisType1[_ <: PThisLike1, P1], T1] {
      this: PThisLike1[T1] =>
      type ThisLike1[P1] = PThisLike1[P1]

      inline
      def self(): ThisLike1[T1] with this.type = this
    }

    trait HasThisType2[PThisLike2[P1,P2] <: HasThisType2[_ <: PThisLike2, P1, P2], T1,T2] {
      this: PThisLike2[T1,T2] =>
      type ThisLike2[P1,P2] = PThisLike2[P1,P2]

      inline
      def self(): ThisLike2[T1,T2] with this.type = this
    }

Invariant version (with stronger constraint like #PThisLike1#PThisLike1 =:= #PThisLike1) will become

    // invariant version
    trait HasThisType1[PThisLike1[P1] <: HasThisType1[PThisLike1, P1], T1] {
      this: PThisLike1[T1] =>
      type ThisLike1[P1] = PThisLike1[P1]

      // inline
      def self(): ThisLike1[T1] with this.type = this
    }

    trait HasThisType2[PThisLike2[P1,P2] <: HasThisType2[PThisLike2, P1, P2], T1,T2] {
      this: PThisLike2[T1,T2] =>
      type ThisLike2[P1,P2] = PThisLike2[P1,P2]

      // inline
      def self(): ThisLike2[T1,T2] with this.type = this
    }

Again constraint#1 (.type <: .ThisLike1[T1]) and constraint#2 (#ThisLike1[_]#ThisLike1[P1] <: #ThisLike1[P1]) could be easily seen inside class/trait body
(like this.type <: this.ThisLike1[T1], etc), while outside constraint#1 could be exposed through that.self().type <: that.ThisLike[T1] (where that: HasThisType1[T1]), or again alternatively one may define auxiliary type type ThisLikeB <: this.type <: ThisLike1[T1] and expose that as that.type <: that.ThisLikeB <: ThisLike1[T1]
(complete example here) (see also issue#5878, issue#5879)

Bigger snippet that reveals that in Scala, at least inside of enclosing class/trait body that approach works as expected (`this.type <: this.ThisLike1[T1]` and `this.type#ThisLike1[_]#ThisLike1[P1] <: this.type#ThisLike1[P1]`). Briefly it will look like:
    trait FooLike[PFooLike[P1] <: FooLike[PFooLike, P1], T1] extends HasThisType1[PFooLike, T1] {
      this: PFooLike[T1] =>

      def doSmth(): ThisLike1[T1]
      def doSmth2(): ThisLike1[T1] = self().doSmth().doSmth()

      def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1]
      def applySmth2[P1](arg: ThisLike1[P1]): ThisLike1[P1] = doSmth2().applySmth(arg.doSmth2()).doSmth2()

    }

    case class Foo[T1](any: Any, tag: T1) extends FooLike[Foo,T1] {

      override def doSmth(): ThisLike1[T1] = Foo(s"doSmth($this)", tag)
      override def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1] = Foo(s"($this)applySmth($arg)", arg.tag)
    }

Approach#3

applied to HasThisLikeN / ThisLike[T1, ... ,TN] could look like this

    trait HasThisTypeHolder {
      // `PThisLike1` serves as generic parameter replacement to make usage in sub classes bit easier
      type PThisLike1[P1] <: HasThisLikeType1[P1]

      trait HasThisLikeType1[T1] {
        this: PThisLike1[T1] =>

        type ThisLike1[P1] = PThisLike1[P1]

        // inline
        def self(): ThisLike1[T1] with this.type = this
      }

      // `PThisLike2` serves as generic parameter replacement to make usage in sub classes bit easier
      type PThisLike2[P1,P2] <: HasThisLikeType2[P1,P2]

      trait HasThisLikeType2[T1,T2] {
        this: PThisLike2[T1,T2] =>

        type ThisLike2[P1,P2] = PThisLike2[P1,P2]

        // inline
        def self(): ThisLike2[T1,T2] with this.type = this
      }
    }
Bigger snippet that reveals that in Scala, at least inside of enclosing class/trait body that approach works as expected (`this.type <: this.ThisLike1[T1]` and `this.type#ThisLike1[_]#ThisLike1[P1] <: this.type#ThisLike1[P1]`). Briefly will look like snippet inside
    trait FooLikeHolder extends HasThisTypeHolder {
      type PThisLike1[P1] <: FooLike[P1]

      type PFooLike[P1] = PThisLike1[P1]

      trait FooLike[T1] extends HasThisLikeType1[T1] {
        this: PThisLike1[T1] =>

        def doSmth(): ThisLike1[T1]
        def doSmth2(): ThisLike1[T1] = self().doSmth().doSmth()
        def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1]
        def applySmth2[P1](arg: ThisLike1[P1]): ThisLike1[P1] = doSmth2().applySmth(arg.doSmth2()).doSmth2()
      }
    }

    object fooHolder extends FooLikeHolder {
      type PThisLike1[P1] = Foo[P1]

      case class Foo[T1](any: Any, tag: T1) extends FooLike[T1] {

        override def doSmth(): ThisLike1[T1] = Foo(s"doSmth($this)", tag)
        override def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1] = Foo(s"($this)applySmth($arg)", arg.tag)
      }
    }