Is this a compiler bug, or there is some explanation why this does not work?
object Types {
case class S(s: String)
opaque type SS = S
object SS {
def apply(v: String): SS = S(v)
}
}
import Types.*
@main def main = {
val u: SS | Null = SS("Hi")
u match {
case s: S => Some(s.s)
case _ => None
}
}
Error is:
value s is not a member of Types.S & Types.SS | Null
The Null type is very persistent in this code, even using s.nn.s does not help.
The error does not happen when not using opaque types or using opaque type SS >: Null = S,
Tested in Scala 3.7.4 and 3.8.1.
1 Like
(s: S).s does fix the issue
Do also note that Null is only useful if you use the -Yexplicit-nulls, as otherwise every reference type includes null
I highly encourage you to use more diverse names in your reproducers, I had trouble differentiating all the s’s ^^’
From my experimenting, here is what is going on:
s has type (S & SS) | Null and not, as one might assume, S & (SS | Null)
Since Null does not define a member s, the original fails
This does not seem correct, since the pattern s: S will never match null, so S & (SS | Null) makes a lot more sense
s.nn for some reason has type (s : (SS & S) | Null) & SS so basically SS, which does not define a member s
But I would expect it to instead be (s: ...) & (SS & S) subtype of S which does have a member s
Both of those seem like bugs, do you want to file issues, or should I ?
2 Likes
Opaque types are not reference types. You need to either declare Null as a bound, or use union with Null, if you need to express they can be null.
I will file the bugs. I keep a portfolio of them, as something I can be proud of. What I find amazing on Scala community is that most issues I have reported were actually fixed. 
2 Likes
I find the example surprising for yet another reason.
I would have expected the match statement to produce a compile error like “this case is unreachable since type Types.SS | Null is not a subclass of class S”.
Why? Because only within the object Types should it be known that the opaque type SS is actually the same as S.
To enable a match like this outside of Types we should have to declare the type SS as opaque type SS <: S = S (or use a non-opaque type alias in the first place), right?
I think matching often allows you to access types which are not known to be related. I think opaque types behave like Any in this respect, like following which also compiles:
("hi":Any) match {
case s: String => s
}
OTOH, if you replace case s: S => Some(s.s) with case _: Int => Some("") in the original example, it results in the compile error “this case is unreachable since type Types.SS | Null is not a subclass of class Integer”.
1 Like