@@ -81,11 +81,11 @@ class Completion extends TCompletion {
8181 or
8282 if mustHaveBooleanCompletion ( cfe ) then
8383 exists ( boolean value |
84- isConstant ( cfe , value ) |
84+ isBooleanConstant ( cfe , value ) |
8585 this = TBooleanCompletion ( value , value )
8686 )
8787 or
88- not isConstant ( cfe , _) and
88+ not isBooleanConstant ( cfe , _) and
8989 exists ( boolean b | this = TBooleanCompletion ( b , b ) )
9090 or
9191 // Corner case: In `if (x ?? y) { ... }`, `x` must have both a `true`
@@ -94,8 +94,20 @@ class Completion extends TCompletion {
9494 mustHaveNullnessCompletion ( cfe ) and
9595 this = TNullnessCompletion ( true )
9696 else if mustHaveNullnessCompletion ( cfe ) then
97+ exists ( boolean value |
98+ isNullnessConstant ( cfe , value ) |
99+ this = TNullnessCompletion ( value )
100+ )
101+ or
102+ not isNullnessConstant ( cfe , _) and
97103 this = TNullnessCompletion ( _)
98- else if mustHaveMatchingCompletion ( cfe ) then
104+ else if mustHaveMatchingCompletion ( _, cfe ) then
105+ exists ( boolean value |
106+ isMatchingConstant ( cfe , value ) |
107+ this = TMatchingCompletion ( value )
108+ )
109+ or
110+ not isMatchingConstant ( cfe , _) and
99111 this = TMatchingCompletion ( _)
100112 else if mustHaveEmptinessCompletion ( cfe ) then
101113 this = TEmptinessCompletion ( _)
@@ -118,14 +130,82 @@ class Completion extends TCompletion {
118130 }
119131}
120132
121- private predicate isConstant ( Expr e , boolean value ) {
122- e .getValue ( ) = "true" and
123- value = true
124- or
125- e .getValue ( ) = "false" and
126- value = false
127- or
128- isConstantComparison ( e , value )
133+ /** Holds if expression `e` has the Boolean constant value `value`. */
134+ private predicate isBooleanConstant ( Expr e , boolean value ) {
135+ mustHaveBooleanCompletion ( e ) and
136+ (
137+ e .getValue ( ) = "true" and
138+ value = true
139+ or
140+ e .getValue ( ) = "false" and
141+ value = false
142+ or
143+ isConstantComparison ( e , value )
144+ )
145+ }
146+
147+ /**
148+ * Holds if expression `e` is constantly `null` (`value = true`) or constantly
149+ * non-`null` (`value = false`).
150+ */
151+ private predicate isNullnessConstant ( Expr e , boolean value ) {
152+ mustHaveNullnessCompletion ( e ) and
153+ exists ( Expr stripped |
154+ stripped = e .stripCasts ( ) |
155+ stripped .getType ( ) = any ( ValueType t |
156+ not t instanceof NullableType and
157+ // Extractor bug: the type of `x?.Length` is reported as `int`, but it should
158+ // be `int?`
159+ not getQualifier * ( stripped ) .( QualifiableExpr ) .isConditional ( )
160+ ) and
161+ value = false
162+ or
163+ stripped instanceof NullLiteral and
164+ value = true
165+ or
166+ stripped .hasValue ( ) and
167+ not stripped instanceof NullLiteral and
168+ value = false
169+ )
170+ }
171+
172+ private Expr getQualifier ( QualifiableExpr e ) {
173+ // `e.getQualifier()` does not work for calls to extension methods
174+ result = e .getChildExpr ( - 1 )
175+ }
176+
177+ /**
178+ * Holds if expression `e` constantly matches (`value = true`) or constantly
179+ * non-matches (`value = false`).
180+ */
181+ private predicate isMatchingConstant ( Expr e , boolean value ) {
182+ exists ( SwitchStmt ss |
183+ mustHaveMatchingCompletion ( ss , e ) |
184+ exists ( Expr stripped |
185+ stripped = ss .getCondition ( ) .stripCasts ( ) |
186+ exists ( ConstCase cc , string strippedValue |
187+ cc = ss .getAConstCase ( ) and
188+ e = cc .getExpr ( ) and
189+ strippedValue = stripped .getValue ( ) |
190+ if strippedValue = e .getValue ( ) then
191+ value = true
192+ else
193+ value = false
194+ )
195+ or
196+ exists ( TypeCase tc , Type t , Type strippedType |
197+ tc = ss .getATypeCase ( ) |
198+ e = tc .getTypeAccess ( ) and
199+ t = e .getType ( ) and
200+ strippedType = stripped .getType ( ) and
201+ not t .isImplicitlyConvertibleTo ( strippedType ) and
202+ not t instanceof Interface and
203+ not t .containsTypeParameters ( ) and
204+ not strippedType .containsTypeParameters ( ) and
205+ value = false
206+ )
207+ )
208+ )
129209}
130210
131211/** A control flow element that is inside a `try` block. */
@@ -325,12 +405,10 @@ private predicate inNullnessContext(Expr e, boolean isNullnessCompletionForParen
325405 * Holds if a normal completion of `e` must be a matching completion. Thats is,
326406 * whether `e` determines a match in a `switch` statement.
327407 */
328- private predicate mustHaveMatchingCompletion ( Expr e ) {
329- exists ( SwitchStmt ss |
330- e = ss .getAConstCase ( ) .getExpr ( )
331- or
332- e = ss .getATypeCase ( ) .getTypeAccess ( ) // use type access to represent the type test
333- )
408+ private predicate mustHaveMatchingCompletion ( SwitchStmt ss , Expr e ) {
409+ e = ss .getAConstCase ( ) .getExpr ( )
410+ or
411+ e = ss .getATypeCase ( ) .getTypeAccess ( ) // use type access to represent the type test
334412}
335413
336414/**
0 commit comments