Skip to content

Commit 3be3813

Browse files
noti0na1tgodzik
authored andcommitted
Add terminated info
1 parent eda8749 commit 3be3813

File tree

4 files changed

+39
-37
lines changed

4 files changed

+39
-37
lines changed

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

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,37 +49,45 @@ object Nullables:
4949
TypeBoundsTree(lo, newHi, alias)
5050

5151
/** A set of val or var references that are known to be not null
52-
* after the tree finishes executing normally (non-exceptionally),
52+
* after the tree finishes executing normally (non-exceptionally),
5353
* plus a set of variable references that are ever assigned to null,
5454
* and may therefore be null if execution of the tree is interrupted
5555
* by an exception.
5656
*/
57-
case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]):
57+
case class NotNullInfo(asserted: Set[TermRef] | Null, retracted: Set[TermRef]):
5858
def isEmpty = this eq NotNullInfo.empty
5959

6060
def retractedInfo = NotNullInfo(Set(), retracted)
6161

62+
def terminatedInfo = NotNullInfo(null, retracted)
63+
6264
/** The sequential combination with another not-null info */
6365
def seq(that: NotNullInfo): NotNullInfo =
6466
if this.isEmpty then that
6567
else if that.isEmpty then this
66-
else NotNullInfo(
67-
this.asserted.diff(that.retracted).union(that.asserted),
68-
this.retracted.union(that.retracted))
68+
else
69+
val newAsserted =
70+
if this.asserted == null || that.asserted == null then null
71+
else this.asserted.diff(that.retracted).union(that.asserted)
72+
val newRetracted = this.retracted.union(that.retracted)
73+
NotNullInfo(newAsserted, newRetracted)
6974

7075
/** The alternative path combination with another not-null info. Used to merge
71-
* the nullability info of the two branches of an if.
76+
* the nullability info of the branches of an if or match.
7277
*/
7378
def alt(that: NotNullInfo): NotNullInfo =
74-
NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted))
75-
76-
def withRetracted(that: NotNullInfo): NotNullInfo =
77-
NotNullInfo(this.asserted, this.retracted.union(that.retracted))
79+
val newAsserted =
80+
if this.asserted == null then that.asserted
81+
else if that.asserted == null then this.asserted
82+
else this.asserted.intersect(that.asserted)
83+
val newRetracted = this.retracted.union(that.retracted)
84+
NotNullInfo(newAsserted, newRetracted)
85+
end NotNullInfo
7886

7987
object NotNullInfo:
8088
val empty = new NotNullInfo(Set(), Set())
81-
def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo =
82-
if asserted.isEmpty && retracted.isEmpty then empty
89+
def apply(asserted: Set[TermRef] | Null, retracted: Set[TermRef]): NotNullInfo =
90+
if asserted != null && asserted.isEmpty && retracted.isEmpty then empty
8391
else new NotNullInfo(asserted, retracted)
8492
end NotNullInfo
8593

@@ -202,7 +210,7 @@ object Nullables:
202210
*/
203211
@tailrec def impliesNotNull(ref: TermRef): Boolean = infos match
204212
case info :: infos1 =>
205-
if info.asserted.contains(ref) then true
213+
if info.asserted != null && info.asserted.contains(ref) then true
206214
else if info.retracted.contains(ref) then false
207215
else infos1.impliesNotNull(ref)
208216
case _ =>
@@ -218,7 +226,9 @@ object Nullables:
218226
/** Retract all references to mutable variables */
219227
def retractMutables(using Context) =
220228
val mutables = infos.foldLeft(Set[TermRef]()):
221-
(ms, info) => ms.union(info.asserted.filter(_.symbol.is(Mutable)))
229+
(ms, info) => ms.union(
230+
if info.asserted == null then Set.empty
231+
else info.asserted.filter(_.symbol.is(Mutable)))
222232
infos.extendWith(NotNullInfo(Set(), mutables))
223233

224234
end extension
@@ -491,7 +501,10 @@ object Nullables:
491501
&& assignmentSpans.getOrElse(sym.span.start, Nil).exists(whileSpan.contains(_))
492502
&& ctx.notNullInfos.impliesNotNull(ref)
493503

494-
val retractedVars = ctx.notNullInfos.flatMap(_.asserted.filter(isRetracted)).toSet
504+
val retractedVars = ctx.notNullInfos.flatMap(info =>
505+
if info.asserted == null then Set.empty
506+
else info.asserted.filter(isRetracted)
507+
).toSet
495508
ctx.addNotNullInfo(NotNullInfo(Set(), retractedVars))
496509
end whileContext
497510

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

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,13 +1333,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13331333

