Skip to content

Fix treatment of bottom types in OrType#join and baseType computations #11979

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 5, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2098,25 +2098,27 @@ object SymDenotations {
computeTypeProxy

case tp: AndOrType =>
def computeAndOrType = {
def computeAndOrType: Type =
val tp1 = tp.tp1
val tp2 = tp.tp2
if !tp.isAnd then
if tp1.isBottomType && (tp1 frozen_<:< tp2) then return recur(tp2)
if tp2.isBottomType && (tp2 frozen_<:< tp1) then return recur(tp1)
val baseTp =
if (symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty)
if symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty then
symbol.typeRef
else {
else
val baseTp1 = recur(tp1)
val baseTp2 = recur(tp2)
val combined = if (tp.isAnd) baseTp1 & baseTp2 else baseTp1 | baseTp2
combined match {
combined match
case combined: AndOrType
if (combined.tp1 eq tp1) && (combined.tp2 eq tp2) && (combined.isAnd == tp.isAnd) => tp
case _ => combined
}
}

if (baseTp.exists && inCache(tp1) && inCache(tp2)) record(tp, baseTp)
baseTp
}

computeAndOrType

case JavaArrayType(_) if symbol == defn.ObjectClass =>
Expand Down
16 changes: 10 additions & 6 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,16 @@ object TypeOps:
*/
def orDominator(tp: Type)(using Context): Type = {

/** a faster version of cs1 intersect cs2 */
def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = {
val cs2AsSet = new util.HashSet[ClassSymbol](128)
cs2.foreach(cs2AsSet += _)
cs1.filter(cs2AsSet.contains)
}
/** a faster version of cs1 intersect cs2 that treats bottom types correctly */
def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] =
if cs1.head == defn.NothingClass then cs2
else if cs2.head == defn.NothingClass then cs1
else if cs1.head == defn.NullClass && !ctx.explicitNulls && cs2.head.derivesFrom(defn.ObjectClass) then cs2
else if cs2.head == defn.NullClass && !ctx.explicitNulls && cs1.head.derivesFrom(defn.ObjectClass) then cs1
else
val cs2AsSet = new util.HashSet[ClassSymbol](128)
cs2.foreach(cs2AsSet += _)
cs1.filter(cs2AsSet.contains)

/** The minimal set of classes in `cs` which derive all other classes in `cs` */
def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match {
Expand Down
10 changes: 10 additions & 0 deletions tests/pos/i11968.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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
}
48 changes: 48 additions & 0 deletions tests/pos/i11968a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

class A {
def get(): Int = 0
}

class B extends A {}

class C extends A {}

def test1 = {
val s: String | Null = ???
val l = s.length

val a: A | Null = new A
a.get()

val bc: B | C = new B
bc.get()

val bcn: B | (C | Null) = new C
bcn.get()

val bnc: (B | Null) | C = null
bnc.get()

val abcn: A | B | C | Null = new A
abcn.get()
}

def test2 = {
val s: String | Nothing = ???
val l = s.length

val a: A | Nothing = new A
a.get()

val bc: B | C = new B
bc.get()

val bcn: B | (C | Nothing) = new C
bcn.get()

val bnc: (B | Nothing) | C = new B
bnc.get()

val abcn: A | B | C | Nothing = new A
abcn.get()
}
21 changes: 21 additions & 0 deletions tests/pos/i11981.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
object Main:
class Null
type Optional[A] = A | Null

val maybeInt: Optional[Int] = 1

// simplest typeclass
trait TC[F[_]]

// given instances for our Optional and standard Option[_]
given g1: TC[Optional] = ???
given g2: TC[Option] = ???

def summonTC[F[_], A](f: F[A])(using TC[F]): Unit = ???

summonTC(Option(42)) // OK

summonTC[Optional, Int](maybeInt) // OK

summonTC(maybeInt)