@@ -20,6 +20,36 @@ private int numStmts(ForEachStmt fes) {
2020 else result = 1
2121}
2222
23+ private predicate returnsLoopVariable ( ForEachStmt fes , Stmt s ) {
24+ exists ( ReturnStmt ret |
25+ ret = s .stripSingletonBlocks ( ) and
26+ ret .getExpr ( ) .stripImplicit ( ) .( VariableAccess ) .getTarget ( ) = fes .getVariable ( )
27+ )
28+ }
29+
30+ private predicate hasNullDefault ( Type t ) { t .isRefType ( ) or t instanceof NullableType }
31+
32+ private predicate returnsDefaultValueAfterForeach ( ForEachStmt fes ) {
33+ exists ( BlockStmt enclosingBlock , int i , Type elementType , ReturnStmt ret |
34+ enclosingBlock .getStmt ( i ) = fes and
35+ enclosingBlock .getStmt ( i + 1 ) = ret and
36+ elementType = fes .getVariable ( ) .getType ( )
37+ |
38+ ret .getExpr ( ) .stripImplicit ( ) instanceof NullLiteral and
39+ hasNullDefault ( elementType )
40+ or
41+ exists ( DefaultValueExpr defaultValue |
42+ defaultValue = ret .getExpr ( ) .stripImplicit ( ) and
43+ (
44+ defaultValue .getType ( ) = elementType
45+ or
46+ hasNullDefault ( elementType ) and
47+ hasNullDefault ( defaultValue .getType ( ) )
48+ )
49+ )
50+ )
51+ }
52+
2353private predicate terminatesCallable ( Stmt s ) {
2454 exists ( Stmt stripped | stripped = s .stripSingletonBlocks ( ) |
2555 stripped instanceof ReturnStmt
@@ -183,6 +213,29 @@ predicate missedWhereOpportunity(ForEachStmtGenericEnumerable fes, IfStmt is) {
183213 )
184214}
185215
216+ /**
217+ * Holds if `foreach` statement `fes` could be converted to a `.FirstOrDefault()` call.
218+ * That is, the loop contains a single `if` statement that accesses the loop variable,
219+ * returns the loop variable when the condition matches, and is followed by a default return.
220+ */
221+ predicate missedFirstOrDefaultOpportunity ( ForEachStmtGenericEnumerable fes , IfStmt is ) {
222+ // The loop only checks whether the current element is the first match.
223+ is = firstStmt ( fes ) and
224+ not exists ( is .getElse ( ) ) and
225+ numStmts ( fes ) = 1 and
226+ // Condition relies on loop variable.
227+ exists ( VariableAccess va |
228+ va .getTarget ( ) = fes .getVariable ( ) and
229+ va = is .getCondition ( ) .getAChildExpr * ( )
230+ ) and
231+ not is .getCondition ( ) .getAChildExpr * ( ) instanceof AwaitExpr and
232+ not fes .isAsync ( ) and
233+ not fes .getVariable ( ) .isCaptured ( ) and
234+ returnsLoopVariable ( fes , is .getThen ( ) ) and
235+ fes .getElementType ( ) = fes .getVariable ( ) .getType ( ) and
236+ returnsDefaultValueAfterForeach ( fes )
237+ }
238+
186239//#################### CLASSES ####################
187240/** A LINQ Any(...) call. */
188241class AnyCall extends MethodCall {
0 commit comments