Repository: bazelbuild/buildtools
Pull request #1130 (2023-01-25 14:01:12)

Fix the uninitialized variable warning for lambda expressions


Patch:
diff --git a/warn/warn_control_flow.go b/warn/warn_control_flow.go
index 7e788d6..7566515 100644
--- a/warn/warn_control_flow.go
+++ b/warn/warn_control_flow.go
@@ -225,12 +225,13 @@ func noEffectWarning(f *build.File) []*LinterFinding {
 	return findings
 }
 
-// extractIdentsFromStmt returns all idents from the an AST node representing a
+// extractIdentsFromStmt returns all idents from an AST node representing a
 // single statement that are either defined outside the node and used inside,
 // or defined inside the node and can be used outside.
 // Examples of idents that don't fall into either of the categories:
 //   - Named arguments of function calls: `foo` in `f(foo = "bar")`
 //   - Iterators of comprehension nodes and its usages: `x` in `[f(x) for x in y]`
+//   - Lambda arguments: `x` in `lambda x: x + 1`
 //
 // Statements that contain other statements (for-loops, if-else blocks) are not
 // traversed inside.
@@ -305,7 +306,7 @@ func extractIdentsFromStmt(stmt build.Expr) (assigned, used map[*build.Ident]boo
 			}
 
 		case *build.ForStmt:
-			// Like AssignExpr, ForStmt too have an analogue of LHS and RHS.
+			// Like AssignExpr, ForStmt too has an analogue of LHS and RHS.
 			// Unlike AssignExpr, in this function they may appear only in the root of
 			// traversal and shouldn't be traversed inside (the caller of
 			// `extractIdentsFromStmt` should be responsible for checking all
@@ -354,6 +355,17 @@ func extractIdentsFromStmt(stmt build.Expr) (assigned, used map[*build.Ident]boo
 				}
 			}
 			scopes[expr] = scope
+
+		case *build.LambdaExpr:
+			// Similar to Comprehension nodes
+			scope := make(map[string]bool)
+			for _, param := range expr.Params {
+				for _, lValue := range bzlenv.CollectLValues(param) {
+					scope[lValue.Name] = true
+				}
+			}
+			scopes[expr] = scope
+
 		case *build.Ident:
 			// If the identifier is defined in an intermediate scope, ignore it.
 			for _, node := range stack {
@@ -751,11 +763,12 @@ func findUninitializedVariables(stmts []build.Expr, previouslyInitialized map[st
 
 		build.WalkInterruptable(expr, func(expr build.Expr, stack []build.Expr) (err error) {
 			switch expr := expr.(type) {
-			case *build.Comprehension:
-				// Comprehension nodes are special, they have their own scope with variables
-				// that are only defined inside. Instead of traversing inside stop the
-				// traversal and call a special function to retrieve idents from the outer
-				// scope that are used inside the comprehension.
+			case *build.Comprehension, *build.LambdaExpr:
+				// Comprehension and Lambda nodes are special, they have their own scope
+				// with variables that are only defined inside.
+				// Instead of traversing inside stop the traversal and call a special
+				// function to retrieve idents from the outer scope that are used inside
+				// the comprehension.
 
 				_, used := extractIdentsFromStmt(expr)
 				for ident := range used {


Test patch:
diff --git a/warn/warn_control_flow_test.go b/warn/warn_control_flow_test.go
index 2ef8f90..49e6baa 100644
--- a/warn/warn_control_flow_test.go
+++ b/warn/warn_control_flow_test.go
@@ -1262,4 +1262,15 @@ def foo():
 			":6: Variable \"z\" may not have been initialized.",
 		},
 		scopeEverywhere)
+
+	checkFindings(t, "uninitialized", `
+def foo():
+  for bar in baz:
+    pass
+
+  y = [baz.get(bar) for bar in bars]
+  x = lambda bar: baz.get(bar)
+`,
+		[]string{},
+		scopeEverywhere)
 }