Skip to content

Commit bcc9e68

Browse files
committed
Use a different rule for NotNullInfo
1 parent 585dda9 commit bcc9e68

File tree

3 files changed

+50
-47
lines changed

3 files changed

+50
-47
lines changed

Diff for: compiler/src/dotty/tools/dotc/typer/Nullables.scala

+14-30
Original file line numberDiff line numberDiff line change
@@ -53,48 +53,35 @@ object Nullables:
5353
TypeBoundsTree(lo, hiTree, alias)
5454

5555
/** A set of val or var references that are known to be not null,
56-
* a set of variable references that are not known (anymore) to be not null,
57-
* plus a set of variables that are known to be not null at any point.
56+
* plus a set of variable references that are once assigned to null.
5857
*/
59-
case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]):
60-
assert((asserted & retracted).isEmpty)
61-
assert(retracted.subsetOf(onceRetracted))
62-
58+
case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]):
6359
def isEmpty = this eq NotNullInfo.empty
6460

65-
def retractedInfo = NotNullInfo(Set(), retracted, onceRetracted)
66-
67-
def onceRetractedInfo = NotNullInfo(Set(), onceRetracted, onceRetracted)
61+
def retractedInfo = NotNullInfo(Set(), retracted)
6862

6963
/** The sequential combination with another not-null info */
7064
def seq(that: NotNullInfo): NotNullInfo =
7165
if this.isEmpty then that
7266
else if that.isEmpty then this
7367
else NotNullInfo(
74-
this.asserted.union(that.asserted).diff(that.retracted),
75-
this.retracted.union(that.retracted).diff(that.asserted),
76-
this.onceRetracted.union(that.onceRetracted))
68+
this.asserted.diff(that.retracted).union(that.asserted),
69+
this.retracted.union(that.retracted))
7770

7871
/** The alternative path combination with another not-null info. Used to merge
7972
* the nullability info of the two branches of an if.
8073
*/
8174
def alt(that: NotNullInfo): NotNullInfo =
82-
NotNullInfo(
83-
this.asserted.intersect(that.asserted),
84-
this.retracted.union(that.retracted),
85-
this.onceRetracted.union(that.onceRetracted))
75+
NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted))
8676

87-
def withOnceRetracted(that: NotNullInfo): NotNullInfo =
88-
if that.isEmpty then this
89-
else NotNullInfo(this.asserted, this.retracted, this.onceRetracted.union(that.onceRetracted))
77+
def withRetracted(that: NotNullInfo): NotNullInfo =
78+
NotNullInfo(this.asserted, this.retracted.union(that.retracted))
9079

9180
object NotNullInfo:
92-
val empty = new NotNullInfo(Set(), Set(), Set())
81+
val empty = new NotNullInfo(Set(), Set())
9382
def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo =
94-
apply(asserted, retracted, retracted)
95-
def apply(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): NotNullInfo =
96-
if asserted.isEmpty && onceRetracted.isEmpty then empty
97-
else new NotNullInfo(asserted, retracted, onceRetracted)
83+
if asserted.isEmpty && retracted.isEmpty then empty
84+
else new NotNullInfo(asserted, retracted)
9885
end NotNullInfo
9986

10087
/** A pair of not-null sets, depending on whether a condition is `true` or `false` */
@@ -247,16 +234,13 @@ object Nullables:
247234
* or retractions in `info` supersede infos in existing entries of `infos`.
248235
*/
249236
def extendWith(info: NotNullInfo) =
250-
if info.isEmpty
251-
|| info.asserted.forall(infos.impliesNotNull(_))
252-
&& !info.retracted.exists(infos.impliesNotNull(_))
253-
then infos
237+
if info.isEmpty then infos
254238
else info :: infos
255239

256240
/** Retract all references to mutable variables */
257241
def retractMutables(using Context) =
258-
val mutables = infos.foldLeft(Set[TermRef]())((ms, info) =>
259-
ms.union(info.asserted.filter(_.symbol.is(Mutable))))
242+
val mutables = infos.foldLeft(Set[TermRef]()):
243+
(ms, info) => ms.union(info.asserted.filter(_.symbol.is(Mutable)))
260244
infos.extendWith(NotNullInfo(Set(), mutables))
261245

262246
end extension

Diff for: compiler/src/dotty/tools/dotc/typer/Typer.scala

