Skip to content

Commit be022bb

Browse files
noti0na1tgodzik
authored andcommitted
Refactor NotNullInfo to record every reference which is retracted once.
[Cherry-picked 585dda9]
1 parent be7c860 commit be022bb

File tree

4 files changed

+100
-15
lines changed

4 files changed

+100
-15
lines changed

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,35 +48,49 @@ object Nullables:
4848
val newHi = if needNullifyHi(lo.typeOpt, hiTpe) then TypeTree(OrType(hiTpe, defn.NullType, soft = false)) else hi
4949
TypeBoundsTree(lo, newHi, alias)
5050

51-
/** A set of val or var references that are known to be not null, plus a set of
52-
* variable references that are not known (anymore) to be not null
51+
/** A set of val or var references that are known to be not null,
52+
* a set of variable references that are not known (anymore) to be not null,
53+
* plus a set of variables that are known to be not null at any point.
5354
*/
54-
case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]):
55+
case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]):
5556
assert((asserted & retracted).isEmpty)
57+
assert(retracted.subsetOf(onceRetracted))
5658

5759
def isEmpty = this eq NotNullInfo.empty
5860

59-
def retractedInfo = NotNullInfo(Set(), retracted)
61+
def retractedInfo = NotNullInfo(Set(), retracted, onceRetracted)
62+
63+
def onceRetractedInfo = NotNullInfo(Set(), onceRetracted, onceRetracted)
6064

6165
/** The sequential combination with another not-null info */
6266
def seq(that: NotNullInfo): NotNullInfo =
6367
if this.isEmpty then that
6468
else if that.isEmpty then this
6569
else NotNullInfo(
6670
this.asserted.union(that.asserted).diff(that.retracted),
67-
this.retracted.union(that.retracted).diff(that.asserted))
71+
this.retracted.union(that.retracted).diff(that.asserted),
72+
this.onceRetracted.union(that.onceRetracted))
6873

6974
/** The alternative path combination with another not-null info. Used to merge
7075
* the nullability info of the two branches of an if.
7176
*/
7277
def alt(that: NotNullInfo): NotNullInfo =
73-
NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted))
78+
NotNullInfo(
79+
this.asserted.intersect(that.asserted),
80+
this.retracted.union(that.retracted),
81+
this.onceRetracted.union(that.onceRetracted))
82+
83+
def withOnceRetracted(that: NotNullInfo): NotNullInfo =
84+
if that.isEmpty then this
85+
else NotNullInfo(this.asserted, this.retracted, this.onceRetracted.union(that.onceRetracted))
7486

7587
object NotNullInfo:
76-
val empty = new NotNullInfo(Set(), Set())
88+
val empty = new NotNullInfo(Set(), Set(), Set())
7789
def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo =
78-
if asserted.isEmpty && retracted.isEmpty then empty
79-
else new NotNullInfo(asserted, retracted)
90+
apply(asserted, retracted, retracted)
91+
def apply(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): NotNullInfo =
92+
if asserted.isEmpty && onceRetracted.isEmpty then empty
93+
else new NotNullInfo(asserted, retracted, onceRetracted)
8094
end NotNullInfo
8195

8296
/** A pair of not-null sets, depending on whether a condition is `true` or `false` */

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,8 +1334,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13341334
def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo)
13351335
def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo)
13361336
result.withNotNullInfo(
1337-
if result.thenp.tpe.isRef(defn.NothingClass) then elsePathInfo
1338-
else if result.elsep.tpe.isRef(defn.NothingClass) then thenPathInfo
1337+
if result.thenp.tpe.isRef(defn.NothingClass) then
1338+
elsePathInfo.withOnceRetracted(thenPathInfo)
1339+
else if result.elsep.tpe.isRef(defn.NothingClass) then
1340+
thenPathInfo.withOnceRetracted(elsePathInfo)
13391341
else thenPathInfo.alt(elsePathInfo)
13401342
)
13411343
end typedIf
@@ -2069,10 +2071,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
20692071
}: @unchecked
20702072
val cases2 = cases2x.asInstanceOf[List[CaseDef]]
20712073

2072-
var nni = expr2.notNullInfo.retractedInfo
2074+
// Since we don't know at which point the the exception is thrown in the body,
2075+
// we have to collect any reference that is once retracted.
2076+
var nni = expr2.notNullInfo.onceRetractedInfo
2077+
// It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught.
2078+
// Therefore, the code in the finallizer and after the try block can only rely on the retracted
2079+
// info from the cases' body.
20732080
if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_)))
2081+
20742082
val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni))
20752083
nni = nni.seq(finalizer1.notNullInfo)
2084+
20762085
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni)
20772086
}
20782087

tests/explicit-nulls/neg/i21380c.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ def test4: Int =
3232
case npe: NullPointerException => x = ""
3333
case _ => x = ""
3434
x.length // error
35-
// Although the catch block here is exhaustive,
36-
// it is possible that the exception is thrown and not caught.
37-
// Therefore, the code after the try block can only rely on the retracted info.
35+
// Although the catch block here is exhaustive, it is possible to have non-exhaustive cases,
36+
// and some exceptions are thrown and not caught. Therefore, the code in the finallizer and
37+
// after the try block can only rely on the retracted info from the cases' body.
3838

3939
def test5: Int =
4040
var x: String | Null = null

tests/explicit-nulls/neg/i21619.scala

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
def test1: String =
2+
var x: String | Null = null
3+
x = ""
4+
var i: Int = 1
5+
try
6+
i match
7+
case _ =>
8+
x = null
9+
throw new Exception()
10+
x = ""
11+
catch
12+
case e: Exception =>
13+
x.replace("", "") // error
14+
15+
def test2: String =
16+
var x: String | Null = null
17+
x = ""
18+
var i: Int = 1
19+
try
20+
i match
21+
case _ =>
22+
x = null
23+
throw new Exception()
24+
x = ""
25+
catch
26+
case e: Exception =>
27+
x = "e"
28+
x.replace("", "") // error
29+
30+
def test3: String =
31+
var x: String | Null = null
32+
x = ""
33+
var i: Int = 1
34+
try
35+
i match
36+
case _ =>
37+
x = null
38+
throw new Exception()
39+
x = ""
40+
catch
41+
case e: Exception =>
42+
finally
43+
x = "f"
44+
x.replace("", "") // ok
45+
46+
def test4: String =
47+
var x: String | Null = null
48+
x = ""
49+
var i: Int = 1
50+
try
51+
try
52+
if i == 1 then
53+
x = null
54+
throw new Exception()
55+
else
56+
x = ""
57+
catch
58+
case _ =>
59+
x = ""
60+
catch
61+
case _ =>
62+
x.replace("", "") // error

0 commit comments

Comments
 (0)