Skip to content

Commit 3a5aff6

Browse files
committed
Account for side effects in conditions
Generalize not null computations so that any side effects in conditions if if and while expressions are accounted for.
1 parent 4216400 commit 3a5aff6

File tree

2 files changed

+35
-25
lines changed

2 files changed

+35
-25
lines changed

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

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package typer
44

55
import core._
66
import Types._, Contexts._, Symbols._, Decorators._, Constants._
7-
import annotation.{tailrec, infix}
7+
import annotation.tailrec
88
import StdNames.nme
99
import util.Property
1010

@@ -20,14 +20,20 @@ object Nullables with
2020

2121
def isEmpty = this eq NotNullInfo.empty
2222

23+
def retractedInfo = NotNullInfo(Set(), retracted)
24+
2325
/** The sequential combination with another not-null info */
24-
@infix def seq(that: NotNullInfo): NotNullInfo =
26+
def seq(that: NotNullInfo): NotNullInfo =
2527
if this.isEmpty then that
2628
else if that.isEmpty then this
2729
else NotNullInfo(
2830
this.asserted.union(that.asserted).diff(that.retracted),
2931
this.retracted.union(that.retracted).diff(that.asserted))
3032

33+
/** The alternative path combination with another not-null info */
34+
def alt(that: NotNullInfo): NotNullInfo =
35+
NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted))
36+
3137
object NotNullInfo with
3238
val empty = new NotNullInfo(Set(), Set())
3339
def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo =
@@ -124,24 +130,28 @@ object Nullables with
124130

125131
given (tree: Tree)
126132

127-
/* The `tree` with added attachment stating that all paths in `refs` are not-null */
133+
/* The `tree` with added nullability attachment */
128134
def withNotNullInfo(info: NotNullInfo): tree.type =
129135
if !info.isEmpty then tree.putAttachment(NNInfo, info)
130136
tree
131137

132-
def withNotNullRefs(refs: Set[TermRef]) = tree.withNotNullInfo(NotNullInfo(refs, Set()))
133-
134-
/* The paths that are known to be not null after execution of `tree` terminates normally */
138+
/* The nullability info of `tree` */
135139
def notNullInfo(given Context): NotNullInfo =
136140
stripInlined(tree).getAttachment(NNInfo) match
137141
case Some(info) if !curCtx.erasedTypes => info
138142
case _ => NotNullInfo.empty
139143

144+
/* The nullability info of `tree`, assuming it is a condition that evaluates to `c` */
145+
def notNullInfoIf(c: Boolean)(given Context): NotNullInfo =
146+
val cond = tree.notNullConditional
147+
if cond.isEmpty then tree.notNullInfo
148+
else tree.notNullInfo.seq(NotNullInfo(if c then cond.ifTrue else cond.ifFalse, Set()))
149+
140150
/** The paths that are known to be not null if the condition represented
141151
* by `tree` yields `true` or `false`. Two empty sets if `tree` is not
142152
* a condition.
143153
*/
144-
def notNullConditional(given Context): NotNullConditional =
154+
private def notNullConditional(given Context): NotNullConditional =
145155
stripBlock(tree).getAttachment(NNConditional) match
146156
case Some(cond) if !curCtx.erasedTypes => cond
147157
case _ => NotNullConditional.empty
@@ -153,21 +163,20 @@ object Nullables with
153163

154164
/** The current context augmented with nullability information,
155165
* assuming the result of the condition represented by `tree` is the same as
156-
* the value of `tru`. The current context if `tree` is not a condition.
166+
* the value of `c`.
157167
*/
158-
def nullableContext(tru: Boolean)(given Context): Context =
159-
val cond = tree.notNullConditional
160-
if cond.isEmpty then curCtx
161-
else curCtx.addNotNullRefs(if tru then cond.ifTrue else cond.ifFalse)
168+
def nullableContextIf(c: Boolean)(given Context): Context =
169+
val info = tree.notNullInfoIf(c)
170+
if info.isEmpty then curCtx else curCtx.addNotNullInfo(info)
162171

163172
/** The context to use for the arguments of the function represented by `tree`.
164173
* This is the current context, augmented with nullability information
165174
* of the left argument, if the application is a boolean `&&` or `||`.
166175
*/
167176
def nullableInArgContext(given Context): Context = tree match
168177
case Select(x, _) if !curCtx.erasedTypes =>
169-
if tree.symbol == defn.Boolean_&& then x.nullableContext(true)
170-
else if tree.symbol == defn.Boolean_|| then x.nullableContext(false)
178+
if tree.symbol == defn.Boolean_&& then x.nullableContextIf(true)
179+
else if tree.symbol == defn.Boolean_|| then x.nullableContextIf(false)
171180
else curCtx
172181
case _ => curCtx
173182

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -814,23 +814,24 @@ class Typer extends Namer
814814

815815
val result =
816816
if tree.elsep.isEmpty then
817-
val thenp1 = typed(tree.thenp, defn.UnitType)(given cond1.nullableContext(true))
817+
val thenp1 = typed(tree.thenp, defn.UnitType)(given cond1.nullableContextIf(true))
818818
val elsep1 = tpd.unitLiteral.withSpan(tree.span.endPos)
819819
cpy.If(tree)(cond1, thenp1, elsep1).withType(defn.UnitType)
820820
else
821821
val thenp1 :: elsep1 :: Nil = harmonic(harmonize, pt) {
822-
val thenp0 = typed(tree.thenp, pt.dropIfProto)(given cond1.nullableContext(true))
823-
val elsep0 = typed(tree.elsep, pt.dropIfProto)(given cond1.nullableContext(false))
822+
val thenp0 = typed(tree.thenp, pt.dropIfProto)(given cond1.nullableContextIf(true))
823+
val elsep0 = typed(tree.elsep, pt.dropIfProto)(given cond1.nullableContextIf(false))
824824
thenp0 :: elsep0 :: Nil
825825
}
826826
assignType(cpy.If(tree)(cond1, thenp1, elsep1), thenp1, elsep1)
827827

828-
if result.thenp.tpe.isRef(defn.NothingClass) then
829-
result.withNotNullRefs(cond1.notNullConditional.ifFalse)
830-
else if result.elsep.tpe.isRef(defn.NothingClass) then
831-
result.withNotNullRefs(cond1.notNullConditional.ifTrue)
832-
else
833-
result
828+
def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo)
829+
def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo)
830+
result.withNotNullInfo(
831+
if result.thenp.tpe.isRef(defn.NothingClass) then elsePathInfo
832+
else if result.elsep.tpe.isRef(defn.NothingClass) then thenPathInfo
833+
else thenPathInfo.alt(elsePathInfo)
834+
)
834835
end typedIf
835836

836837
/** Decompose function prototype into a list of parameter prototypes and a result prototype
@@ -1261,9 +1262,9 @@ class Typer extends Namer
12611262
val cond1 =
12621263
if (tree.cond eq EmptyTree) EmptyTree
12631264
else typed(tree.cond, defn.BooleanType)
1264-
val body1 = typed(tree.body, defn.UnitType)(given cond1.nullableContext(true))
1265+
val body1 = typed(tree.body, defn.UnitType)(given cond1.nullableContextIf(true))
12651266
assignType(cpy.WhileDo(tree)(cond1, body1))
1266-
.withNotNullRefs(cond1.notNullConditional.ifFalse)
1267+
.withNotNullInfo(body1.notNullInfo.retractedInfo.seq(cond1.notNullInfoIf(false)))
12671268
}
12681269

12691270
def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try = {

0 commit comments

Comments
 (0)