Description
When testing HasThisType
trait defined in following from (PThis
parameter itself is invariant, but recursive constraint is deliberately covariant)
trait HasThisType[PThis <: HasThisType[_ <: PThis]] {
this: PThis =>
type This = PThis
}
During verification whether "Self-type" constraint is visible or not in particular contexts (especially from outside of that trait) some problems with HasThisType[_]
and generic methods signatures was revealed.
Part1
In particular, snippet (complete code here)
def testSelf[PThis <: HasThisType[_ <: PThis]](that: PThis with HasThisType[PThis]): Unit = {
// some asserts here
}
val that: HasThisType[_] = ???
// this line of code makes Dotty compiler infinite recursion (stopped only by overflow)
testSelf(that)
makes Dotty compiler crash
assertion failure for Main.HasThisType[_ <: LazyRef(Main.HasThisType[_]#PThis)] <:< Main.HasThisType ...
After rephrasing generic method signature to some alternative equivalents
// rewrite#1
def testSelf[PThis <: HasThisType[_ <: PThis], That <: PThis with HasThisType[PThis]](that: That): Unit = ???
// rewrite#2
type SelfForHasThisType[PThis <: HasThisType[_ <: PThis]] = PThis with HasThisType[PThis]
def testSelf[PThis <: HasThisType[_ <: PThis]](that: SelfForHasThisType[PThis]): Unit = ???
compiler crash could be reduced to regular compilation error.
While for "rewrite#2" compiler provides some error message of quite reasonable size (couple of lines)
for "rewrite#1" it shows "significantly" multiline error message like this:
42 | testSelf(that)
| ^
|Recursion limit exceeded.
|Maybe there is an illegal cyclic reference?
|If that's not the case, you could also try to increase the stacksize using the -Xss JVM option.
|A recurring operation is (inner to outer):
|
| subtype Main.HasThisType[_ <: LazyRef(Main.HasThisType[_ <: LazyRef(PThis)]#PThis)] <:< Nothing
... - 20 more similar lines here
| subtype Main.HasThisType[_ <: LazyRef(PThis)] <:< Main.HasThisType[PThis]
Also, essentially, that problem with tricky signature reproduces only with argument of wildcard (HasThisType[_]
) type.
For normal (not wildcard) types matching that signature everything works well
(complete code here)
def testSelf[PThis <: HasThisType[_ <: PThis]](that: PThis with HasThisType[PThis]): Unit = {
// some asserts here
}
// this lines works as expected
val that: Foo = Foo()
testSelf(that)
Other snippet shows that problem with aforementioned signature could also happens not only when signature doesn't match passed argument, but in potentially positive case (but when some wildcard-ed type need to be passed/inferred through 'PThis')
Considering following example (complete code here)
def testSelf[PThis <: HasThisType[_ <: PThis]](that: PThis with HasThisType[PThis]): Unit = {
// some asserts here
}
val that3: Bar = Bar()
// this line of code makes Dotty compiler runtime crash - comment it to make it compilable again
testSelf(that3)
// ...
// `HasThisType` instantiation/sub-classing
trait FooLike[PThis <: FooLike[_ <: PThis]] extends HasThisType[PThis] {
this: PThis =>
}
case class Foo(payload: Any = "dummy") extends FooLike[Foo]
case class Bar(dummy: Any = "payload") extends FooLike[FooLike[_]]
One may expect no error there, while in fact it end up with similar compile runtime crash.
However in this case one may resolve problem "manually" by explicitly hinting PThis
parameter (instead of expecting it's correct inference).
So that following fixed (explicit) snippet (complete code here)
val that3: Bar = Bar()
// provide `PThis` generic method parameter explicitly
testSelf[PThis=FooLike[_ <: FooLike[_]]](that3)
will become compilable under Dotty compiler
Eventually, in case of invariant PThis
parameter definition, which may looks like
trait HasThisType[PThis <: HasThisType[PThis]] {
this: PThis =>
type This = PThis
}
Runtime crash is not reproducible, however unreasonably long compilation error messages still appears.
Demonstrating snippet
def testSelf[PThis <: HasThisType[PThis]](that: PThis with HasThisType[PThis]): Unit = {
// some asserts here
}
val that: HasThisType[_] = ???
// this line of code makes Dotty compiler uncontrolled recursion which produces unreasonably long error message
testSelf(that)
Also rewriting signature using intermediate aliasing type as
type SelfForHasThisType[PThis <: HasThisType[PThis]] = PThis with HasThisType[PThis]
def testSelf[PThis <: HasThisType[PThis]](that: SelfForHasThisType[PThis]): Unit = ???
allows to see error message of more reasonable size, and may look like
41 | testSelf(that)
| ^^^^
|Found: Main.HasThisType[_](that)
|Required: SelfForHasThisType[PThis]
|
|where: PThis is a type variable with constraint <: Main.HasThisType[LazyRef(PThis)]