bug
bug copied to clipboard
Stack overflow in ref-check of a method-local type alias refining a contravariant type parameter of the enclosing class
Reproduction steps
In Scala 2.13.8
type Type[T] = T { type X = Type[T] }
Another case:
trait Contra[-F] {
def method() :Unit = {
type Refine = F { type Self <: Refine }
}
}
Problem
Compiler's output:
java.lang.StackOverflowError
at scala.reflect.internal.Symbols$Symbol.variance(Symbols.scala:1160)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:126)
at scala.reflect.internal.Types$TypeBounds.$anonfun$mapOver$1(Types.scala:1575)
at scala.reflect.internal.Types$TypeBounds.mapOver(Types.scala:1575)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:130)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$2(Variances.scala:106)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$1(Variances.scala:106)
at scala.reflect.internal.Scopes$Scope.foreach(Scopes.scala:455)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.mapOver(Variances.scala:106)
at scala.reflect.internal.Types$RefinedType.mapOver(Types.scala:1863)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:127)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:125)
at scala.reflect.internal.Types$TypeBounds.mapOver(Types.scala:1578)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:130)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$2(Variances.scala:106)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$1(Variances.scala:106)
at scala.reflect.internal.Scopes$Scope.foreach(Scopes.scala:455)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.mapOver(Variances.scala:106)
at scala.reflect.internal.Types$RefinedType.mapOver(Types.scala:1863)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:127)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:125)
at scala.reflect.internal.Types$TypeBounds.mapOver(Types.scala:1578)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:130)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$2(Variances.scala:106)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$1(Variances.scala:106)
at scala.reflect.internal.Scopes$Scope.foreach(Scopes.scala:455)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.mapOver(Variances.scala:106)
at scala.reflect.internal.Types$RefinedType.mapOver(Types.scala:1863)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:127)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:125)
at scala.reflect.internal.Types$TypeBounds.mapOver(Types.scala:1578)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:130)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$2(Variances.scala:106)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$1(Variances.scala:106)
at scala.reflect.internal.Scopes$Scope.foreach(Scopes.scala:455)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.mapOver(Variances.scala:106)
at scala.reflect.internal.Types$RefinedType.mapOver(Types.scala:1863)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:127)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:125)
at scala.reflect.internal.Types$TypeBounds.mapOver(Types.scala:1578)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.apply(Variances.scala:130)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$2(Variances.scala:106)
at scala.reflect.internal.Variances$VarianceValidator$ValidateVarianceMap$.$anonfun$mapOver$1_
```_
This must be my personal record: smallest example and three days of narrowing it down from my project :.(
It doesn't matter that it's contravariant. But we should have got an illegal cyclic reference. I wonder if that check comes after variance or is somehow skipped for local type aliases 🤔
object VarianceRefinement {
trait Contra[F] {
type Refine = F { type Self <: Refine }
def method() :Unit = ()
}
}
scratch/src/main/scala/VarianceRefinement.scala:3:36: illegal cyclic reference involving type Self
[error] type Refine = F { type Self <: Refine }
[error] ^
[error] one error found
Yes, I realised when I narrowed it down to this line. The stack trace being full with variance checks, for quite a bit it didn't even occur to me that the problem may be in a body of a method, rather than its signature. After enabling logging, it is clear that it fails in the ref-check phase.
Nota bene such prohibitions seem quite arbitrary to me; why can't I define a local bound type, but I can do so in an object? As with infamous volatile types, they feel like arbitrary restrictions, because the desired type can often be constructed by dividing its definition into different code units. Back to the cyclic reference issue, this works (in an object or class):
type Refine[F] <: F { type Self <: Refine[F] }
while this doesn't:
type Refine <: F(in trait contra) { type Self <: Refine }
And I can replace all usages of type Refine with Refine[F]. I do not have any experience with the compiler and my perception got somewhat dulled since the university, but they are the same thing to me, to paraphrase a meme.
They are not quite the same because one is an abstract type with a type bound (so could be F-bound) while the other is a type alias. And type aliases cannot be recursive (aka. cyclic) - but it looks like you found a hole in the cyclic type check when the type alias is defined local to a method. In particular the check that should have triggered but didn't is this one: https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/Typers.scala#L2463
It gets better!
type Type[T] = T { type X = Type[T] } //StackOverflow
On the other hand,
type Type[T] = T { type >: Type[T] <: Type[T] }
is ok. I think I'll settle on the explanation that the compiler is simply moody.
(@noresttherein your last example there doesn't compile, type name is missing)