Type alias parameter bounds not enforced in all cases

893 views Asked by At

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?

1

There are 1 answers

0
dk14 On

Scala's underscore is not equivalent to SerializableList[X forSome {type X}] by default:

scala> def aa(a: SerializableList[_]) = a
aa: (a: SerializableList[_])List[Any]

scala> def aa(a: SerializableList[X forSome {type X}]) = a
<console>:11: error: type arguments [X forSome { type X }] do not conform to type SerializableList's type parameter bounds [T <: Serializable]
   def aa(a: SerializableList[X forSome {type X}]) = a
             ^
scala> class NetworkDataCC(things: SerializableList[X forSome {type X}])
<console>:11: error: type arguments [X forSome { type X }] do not conform to typ
e SerializableList's type parameter bounds [T <: Serializable]
   class NetworkDataCC(things: SerializableList[X forSome {type X}])

It is equivalent to

scala> def aa(a: SerializableList[X] forSome {type X} ) = a
aa: (a: SerializableList[_])List[Any]

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:

 scala> case class NetworkDataCC[SerializableList[_]](things: SerializableList[_])
 warning: there were 2 feature warning(s); re-run with -feature for details
 defined class NetworkDataCC

 scala> NetworkDataCC(List(1))
 res5: NetworkDataCC[List] = NetworkDataCC(List(1))

Or even:

 scala> type SLL = SerializableList[_]
 defined type alias SLL

 scala> case class NetworkDataDD(things: SLL)
 defined class NetworkDataDD

So this is definitely a bug regarding that you can workaround it with semantically equivalent type alias (see SI-8997)