+18-16
Original file line numberDiff line numberDiff line change
@@ -1553,9 +1553,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
15531553
def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo)
15541554
result.withNotNullInfo(
15551555
if result.thenp.tpe.isRef(defn.NothingClass) then
1556-
elsePathInfo.withOnceRetracted(thenPathInfo)
1556+
elsePathInfo.withRetracted(thenPathInfo)
15571557
else if result.elsep.tpe.isRef(defn.NothingClass) then
1558-
thenPathInfo.withOnceRetracted(elsePathInfo)
1558+
thenPathInfo.withRetracted(elsePathInfo)
15591559
else thenPathInfo.alt(elsePathInfo)
15601560
)
15611561
end typedIf
@@ -2150,9 +2150,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
21502150
def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context): Tree = {
21512151
val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto))
21522152
.asInstanceOf[List[CaseDef]]
2153-
var nni = sel.notNullInfo
2154-
if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
2155-
assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nni)
2153+
var nnInfo = sel.notNullInfo
2154+
if cases1.nonEmpty then nnInfo = nnInfo.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
2155+
assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nnInfo)
21562156
}
21572157

21582158
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] =
@@ -2334,7 +2334,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
23342334
val capabilityProof = caughtExceptions.reduce(OrType(_, _, true))
23352335
untpd.Block(makeCanThrow(capabilityProof), expr)
23362336

2337-
def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = {
2337+
def typedTry(tree: untpd.Try, pt: Type)(using Context): Try =
2338+
var nnInfo = NotNullInfo.empty
23382339
val expr2 :: cases2x = harmonic(harmonize, pt) {
23392340
// We want to type check tree.expr first to comput NotNullInfo, but `addCanThrowCapabilities`
23402341
// uses the types of patterns in `tree.cases` to determine the capabilities.
@@ -2346,25 +2347,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
23462347
val casesEmptyBody1 = tree.cases.mapconserve(cpy.CaseDef(_)(body = EmptyTree))
23472348
val casesEmptyBody2 = typedCases(casesEmptyBody1, EmptyTree, defn.ThrowableType, WildcardType)
23482349
val expr1 = typed(addCanThrowCapabilities(tree.expr, casesEmptyBody2), pt.dropIfProto)
2349-
val casesCtx = ctx.addNotNullInfo(expr1.notNullInfo.retractedInfo)
2350+
2351+
// Since we don't know at which point the the exception is thrown in the body,
2352+
// we have to collect any reference that is once retracted.
2353+
nnInfo = expr1.notNullInfo.retractedInfo
2354+
2355+
val casesCtx = ctx.addNotNullInfo(nnInfo)
23502356
val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto)(using casesCtx)
23512357
expr1 :: cases1
23522358
}: @unchecked
23532359
val cases2 = cases2x.asInstanceOf[List[CaseDef]]
23542360

2355-
// Since we don't know at which point the the exception is thrown in the body,
2356-
// we have to collect any reference that is once retracted.
2357-
var nni = expr2.notNullInfo.onceRetractedInfo
23582361
// It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught.
23592362
// Therefore, the code in the finallizer and after the try block can only rely on the retracted
23602363
// info from the cases' body.
2361-
if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_)))
2362-
2363-
val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni))
2364-
nni = nni.seq(finalizer1.notNullInfo)
2364+
if cases2.nonEmpty then
2365+
nnInfo = nnInfo.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_)))
23652366

2366-
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni)
2367-
}
2367+
val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nnInfo))
2368+
nnInfo = nnInfo.seq(finalizer1.notNullInfo)
2369+
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nnInfo)
23682370

23692371
def typedTry(tree: untpd.ParsedTry, pt: Type)(using Context): Try =
23702372
val cases: List[untpd.CaseDef] = tree.handler match

Diff for: tests/explicit-nulls/neg/i21619.scala

+18-1
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,21 @@ def test4: String =
5959
x = ""
6060
catch
6161
case _ =>
62-
x.replace("", "") // error
62+
x.replace("", "") // error
63+
64+
def test5: Unit =
65+
var x: String | Null = null
66+
var y: String | Null = null
67+
x = ""
68+
y = ""
69+
var i: Int = 1
70+
try
71+
i match
72+
case _ =>
73+
x = null
74+
throw new Exception()
75+
x = ""
76+
catch
77+
case _ =>
78+
val z1: String = x.replace("", "") // error
79+
val z2: String = y.replace("", "")

0 commit comments

Comments
 (0)