Skip to content

Commit 4ab4b0a

Browse files
committed
Use a different rule for NotNullInfo
1 parent cdbbe5a commit 4ab4b0a

File tree

3 files changed

+50
-47
lines changed

3 files changed

+50
-47
lines changed

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

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

+18-16
Original file line numberDiff line numberDiff line change
@@ -1544,9 +1544,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
15441544
def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo)
15451545
result.withNotNullInfo(
15461546
if result.thenp.tpe.isRef(defn.NothingClass) then
1547-
elsePathInfo.withOnceRetracted(thenPathInfo)
1547+
elsePathInfo.withRetracted(thenPathInfo)
15481548
else if result.elsep.tpe.isRef(defn.NothingClass) then
1549-
thenPathInfo.withOnceRetracted(elsePathInfo)
1549+
thenPathInfo.withRetracted(elsePathInfo)
15501550
else thenPathInfo.alt(elsePathInfo)
15511551
)
15521552
end typedIf
@@ -2153,9 +2153,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
21532153
def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context): Tree = {
21542154
val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto))
21552155
.asInstanceOf[List[CaseDef]]
2156-
var nni = sel.notNullInfo
2157-
if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
2158-
assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nni)
2156+
var nnInfo = sel.notNullInfo
2157+
if cases1.nonEmpty then nnInfo = nnInfo.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
2158+
assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nnInfo)
21592159
}
21602160

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

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

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

2369-
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni)
2370-
}
2370+
val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nnInfo))
2371+
nnInfo = nnInfo.seq(finalizer1.notNullInfo)
2372+
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nnInfo)
23712373

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

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)