Add 'provider()' function.

--
MOS_MIGRATED_REVID=129889793
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java
index 1a78509..be74013 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkAspect.java
@@ -20,14 +20,12 @@
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.Printer;
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.util.Preconditions;
-
 import javax.annotation.Nullable;
 
 /** A Skylark value that is a result of an 'aspect(..)' function call. */
@@ -39,7 +37,7 @@
           + "documentation of the aspect function</a> or the "
           + "<a href=\"../aspects.md\">introduction to Aspects</a>."
 )
-public class SkylarkAspect implements SkylarkValue {
+public class SkylarkAspect implements SkylarkExportable {
   private final BaseFunction implementation;
   private final ImmutableList<String> attributeAspects;
   private final ImmutableList<Attribute> attributes;
@@ -107,6 +105,7 @@
     return paramAttributes;
   }
 
+  @Override
   public void export(Label extensionLabel, String name) {
     Preconditions.checkArgument(!isExported());
     this.aspectClass = new SkylarkAspectClass(extensionLabel, name);
@@ -137,6 +136,7 @@
     return builder.build();
   }
 
+  @Override
   public boolean isExported() {
     return aspectClass != null;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java
index f519096..5e5183c 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObject.java
@@ -124,6 +124,10 @@
     return StructConcatter.INSTANCE;
   }
 
+  public SkylarkClassObjectConstructor getConstructor() {
+    return constructor;
+  }
+
   private static class StructConcatter implements Concatter {
     private static final StructConcatter INSTANCE = new StructConcatter();
 
@@ -134,6 +138,12 @@
         Concatable left, Concatable right, Location loc) throws EvalException {
       SkylarkClassObject lval = (SkylarkClassObject) left;
       SkylarkClassObject rval = (SkylarkClassObject) right;
+      if (!lval.constructor.equals(rval.constructor)) {
+        throw new EvalException(loc,
+            String.format("Cannot concat %s with %s",
+                lval.constructor.getPrintableName(),
+                rval.constructor.getPrintableName()));
+      }
       SetView<String> commonFields = Sets
           .intersection(lval.values.keySet(), rval.values.keySet());
       if (!commonFields.isEmpty()) {
@@ -174,7 +184,7 @@
   @Override
   public void write(Appendable buffer, char quotationMark) {
     boolean first = true;
-    Printer.append(buffer, constructor.getName());
+    Printer.append(buffer, constructor.getPrintableName());
     Printer.append(buffer, "(");
     // Sort by key to ensure deterministic output.
     for (String key : Ordering.natural().sortedCopy(values.keySet())) {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
index b451efe..d090c95 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkClassObjectConstructor.java
@@ -14,22 +14,29 @@
 
 package com.google.devtools.build.lib.packages;
 
+import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
 import com.google.devtools.build.lib.syntax.FunctionSignature;
-import com.google.devtools.build.lib.syntax.FunctionSignature.WithValues;
 import com.google.devtools.build.lib.syntax.SkylarkType;
-import com.google.devtools.build.lib.syntax.Type.ConversionException;
+import com.google.devtools.build.lib.util.Preconditions;
 import java.util.Map;
+import java.util.Objects;
 import javax.annotation.Nullable;
 
 /**
  * A constructor for {@link SkylarkClassObject}.
  */
-public final class SkylarkClassObjectConstructor extends BaseFunction {
+@SkylarkModule(name = "provider",
+    doc = "A constructor for simple value objects. "
+        + "See the global <a href=\"globals.html#provider\">provider</a> function "
+        + "for more details."
+)
+public final class SkylarkClassObjectConstructor extends BaseFunction implements SkylarkExportable {
   /**
    * "struct" function.
    */
@@ -38,7 +45,10 @@
 
 
   private static final FunctionSignature.WithValues<Object, SkylarkType> SIGNATURE =
-      WithValues.create(FunctionSignature.KWARGS);
+      FunctionSignature.WithValues.create(FunctionSignature.KWARGS);
+
+  @Nullable
+  private Key key;
 
   public SkylarkClassObjectConstructor(String name, Location location) {
     super(name, SIGNATURE, location);
@@ -50,7 +60,7 @@
 
   @Override
   protected Object call(Object[] args, @Nullable FuncallExpression ast, @Nullable Environment env)
-      throws EvalException, ConversionException, InterruptedException {
+      throws EvalException, InterruptedException {
     @SuppressWarnings("unchecked")
     Map<String, Object> kwargs = (Map<String, Object>) args[0];
     return new SkylarkClassObject(this, kwargs, ast != null ? ast.getLocation() : Location.BUILTIN);
@@ -65,6 +75,26 @@
   }
 
   @Override
+  public boolean isExported() {
+    return key != null;
+  }
+
+  @Nullable
+  public Key getKey() {
+    return key;
+  }
+
+  public String getPrintableName() {
+    return key != null ? key.exportedName : getName();
+  }
+
+  @Override
+  public void export(Label extensionLabel, String exportedName) {
+    Preconditions.checkState(!isExported());
+    this.key = new Key(extensionLabel, exportedName);
+  }
+
+  @Override
   public int hashCode() {
     return System.identityHashCode(this);
   }
@@ -73,4 +103,46 @@
   public boolean equals(@Nullable Object other) {
     return other == this;
   }
+
+  /**
+   * A serializable representation of {@link SkylarkClassObjectConstructor}
+   * that uniquely identifies all {@link SkylarkClassObjectConstructor}s that
+   * are exposed to SkyFrame.
+   */
+  public static class Key {
+    private final Label extensionLabel;
+    private final String exportedName;
+
+    public Key(Label extensionLabel, String exportedName) {
+      this.extensionLabel = Preconditions.checkNotNull(extensionLabel);
+      this.exportedName = Preconditions.checkNotNull(exportedName);
+    }
+
+    public Label getExtensionLabel() {
+      return extensionLabel;
+    }
+
+    public String getExportedName() {
+      return exportedName;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(extensionLabel, exportedName);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+
+      if (!(obj instanceof Key)) {
+        return false;
+      }
+      Key other = (Key) obj;
+      return Objects.equals(this.extensionLabel, other.extensionLabel)
+          && Objects.equals(this.exportedName, other.exportedName);
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkExportable.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkExportable.java
new file mode 100644
index 0000000..003baa9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkExportable.java
@@ -0,0 +1,38 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.packages;
+
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
+import com.google.devtools.build.lib.syntax.EvalException;
+
+/**
+ * {@link SkylarkValue}s that need special handling when they are
+ * exported from an extension file. For example, rule definitions
+ * receive their name at the end of the execution of the .bzl file.
+ */
+public interface SkylarkExportable extends SkylarkValue {
+
+  /**
+   * Is this value already exported?
+   */
+  boolean isExported();
+
+  /**
+   * Notify the value that it is exported from {@code extensionLabel}
+   * extension with name {@code exportedName}.
+   */
+  void export(Label extensionLabel, String exportedName) throws EvalException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index 8b6f052..734ae0b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -64,6 +64,7 @@
 import com.google.devtools.build.lib.packages.SkylarkAspect;
 import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor;
+import com.google.devtools.build.lib.packages.SkylarkExportable;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.packages.TestSize;
 import com.google.devtools.build.lib.rules.SkylarkAttr.Descriptor;
@@ -194,7 +195,7 @@
 
   @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc =
       "Creates an immutable struct using the keyword arguments as attributes. It is used to group "
-          + "multiple values together.Example:<br>"
+          + "multiple values together. Example:<br>"
           + "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n"
           + "return s.x + getattr(s, \"y\")  # returns 5</pre>",
       extraKeywords = @Param(name = "kwargs", doc = "the struct attributes"),
@@ -202,6 +203,23 @@
   private static final SkylarkClassObjectConstructor struct =
       SkylarkClassObjectConstructor.STRUCT;
 
+  @SkylarkSignature(name = "provider", returnType = SkylarkClassObjectConstructor.class, doc =
+      "Creates a declared provider 'constructor'. The return value of this"
+          + "function can be used to create \"struct-like\" values. Example:<br>"
+          + "<pre class=\"language-python\">data = provider()\n"
+          + "d = data(x = 2, y = 3)"
+          + "return d.x + d.y # returns 5</pre>",
+      useLocation = true
+  )
+  private static final BuiltinFunction provider =
+      new BuiltinFunction("provider") {
+        public SkylarkClassObjectConstructor invoke(Location location) {
+          return new SkylarkClassObjectConstructor(
+              "<no name>", // name is set on export.
+              location);
+        }
+      };
+
 
   // TODO(bazel-team): implement attribute copy and other rule properties
   @SkylarkSignature(name = "rule", doc =
@@ -473,7 +491,7 @@
 
 
   /** The implementation for the magic function "rule" that creates Skylark rule classes */
-  public static final class RuleFunction extends BaseFunction {
+  public static final class RuleFunction extends BaseFunction implements SkylarkExportable {
     private RuleClass.Builder builder;
 
     private RuleClass ruleClass;
@@ -544,7 +562,7 @@
     /**
      * Export a RuleFunction from a Skylark file with a given name.
      */
-    void export(Label skylarkLabel, String ruleClassName) throws EvalException {
+    public void export(Label skylarkLabel, String ruleClassName) throws EvalException {
       Preconditions.checkState(ruleClass == null && builder != null);
       this.skylarkLabel = skylarkLabel;
       if (type == RuleClassType.TEST != TargetUtils.isTestRuleName(ruleClassName)) {
@@ -569,35 +587,40 @@
       Preconditions.checkState(ruleClass != null && builder == null);
       return ruleClass;
     }
+
+    @Override
+    public boolean isExported() {
+      return skylarkLabel != null;
+    }
   }
 
+  /**
+   * All classes of values that need special processing after they are exported
+   * from an extension file.
+   *
+   * Order in list list is significant: all {@link }SkylarkAspect}s need to be exported
+   * before {@link RuleFunction}s etc.
+   */
+  private static final List<Class<? extends SkylarkExportable>> EXPORTABLES =
+      ImmutableList.of(
+          SkylarkClassObjectConstructor.class,
+          SkylarkAspect.class,
+          RuleFunction.class);
+
   public static void exportRuleFunctionsAndAspects(Environment env, Label skylarkLabel)
       throws EvalException {
     Set<String> globalNames = env.getGlobals().getDirectVariableNames();
 
-    // Export aspects first since rules can depend on aspects.
-    for (String name : globalNames) {
-      Object value = env.lookup(name);
-      if (name == null) {
-        throw new AssertionError(String.format("No such variable: '%s'", name));
-      }
-      if (value instanceof SkylarkAspect) {
-        SkylarkAspect skylarkAspect = (SkylarkAspect) value;
-        if (!skylarkAspect.isExported()) {
-          skylarkAspect.export(skylarkLabel, name);
+    for (Class<? extends SkylarkExportable> exportable : EXPORTABLES) {
+      for (String name : globalNames) {
+        Object value = env.lookup(name);
+        if (value == null) {
+          throw new AssertionError(String.format("No such variable: '%s'", name));
         }
-      }
-    }
-
-    for (String name : globalNames) {
-      Object value = env.lookup(name);
-      if (value == null) {
-        throw new AssertionError(String.format("No such variable: '%s'", name));
-      }
-      if (value instanceof RuleFunction) {
-        RuleFunction function = (RuleFunction) value;
-        if (function.skylarkLabel == null) {
-          function.export(skylarkLabel, name);
+        if (exportable.isInstance(value)) {
+          if (!exportable.cast(value).isExported()) {
+            exportable.cast(value).export(skylarkLabel, name);
+          }
         }
       }
     }
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index 9edfe35..f8823b9 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -960,7 +960,6 @@
     return MutableList.<Object>of(env, 1, 2, 3);
   }
 
-
   @Test
   public void testStructMutabilityDeep() throws Exception {
     assertTrue(EvalUtils.isImmutable(Tuple.<Object>of(makeList(null))));
@@ -972,4 +971,56 @@
     assertFalse(EvalUtils.isImmutable(makeBigStruct(ev.getEnvironment())));
   }
 
+  @Test
+  public void declaredProviders() throws Exception {
+    evalAndExport(
+        "data = provider()",
+        "d = data(x = 1, y ='abc')",
+        "d_x = d.x",
+        "d_y = d.y"
+    );
+    assertThat(lookup("d_x")).isEqualTo(1);
+    assertThat(lookup("d_y")).isEqualTo("abc");
+    SkylarkClassObjectConstructor dataConstructor = (SkylarkClassObjectConstructor) lookup("data");
+    SkylarkClassObject data = (SkylarkClassObject) lookup("d");
+    assertThat(data.getConstructor()).isEqualTo(dataConstructor);
+    assertThat(dataConstructor.isExported()).isTrue();
+    assertThat(dataConstructor.getPrintableName()).isEqualTo("data");
+    assertThat(dataConstructor.getKey()).isEqualTo(
+        new SkylarkClassObjectConstructor.Key(FAKE_LABEL, "data")
+    );
+  }
+
+  @Test
+  public void declaredProvidersConcatSuccess() throws Exception {
+    evalAndExport(
+        "data = provider()",
+        "dx = data(x = 1)",
+        "dy = data(y = 'abc')",
+        "dxy = dx + dy",
+        "x = dxy.x",
+        "y = dxy.y"
+    );
+    assertThat(lookup("x")).isEqualTo(1);
+    assertThat(lookup("y")).isEqualTo("abc");
+    SkylarkClassObjectConstructor dataConstructor = (SkylarkClassObjectConstructor) lookup("data");
+    SkylarkClassObject dx = (SkylarkClassObject) lookup("dx");
+    assertThat(dx.getConstructor()).isEqualTo(dataConstructor);
+    SkylarkClassObject dy = (SkylarkClassObject) lookup("dy");
+    assertThat(dy.getConstructor()).isEqualTo(dataConstructor);
+  }
+
+  @Test
+  public void declaredProvidersConcatError() throws Exception {
+    evalAndExport(
+        "data1 = provider()",
+        "data2 = provider()"
+    );
+
+    checkEvalError("Cannot concat data1 with data2",
+        "d1 = data1(x = 1)",
+        "d2 = data2(y = 2)",
+        "d = d1 + d2"
+    );
+  }
 }