13341334
def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo)
13351335
def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo)
1336-
result.withNotNullInfo(
1337-
if result.thenp.tpe.isNothingType then
1338-
elsePathInfo.withRetracted(thenPathInfo)
1339-
else if result.elsep.tpe.isNothingType then
1340-
thenPathInfo.withRetracted(elsePathInfo)
1341-
else thenPathInfo.alt(elsePathInfo)
1342-
)
1336+
result.withNotNullInfo(thenPathInfo.alt(elsePathInfo))
13431337
end typedIf
13441338

13451339
/** Decompose function prototype into a list of parameter prototypes and a result
@@ -1875,14 +1869,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18751869
}
18761870

18771871
private def notNullInfoFromCases(initInfo: NotNullInfo, cases: List[CaseDef])(using Context): NotNullInfo =
1878-
var nnInfo = initInfo
18791872
if cases.nonEmpty then
1880-
val (nothingCases, normalCases) = cases.partition(_.body.tpe.isNothingType)
1881-
nnInfo = nothingCases.foldLeft(nnInfo):
1882-
(nni, c) => nni.withRetracted(c.notNullInfo)
1883-
if normalCases.nonEmpty then
1884-
nnInfo = nnInfo.seq(normalCases.map(_.notNullInfo).reduce(_.alt(_)))
1885-
nnInfo
1873+
initInfo.seq(cases.map(_.notNullInfo).reduce(_.alt(_)))
1874+
else initInfo
18861875

18871876
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType: Type, pt: Type)(using Context): List[CaseDef] =
18881877
var caseCtx = ctx
@@ -1970,7 +1959,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
19701959
def typedLabeled(tree: untpd.Labeled)(using Context): Labeled = {
19711960
val bind1 = typedBind(tree.bind, WildcardType).asInstanceOf[Bind]
19721961
val expr1 = typed(tree.expr, bind1.symbol.info)
1973-
assignType(cpy.Labeled(tree)(bind1, expr1))
1962+
assignType(cpy.Labeled(tree)(bind1, expr1)).withNotNullInfo(expr1.notNullInfo.retractedInfo)
19741963
}
19751964

19761965
/** Type a case of a type match */
@@ -2020,7 +2009,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
20202009
// Hence no adaptation is possible, and we assume WildcardType as prototype.
20212010
(from, proto)
20222011
val expr1 = typedExpr(tree.expr orElse untpd.syntheticUnitLiteral.withSpan(tree.span), proto)
2023-
assignType(cpy.Return(tree)(expr1, from))
2012+
assignType(cpy.Return(tree)(expr1, from)).withNotNullInfo(expr1.notNullInfo.terminatedInfo)
20242013
end typedReturn
20252014

20262015
def typedWhileDo(tree: untpd.WhileDo)(using Context): Tree =
@@ -2107,15 +2096,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
21072096
def typedThrow(tree: untpd.Throw)(using Context): Tree =
21082097
val expr1 = typed(tree.expr, defn.ThrowableType)
21092098
val cap = checkCanThrow(expr1.tpe.widen, tree.span)
2110-
val res = Throw(expr1).withSpan(tree.span)
2099+
var res = Throw(expr1).withSpan(tree.span)
21112100
if Feature.ccEnabled && !cap.isEmpty && !ctx.isAfterTyper then
21122101
// Record access to the CanThrow capabulity recovered in `cap` by wrapping
2113-
// the type of the `throw` (i.e. Nothing) in a `@requiresCapability` annotatoon.
2114-
Typed(res,
2102+
// the type of the `throw` (i.e. Nothing) in a `@requiresCapability` annotation.
2103+
res = Typed(res,
21152104
TypeTree(
21162105
AnnotatedType(res.tpe,
21172106
Annotation(defn.RequiresCapabilityAnnot, cap, tree.span))))
2118-
else res
2107+
res.withNotNullInfo(expr1.notNullInfo.terminatedInfo)
21192108

21202109
def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral = {
21212110
val elemProto = pt.stripNull.elemType match {

tests/explicit-nulls/neg/flow-early-exit.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Foo(x: String|Null) {
4949
def retTypeNothing(): String = {
5050
val y: String|Null = ???
5151
if (y == null) err("y is null!")
52-
y
52+
y // error
5353
}
5454

5555
def errRetUnit(msg: String): Unit = {

tests/explicit-nulls/neg/flow-simple-var.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class SimpleVar {
1919
}
2020

2121
assert(x != null)
22-
val a: String = x
22+
val a: String = x // error
2323
x = nullable(x)
2424
val b: String = x // error: x might be null
2525
}

0 commit comments

Comments
 (0)