Return early if an empty dictionary is returned from starlark transitions (so it can't be misinterpreted as an empty dictionary of dictionaries).

PiperOrigin-RevId: 249097114
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java
index ee16fef..ff2056d 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java
@@ -214,6 +214,13 @@
       }
 
       if (result instanceof SkylarkDict<?, ?>) {
+        // If we're recieving an empty dictionary, it's an error. Even if a
+        // transition function sometimes evaluates to a no-op, it needs to return the passed in
+        // settings. Return early for now since better error reporting will happen in
+        // {@link FunctionTransitionUtil#validateFunctionOutputsMatchesDeclaredOutputs}
+        if (((SkylarkDict) result).isEmpty()) {
+          return ImmutableList.of(ImmutableMap.of());
+        }
         // TODO(bazel-team): integrate keys with ctx.split_attr. Currently ctx.split_attr always
         // keys on cpu value - we should be able to key on the keys returned here.
         try {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
index fd1361f..89de7ca 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
@@ -908,4 +908,39 @@
     assertThat(configuration.getOptions().get(TestOptions.class).testArguments)
         .containsExactly("post-transition");
   }
+
+  /**
+   * Regression test to ensure that an empty dict is not interpreted as a dict of dicts and
+   * generates the proper error message.
+   */
+  @Test
+  public void testEmptyReturnDict() throws Exception {
+    writeWhitelistFile();
+    scratch.file(
+        "test/transitions.bzl",
+        "def _impl(settings, attr):",
+        "  return {}",
+        "my_transition = transition(implementation = _impl, inputs = [],",
+        "  outputs = ['//command_line_option:test_arg'])");
+    scratch.file(
+        "test/rules.bzl",
+        "load('//test:transitions.bzl', 'my_transition')",
+        "def _impl(ctx):",
+        "  return []",
+        "my_rule = rule(",
+        "  implementation = _impl,",
+        "  cfg = my_transition,",
+        "  attrs = {",
+        "    '_whitelist_function_transition': attr.label(",
+        "        default = '//tools/whitelists/function_transition_whitelist',",
+        "    ),",
+        "  })");
+    scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//test");
+    assertContainsEvent(
+        "transition outputs [//command_line_option:test_arg] were "
+            + "not defined by transition function");
+  }
 }