TL;DR: It appears that type parameters of type aliases (e.g. type T[X<:Serializable]
) do not enforce their constraints when referenced as variables, parameters and perhaps other cases. Case classes, however, do enforce the bounds correctly for their parameters.
Consider a type alias designed to represent a subset of generic type. For example, let us say I want a type for lists of Serializable
things:
scala> type SerializableList[T <: Serializable] = List[T]
defined type alias SerializableList
Now say that I want a case class with a parameter of these things:
scala> case class NetworkDataCC(things: SerializableList[_])
<console>:9: error: type arguments [_$1] do not conform to type SerializableList's type parameter bounds [T <: Serializable]
case class NetworkDataCC(things: SerializableList[_])
Well, that doesn't work. Scala (annoyingly) does not carry the parameter bounds with the type, but it's easy to fix:
scala> case class NetworkDataCC(things: SerializableList[_ <: Serializable])
defined class NetworkDataCC
Alright. Looks good. Now, what if I want just a regular class with those things, but I again forget to explicitly declare the type bounds. I expect an error:
scala> class NetworkData(val things: SerializableList[_])
defined class NetworkData
Oh, wait. No error... huh.
So, now I can do this?
scala> new NetworkData(List(1))
res3: NetworkData = NetworkData@e344ad3
Well, that seems quite broken. The case class, works fine of course (because the restrictions were declared):
scala> NetworkDataCC(List(1))
<console>:11: error: type mismatch;
found : Int(1)
required: Serializable
NetworkDataCC(List(1))
In my project, I am making use of reflection to generate some metadata about my classes. The metadata for the non-case-class shows a lack of bounds on things
:
scala> classOf[NetworkData].getDeclaredFields()(0).getGenericType
res0: java.lang.reflect.Type = scala.collection.immutable.List<?>
Whereas the case class is correct:
scala> classOf[NetworkDataCC].getDeclaredFields()(0).getGenericType
res1: java.lang.reflect.Type = scala.collection.immutable.List<? extends scala.Serializable>
I wasn't able to find any bugs in the scala compiler bug tracker for this. Am I misunderstanding how these bounds should be used?
Scala's underscore is not equivalent to
SerializableList[X forSome {type X}]
by default:It is equivalent to
So such "unbounded" behavior is fine. Check out this answer: https://stackoverflow.com/a/15204140/1809978
Case classes seem to have additional type restrictions (due to this bug, which affects
unapply
method, automatically generated for case class).If you want to have "unbounded" existential type in case class, just specify higher-order type explicitly:
Or even:
So this is definitely a bug regarding that you can workaround it with semantically equivalent type alias (see SI-8997)