@@ -4,31 +4,58 @@ package typer
4
4
5
5
import core ._
6
6
import Types ._ , Contexts ._ , Symbols ._ , Decorators ._ , Constants ._
7
- import annotation .tailrec
7
+ import annotation .{ tailrec , infix }
8
8
import util .Property
9
9
10
10
/** Operations for implementing a flow analysis for nullability */
11
11
object Nullables with
12
12
import ast .tpd ._
13
13
14
- /** A set of paths that are known to be not null */
15
- type Excluded = Set [TermRef ]
14
+ /** A set of val or var references that are known to be not null, plus a set of
15
+ * variable references that are not known (anymore) to be null
16
+ */
17
+ case class NotNullInfo (asserted : Set [TermRef ], retracted : Set [TermRef ])
18
+ assert((asserted & retracted).isEmpty)
19
+
20
+ def isEmpty = this eq noNotNulls
21
+
22
+ /** The sequential combination with another not-null info */
23
+ @ infix def seq (that : NotNullInfo ): NotNullInfo =
24
+ if this .isEmpty then that
25
+ else if that.isEmpty then this
26
+ else NotNullInfo (
27
+ this .asserted.union(that.asserted).diff(that.retracted),
28
+ this .retracted.union(that.retracted).diff(that.asserted))
29
+
30
+ object NotNullInfo with
31
+ def apply (asserted : Set [TermRef ], retracted : Set [TermRef ]): NotNullInfo =
32
+ if asserted.isEmpty && retracted.isEmpty then noNotNulls
33
+ else new NotNullInfo (asserted, retracted)
34
+ end NotNullInfo
35
+
36
+ val noNotNulls = new NotNullInfo (Set (), Set ())
16
37
17
38
/** A pair of not-null sets, depending on whether a condition is `true` or `false` */
18
- case class EitherExcluded (ifTrue : Excluded , ifFalse : Excluded ) with
19
- def isEmpty = ifTrue.isEmpty && ifFalse.isEmpty
39
+ case class NotNullConditional (ifTrue : Set [TermRef ], ifFalse : Set [TermRef ]) with
40
+ def isEmpty = this eq neitherNotNull
41
+
42
+ object NotNullConditional with
43
+ def apply (ifTrue : Set [TermRef ], ifFalse : Set [TermRef ]): NotNullConditional =
44
+ if ifTrue.isEmpty && ifFalse.isEmpty then neitherNotNull
45
+ else new NotNullConditional (ifTrue, ifFalse)
46
+ end NotNullConditional
20
47
21
- val NoneExcluded = EitherExcluded (Set (), Set ())
48
+ val neitherNotNull = new NotNullConditional (Set (), Set ())
22
49
23
50
/** An attachment that represents conditional flow facts established
24
51
* by this tree, which represents a condition.
25
52
*/
26
- private [typer] val CondExcluded = Property .StickyKey [Nullables . EitherExcluded ]
53
+ private [typer] val NNConditional = Property .StickyKey [NotNullConditional ]
27
54
28
55
/** An attachment that represents unconditional flow facts established
29
56
* by this tree.
30
57
*/
31
- private [typer] val AlwaysExcluded = Property .StickyKey [Nullables . Excluded ]
58
+ private [typer] val NNInfo = Property .StickyKey [NotNullInfo ]
32
59
33
60
/** An extractor for null comparisons */
34
61
object CompareNull with
@@ -66,11 +93,11 @@ object Nullables with
66
93
def isTracked (ref : TermRef )(given Context ) = ref.isStable
67
94
68
95
def afterPatternContext (sel : Tree , pat : Tree )(given ctx : Context ) = (sel, pat) match
69
- case (TrackedRef (ref), Literal (Constant (null ))) => ctx.addExcluded (Set (ref))
96
+ case (TrackedRef (ref), Literal (Constant (null ))) => ctx.addNotNullRefs (Set (ref))
70
97
case _ => ctx
71
98
72
99
def caseContext (sel : Tree , pat : Tree )(given ctx : Context ): Context = sel match
73
- case TrackedRef (ref) if matchesNotNull(pat) => ctx.addExcluded (Set (ref))
100
+ case TrackedRef (ref) if matchesNotNull(pat) => ctx.addNotNullRefs (Set (ref))
74
101
case _ => ctx
75
102
76
103
private def matchesNotNull (pat : Tree )(given Context ): Boolean = pat match
@@ -79,48 +106,59 @@ object Nullables with
79
106
// TODO: Add constant pattern if the constant type is not nullable
80
107
case _ => false
81
108
82
- given (excluded : List [Excluded ])
83
- def containsRef (ref : TermRef ): Boolean =
84
- excluded.exists(_.contains(ref))
109
+ given (infos : List [NotNullInfo ])
110
+ @ tailRec
111
+ def containsRef (ref : TermRef ): Boolean = infos match
112
+ case info :: infos1 =>
113
+ if info.asserted.contains(ref) then true
114
+ else if info.retracted.contains(ref) then false
115
+ else containsRef(infos1)(ref)
116
+ case _ =>
117
+ false
85
118
86
- def containsAll (refs : Set [TermRef ]): Boolean =
87
- refs.forall(excluded.containsRef(_))
119
+ def extendWith (info : NotNullInfo ) =
120
+ if info.asserted.forall(infos.containsRef(_))
121
+ && ! info.retracted.exists(infos.containsRef(_))
122
+ then infos
123
+ else info :: infos
88
124
89
125
given (tree : Tree )
90
126
91
127
/* The `tree` with added attachment stating that all paths in `refs` are not-null */
92
- def withNotNullRefs ( refs : Excluded ): tree.type =
93
- if refs.nonEmpty then tree.putAttachment(AlwaysExcluded , refs )
128
+ def withNotNullInfo ( info : NotNullInfo ): tree.type =
129
+ if ! info.isEmpty then tree.putAttachment(NNInfo , info )
94
130
tree
95
131
132
+ def withNotNullRefs (refs : Set [TermRef ]) = tree.withNotNullInfo(NotNullInfo (refs, Set ()))
133
+
96
134
/* The paths that are known to be not null after execution of `tree` terminates normally */
97
- def notNullRefs (given Context ): Excluded =
98
- stripInlined(tree).getAttachment(AlwaysExcluded ) match
99
- case Some (excl ) if ! curCtx.erasedTypes => excl
100
- case _ => Set .empty
135
+ def notNullInfo (given Context ): NotNullInfo =
136
+ stripInlined(tree).getAttachment(NNInfo ) match
137
+ case Some (info ) if ! curCtx.erasedTypes => info
138
+ case _ => noNotNulls
101
139
102
140
/** The paths that are known to be not null if the condition represented
103
141
* by `tree` yields `true` or `false`. Two empty sets if `tree` is not
104
142
* a condition.
105
143
*/
106
- def condNotNullRefs (given Context ): EitherExcluded =
107
- stripBlock(tree).getAttachment(CondExcluded ) match
108
- case Some (excl ) if ! curCtx.erasedTypes => excl
109
- case _ => NoneExcluded
144
+ def notNullConditional (given Context ): NotNullConditional =
145
+ stripBlock(tree).getAttachment(NNConditional ) match
146
+ case Some (cond ) if ! curCtx.erasedTypes => cond
147
+ case _ => neitherNotNull
110
148
111
149
/** The current context augmented with nullability information of `tree` */
112
150
def nullableContext (given Context ): Context =
113
- val excl = tree.notNullRefs
114
- if excl .isEmpty then curCtx else curCtx.addExcluded(excl )
151
+ val info = tree.notNullInfo
152
+ if info .isEmpty then curCtx else curCtx.addNotNullInfo(info )
115
153
116
154
/** The current context augmented with nullability information,
117
155
* assuming the result of the condition represented by `tree` is the same as
118
156
* the value of `tru`. The current context if `tree` is not a condition.
119
157
*/
120
158
def nullableContext (tru : Boolean )(given Context ): Context =
121
- val excl = tree.condNotNullRefs
122
- if excl .isEmpty then curCtx
123
- else curCtx.addExcluded (if tru then excl .ifTrue else excl .ifFalse)
159
+ val cond = tree.notNullConditional
160
+ if cond .isEmpty then curCtx
161
+ else curCtx.addNotNullRefs (if tru then cond .ifTrue else cond .ifFalse)
124
162
125
163
/** The context to use for the arguments of the function represented by `tree`.
126
164
* This is the current context, augmented with nullability information
@@ -141,25 +179,26 @@ object Nullables with
141
179
* 2. Boolean &&, ||, !
142
180
*/
143
181
def computeNullable ()(given Context ): tree.type =
144
- def setExcluded (ifTrue : Excluded , ifFalse : Excluded ) =
145
- tree.putAttachment(CondExcluded , EitherExcluded (ifTrue, ifFalse))
146
- if ! curCtx.erasedTypes then tree match
147
- case CompareNull (TrackedRef (ref), testEqual) =>
148
- if testEqual then setExcluded(Set (), Set (ref))
149
- else setExcluded(Set (ref), Set ())
150
- case Apply (Select (x, _), y :: Nil ) =>
151
- val xc = x.condNotNullRefs
152
- val yc = y.condNotNullRefs
153
- if ! (xc.isEmpty && yc.isEmpty) then
154
- if tree.symbol == defn.Boolean_&& then
155
- setExcluded(xc.ifTrue | yc.ifTrue, xc.ifFalse & yc.ifFalse)
156
- else if tree.symbol == defn.Boolean_|| then
157
- setExcluded(xc.ifTrue & yc.ifTrue, xc.ifFalse | yc.ifFalse)
158
- case Select (x, _) if tree.symbol == defn.Boolean_! =>
159
- val xc = x.condNotNullRefs
160
- if ! xc.isEmpty then
161
- setExcluded(xc.ifFalse, xc.ifTrue)
162
- case _ =>
182
+ def setConditional (ifTrue : Set [TermRef ], ifFalse : Set [TermRef ]) =
183
+ tree.putAttachment(NNConditional , NotNullConditional (ifTrue, ifFalse))
184
+ if ! curCtx.erasedTypes then
185
+ tree match
186
+ case CompareNull (TrackedRef (ref), testEqual) =>
187
+ if testEqual then setConditional(Set (), Set (ref))
188
+ else setConditional(Set (ref), Set ())
189
+ case Apply (Select (x, _), y :: Nil ) =>
190
+ val xc = x.notNullConditional
191
+ val yc = y.notNullConditional
192
+ if ! (xc.isEmpty && yc.isEmpty) then
193
+ if tree.symbol == defn.Boolean_&& then
194
+ setConditional(xc.ifTrue | yc.ifTrue, xc.ifFalse & yc.ifFalse)
195
+ else if tree.symbol == defn.Boolean_|| then
196
+ setConditional(xc.ifTrue & yc.ifTrue, xc.ifFalse | yc.ifFalse)
197
+ case Select (x, _) if tree.symbol == defn.Boolean_! =>
198
+ val xc = x.notNullConditional
199
+ if ! xc.isEmpty then
200
+ setConditional(xc.ifFalse, xc.ifTrue)
201
+ case _ =>
163
202
tree
164
203
165
204
/** Compute nullability information for this tree and all its subtrees */
0 commit comments