Accept a single provider instance as return value from an aspect.

Previously, if a single provider instance was returned, it was silently dropped.

This makes the behavior consistent with rule implementation functions.
Fixes #5955

RELNOTES: None.
PiperOrigin-RevId: 209777202
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
index 12a3e98..150cfb4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
@@ -29,6 +29,7 @@
 import com.google.devtools.build.lib.packages.InfoInterface;
 import com.google.devtools.build.lib.packages.SkylarkDefinedAspect;
 import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.packages.StructProvider;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
 import com.google.devtools.build.lib.syntax.Environment;
@@ -86,10 +87,11 @@
         if (ruleContext.hasErrors()) {
           return null;
         } else if (!(aspectSkylarkObject instanceof StructImpl)
-            && !(aspectSkylarkObject instanceof Iterable)) {
+            && !(aspectSkylarkObject instanceof Iterable)
+            && !(aspectSkylarkObject instanceof InfoInterface)) {
           ruleContext.ruleError(
-              String.format(
-                  "Aspect implementation should return a struct or a list, but got %s",
+              String.format("Aspect implementation should return a struct, a list, or a provider "
+                      + "instance, but got %s",
                   SkylarkType.typeOf(aspectSkylarkObject)));
           return null;
         }
@@ -115,25 +117,32 @@
     if (aspectSkylarkObject instanceof Iterable) {
       addDeclaredProviders(builder, (Iterable) aspectSkylarkObject);
     } else {
-      StructImpl struct = (StructImpl) aspectSkylarkObject;
-      Location loc = struct.getCreationLoc();
-      for (String field : struct.getFieldNames()) {
-        if (field.equals("output_groups")) {
-          addOutputGroups(struct.getValue(field), loc, builder);
-        } else if (field.equals("providers")) {
-          Object value = struct.getValue(field);
-          Iterable providers =
-              SkylarkType.cast(
-                  value,
-                  Iterable.class,
-                  loc,
-                  "The value for \"providers\" should be a list of declared providers, "
-                      + "got %s instead",
-                  EvalUtils.getDataTypeName(value, false));
-          addDeclaredProviders(builder, providers);
-        } else {
-          builder.addSkylarkTransitiveInfo(field, struct.getValue(field), loc);
+      // Either an old-style struct or a single declared provider (not in a list)
+      InfoInterface info = (InfoInterface) aspectSkylarkObject;
+      Location loc = info.getCreationLoc();
+      if (info.getProvider().getKey().equals(StructProvider.STRUCT.getKey())) {
+        // Old-style struct, that may contain declared providers.
+        StructImpl struct = (StructImpl) aspectSkylarkObject;
+        for (String field : struct.getFieldNames()) {
+          if (field.equals("output_groups")) {
+            addOutputGroups(struct.getValue(field), loc, builder);
+          } else if (field.equals("providers")) {
+            Object value = struct.getValue(field);
+            Iterable providers =
+                SkylarkType.cast(
+                    value,
+                    Iterable.class,
+                    loc,
+                    "The value for \"providers\" should be a list of declared providers, "
+                        + "got %s instead",
+                    EvalUtils.getDataTypeName(value, false));
+            addDeclaredProviders(builder, providers);
+          } else {
+            builder.addSkylarkTransitiveInfo(field, struct.getValue(field), loc);
+          }
         }
+      } else {
+        builder.addSkylarkDeclaredProvider(info);
       }
     }
 
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java
index 248d609..c39a1fc 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkDefinedAspectsTest.java
@@ -89,6 +89,30 @@
   }
 
   @Test
+  public void aspectWithSingleDeclaredProvider() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        "foo = provider()",
+        "def _impl(target, ctx):",
+        "   return foo()",
+        "MyAspect = aspect(implementation=_impl)");
+    scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+    assertThat(getAspectDescriptions(analysisResult))
+        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+    ConfiguredAspect configuredAspect = Iterables.getOnlyElement(analysisResult.getAspects())
+        .getConfiguredAspect();
+
+    SkylarkKey fooKey =
+        new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "foo");
+
+    assertThat(configuredAspect.get(fooKey).getProvider().getKey()).isEqualTo(fooKey);
+  }
+
+  @Test
   public void aspectWithDeclaredProviders() throws Exception {
     scratch.file(
         "test/aspect.bzl",
@@ -868,7 +892,8 @@
     } catch (ViewCreationFailedException e) {
       // expect to fail.
     }
-    assertContainsEvent("Aspect implementation should return a struct or a list, but got int");
+    assertContainsEvent("Aspect implementation should return a struct, a list, or a provider "
+        + "instance, but got int");
   }
 
   @Test