Description
scalac Stest.scala
class C {
def get(): Int = 0
}
def g = {
val s: String | Null = ???
val l = s.length // ok
val c: C | Null = ???
c.get() // error: value get is not a member of C | Null
}
Output
-- [E008] Not Found Error: Stest.scala:13:6 ------------------------------------
13 | c.get()
| ^^^^^
| value get is not a member of C | Null
The nullable C
is expected to have similar behaviour as nullable String
.
I think the issue is at TypeOps.orDominator
. String | Null
after join becomes String
. But C | Null
after join becomes Object
.
This code can be compiled using explicit-nulls and unsafeNulls (scalac -Yexplicit-nulls -language:unsafeNulls
), since unsafeNulls will ignore Null type when searching members, then there is no need to join the OrType (see Types.goOr
).
(String | Null).join
can return a correct result, because the type of String
after widenExpr
is different, approximateOr
uses subtyping and lub
to decide the result.
// approximateOr
if ((tp1w ne tp1) || (tp2w ne tp2)) {
val isSingle1 = tp1.isInstanceOf[SingletonType]
val isSingle2 = tp2.isInstanceOf[SingletonType]
return {
if (tp2w eq tp2) orDominator(tp1w | tp2) // 2.1
else if (tp1w eq tp1) orDominator(tp1 | tp2w) // 2.1
else if (tp1w frozen_<:< tp2w) orDominator(tp1w | tp2) // 2.2
else if (tp2w frozen_<:< tp1w) orDominator(tp1 | tp2w) // 2.2
else if (isSingle1 && !isSingle2) orDominator(tp1w | tp2) // 2.3
else if (isSingle2 && !isSingle1) orDominator(tp1 | tp2w) // 2.3
else if (tp1 frozen_<:< tp2w) tp2w // 2.4
else if (tp2 frozen_<:< tp1w) tp1w // 2.5
else orDominator(tp1w | tp2) // 2.6
}
}
However, the type of C
after widenExpr
is the same, approximateOr
uses baseClasses
of C
and Null
to decide. I think we need to handle bottom classes for commonBaseClasses more carefully to fix the issue.
// Step 3: Intersect base classes of both sides
val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect)
val doms = dominators(commonBaseClasses, Nil)
def baseTp(cls: ClassSymbol): Type =
tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied)
doms.map(baseTp).reduceLeft(AndType.apply)
I don't understand the reason to use baseClasses here. Why don't we using subtyping compare in step 3 as well?