Skip to content

Commit 03dd157

Browse files
committed
Optimize subtyping ignoring nulls
1 parent 55524c5 commit 03dd157

File tree

7 files changed

+61
-29
lines changed

7 files changed

+61
-29
lines changed

compiler/src/dotty/tools/dotc/core/CheckRealizable.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,11 @@ class CheckRealizable(using Context) {
116116
case _: SingletonType | NoPrefix =>
117117
Realizable
118118
case tp =>
119+
// TODO
120+
// check the realizability of a special AndType
119121
def checkAndType(x: TermRef, y: Type) =
120122
(realizability(x) eq Realizable) && (y <:< x.widen)
121-
val isStableAndType = tp match {
123+
val isRealizableAndType = tp match {
122124
case AndType(tr: TermRef, tp1) =>
123125
checkAndType(tr, tp1)
124126
case AndType(tp1, tr: TermRef) =>
@@ -132,7 +134,7 @@ class CheckRealizable(using Context) {
132134
case tp: OrType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
133135
case _ => false
134136
}
135-
if isStableAndType then Realizable
137+
if isRealizableAndType then Realizable
136138
else if !isConcrete(tp) then NotConcrete
137139
else boundsRealizability(tp).andAlso(memberRealizability(tp))
138140
}

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+38-6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
6565

6666
private var useNecessaryEither = false
6767

68+
private var ignoreNulls = false
69+
6870
/** Is a subtype check in progress? In that case we may not
6971
* permanently instantiate type variables, because the corresponding
7072
* constraint might still be retracted and the instantiation should
@@ -133,12 +135,24 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
133135
}
134136
}
135137

138+
def topLevelSubTypeIgnoringNulls(tp1: Type, tp2: Type): Boolean =
139+
val saved = ignoreNulls
140+
ignoreNulls = true
141+
try topLevelSubType(tp1, tp2)
142+
finally ignoreNulls = saved
143+
136144
def necessarySubType(tp1: Type, tp2: Type): Boolean =
137145
val saved = useNecessaryEither
138146
useNecessaryEither = true
139147
try topLevelSubType(tp1, tp2)
140148
finally useNecessaryEither = saved
141149

150+
def necessarySubTypeIgnoringNulls(tp1: Type, tp2: Type): Boolean =
151+
val saved = useNecessaryEither
152+
useNecessaryEither = true
153+
try topLevelSubTypeIgnoringNulls(tp1, tp2)
154+
finally useNecessaryEither = saved
155+
142156
def testSubType(tp1: Type, tp2: Type): CompareResult =
143157
GADTused = false
144158
if !topLevelSubType(tp1, tp2) then CompareResult.Fail
@@ -333,7 +347,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
333347
case AndType(tp21, tp22) =>
334348
recur(tp1, tp21) && recur(tp1, tp22)
335349
case OrType(tp21, tp22) =>
336-
if (tp21.stripTypeVar eq tp22.stripTypeVar) recur(tp1, tp21)
350+
if ignoreNulls && tp22.isNullType then
351+
tp1.isNullType || recur(tp1, tp21)
352+
else if ignoreNulls && tp21.isNullType then
353+
tp1.isNullType || recur(tp1, tp22)
354+
else if tp21.stripTypeVar eq tp22.stripTypeVar then recur(tp1, tp21)
337355
else secondTry
338356
case TypeErasure.ErasedValueType(tycon1, underlying2) =>
339357
def compareErasedValueType = tp1 match {
@@ -463,10 +481,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
463481
case OrType(tp1, tp2) => containsAnd(tp1) || containsAnd(tp2)
464482
case _ => false
465483

466-
widenOK
467-
|| joinOK
468-
|| (tp1.isSoft || constrainRHSVars(tp2)) && recur(tp11, tp2) && recur(tp12, tp2)
469-
|| containsAnd(tp1) && recur(tp1.join, tp2)
484+
if ignoreNulls && tp12.isNullType then recur(tp11, tp2)
485+
else if ignoreNulls && tp11.isNullType then recur(tp12, tp2)
486+
else widenOK
487+
|| joinOK
488+
|| (tp1.isSoft || constrainRHSVars(tp2)) && recur(tp11, tp2) && recur(tp12, tp2)
489+
|| containsAnd(tp1) && recur(tp1.join, tp2)
470490
case tp1: MatchType =>
471491
val reduced = tp1.reduced
472492
if (reduced.exists) recur(reduced, tp2) else thirdTry
@@ -632,6 +652,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
632652
case Some(b) => return b
633653
case _ =>
634654

655+
if ignoreNulls then
656+
if tp22.isNullType then return recur(tp1, tp21)
657+
if tp21.isNullType then return recur(tp1, tp22)
658+
635659
// The next clause handles a situation like the one encountered in i2745.scala.
636660
// We have:
637661
//
@@ -754,7 +778,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
754778
isSubType(hi1, tp2, approx.addLow) || compareGADT || tryLiftedToThis1
755779
case _ =>
756780
def isNullable(tp: Type): Boolean = tp.widenDealias match {
757-
case tp: TypeRef => tp.symbol.isNullableClass
781+
case tp: TypeRef =>
782+
if ignoreNulls then tp.symbol.isNullableClassAfterErasure
783+
else tp.symbol.isNullableClass
758784
case tp: RefinedOrRecType => isNullable(tp.parent)
759785
case tp: AppliedType => isNullable(tp.tycon)
760786
case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2)
@@ -2550,9 +2576,15 @@ object TypeComparer {
25502576
def topLevelSubType(tp1: Type, tp2: Type)(using Context): Boolean =
25512577
comparing(_.topLevelSubType(tp1, tp2))
25522578

2579+
def topLevelSubTypeIgnoringNulls(tp1: Type, tp2: Type)(using Context): Boolean =
2580+
comparing(_.topLevelSubTypeIgnoringNulls(tp1, tp2))
2581+
25532582
def necessarySubType(tp1: Type, tp2: Type)(using Context): Boolean =
25542583
comparing(_.necessarySubType(tp1, tp2))
25552584

2585+
def necessarySubTypeIgnoringNulls(tp1: Type, tp2: Type)(using Context): Boolean =
2586+
comparing(_.necessarySubTypeIgnoringNulls(tp1, tp2))
2587+
25562588
def isSubType(tp1: Type, tp2: Type)(using Context): Boolean =
25572589
comparing(_.isSubType(tp1, tp2))
25582590

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -648,11 +648,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
648648
tpnme.WILDCARD
649649
case tp: WildcardType =>
650650
sigName(tp.optBounds)
651-
case OrNull(tp) =>
652-
// If explicit nulls is enabled and the type is nullable union,
653-
// we need to strip nulls before computing the signiture name.
654-
// For example, the correct name of `String | Null` is `String` instead of `Object`.
655-
sigName(tp)
651+
// case OrType(tp1) =>
652+
// // If explicit nulls is enabled and the type is nullable union,
653+
// // we need to strip nulls before computing the signiture name.
654+
// // For example, the correct name of `String | Null` is `String` instead of `Object`.
655+
// sigName(tp1)
656656
case _ =>
657657
val erasedTp = this(tp)
658658
assert(erasedTp ne tp, tp)

compiler/src/dotty/tools/dotc/core/Types.scala

+2
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ object Types {
170170
case tp: ExprType => tp.resultType.isStable
171171
case tp: AnnotatedType => tp.parent.isStable
172172
case tp: AndType =>
173+
// if one side is stable and another side is realizable or a subtype,
174+
// then the AndType is stable
173175
def checkAndStable(x: Type, y: Type) =
174176
x.isStable && ((realizability(y) eq Realizable) || y <:< x.widen)
175177
checkAndStable(tp.tp1, tp.tp2) || checkAndStable(tp.tp2, tp.tp1)

compiler/src/dotty/tools/dotc/typer/Implicits.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ object Implicits:
334334
}
335335

336336
/** The implicit references that are eligible for type `tp`. */
337-
def eligible(tp: Type, enableUnsafeNulls: Boolean = false): List[Candidate] =
337+
def eligible(tp: Type, enableUnsafeNulls: Boolean): List[Candidate] =
338338
if tp.hash == NotCached then
339339
Stats.record(i"compute eligible not cached ${tp.getClass}")
340340
Stats.record(i"compute eligible not cached")

compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ trait ImportSuggestions:
157157
// are in the implicit scope of `pt`.
158158
val alreadyAvailableCandidates: Set[Symbol] = {
159159
val wildProto = wildApprox(pt)
160-
val contextualCandidates = ctx.implicits.eligible(wildProto)
160+
val contextualCandidates = ctx.implicits.eligible(wildProto, ctx.mode.is(Mode.UnsafeNullConversion))
161161
val implicitScopeCandidates = ctx.run.implicitScope(wildProto).eligible
162162
val allCandidates = contextualCandidates ++ implicitScopeCandidates
163163
allCandidates.map(_.implicitRef.underlyingRef.symbol).toSet

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

+10-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Uniques._
1515
import config.Printers.typr
1616
import util.SourceFile
1717
import util.Property
18-
import TypeComparer.necessarySubType
18+
import TypeComparer._
1919

2020
import scala.annotation.internal.sharable
2121

@@ -39,10 +39,12 @@ object ProtoTypes {
3939
def isCompatible(tp: Type, pt: Type)(using Context): Boolean =
4040
val tpw = tp.widenExpr
4141
val ptw = pt.widenExpr
42-
(tpw relaxed_<:< ptw)
43-
// If unsafeNulls is enabled, we relax the condition by
44-
// striping all nulls from the types before subtype check.
45-
|| Nullables.convertUnsafeNulls && tpw.isUnsafelyConvertible(ptw, true)
42+
(if Nullables.convertUnsafeNulls then
43+
// If unsafeNulls is enabled, we relax the condition by
44+
// striping all nulls from the types before subtype check.
45+
topLevelSubTypeIgnoringNulls(tpw, ptw)
46+
else tpw <:< ptw)
47+
|| tpw.isValueSubType(ptw)
4648
|| viewExists(tp, pt)
4749

4850
/** Like isCompatibe, but using a subtype comparison with necessary eithers
@@ -51,16 +53,10 @@ object ProtoTypes {
5153
def necessarilyCompatible(tp: Type, pt: Type)(using Context): Boolean =
5254
val tpw = tp.widenExpr
5355
val ptw = pt.widenExpr
54-
necessarySubType(tpw, ptw)
56+
(if Nullables.convertUnsafeNulls then
57+
necessarySubTypeIgnoringNulls(tpw, ptw)
58+
else necessarySubType(tpw, ptw))
5559
|| tpw.isValueSubType(ptw)
56-
|| Nullables.convertUnsafeNulls && {
57-
// See comments in `isCompatible`
58-
val tpwsn = tpw.stripAllNulls
59-
val ptwsn = ptw.stripAllNulls
60-
necessarySubType(tpwsn, ptwsn)
61-
|| tpwsn.isValueSubType(ptwsn)
62-
|| tpwsn.isUnsafelyNulltoAnyRef(ptwsn)
63-
}
6460
|| viewExists(tp, pt)
6561

6662
/** Test compatibility after normalization.

0 commit comments

Comments
 (0)